定期的にまとめたい、技術系のおはなし。
スマホアプリの実績集計のデータ蓄積にMongoDB×Javaを使用していて、いろいろなことに遭遇しました。備忘録がてら、どのようなことが起きて、対処したかをまとめました。
根本的な解決にならず、一時的な対処方法である方法もあるかもしれません。 時間が経過して対応方法が変わったりしたときは、この記事に追記します。
また英語翻訳はGoogle先生にお任せしています。
MongoDB×Java
対応している諸々のバージョンとか
以前の記事でお話した環境で開発しています。まったくかわっていないのが、情報乗っけておきますね。
- Java 1.8
- Spring Framework 4.3.4
- MyBatis 3.3.0
- JSONIC 1.3.10
- MySQL5.7
- MongoDB 3.4
以降、いろいろ経験していて
MongoDBの接続が断続的になる
アプリを使用してるユーザーが多くなり、MongoDBへの読み書きが多くなってきたころ、アプリロジック上は問題ないはずなのに、MongoDBの接続が断続的になってしまう現象が発生。
Java内で接続している箇所は以下のような実装していました。
※メソッドわけだったり、implementsだったり、いろいろ省略していますが、だいたいこんなかんじ。
public class MovieInfoService { /** * MongoClient */ private MongoClient mongoClient; public MongoDatabase mongoSample() { // Mongoクライアントオブジェクト取得 []内は環境設定 this.mongoClient = new MongoClient( new MongoClientURI("mongodb://" + [hostPort] + "/?replicaSet=" + [replicaSetName])); // MongoDBに対する処理 // Mongoクライアントクローズ this.mongoClient.close(); } }
原因:MongoDBClientインスタンスを接続するたびにclose()していた
色々調べてみたんだけど、MongoDBClientインスタンスをクローズしていて、インスタンスを失くしてしまうことが悪いっぽい。
mongo throws IllegalStateException: state should be: open
I meet IllegalStateException: state should be: open and wonder how to solve it. Below is more context: [mongo context] mongo replica set: 3 nodes version: 3.4 with WiredTiger mongo driver in the app: java driver [app context] We have 12 workers to process log and write/read related information to mongo.
だったり。
以下、MongoDB JavaDriver 接続方法 の引用。
Typically you only create one MongoClient instance for a given MongoDB deployment (e.g. standalone, replica set, or a sharded cluster) and use it across your application. However, if you do create multiple instances:
All resource usage limits (e.g. max connections, etc.) apply per MongoClient instance.
To dispose of an instance, call MongoClient.close() to clean up resources.
訳
通常は、特定のMongoDBデプロイメント用に1つのMongoClientインスタンス(スタンドアロン、レプリカセット、またはシャードされたクラスタなど)を作成し、アプリケーション全体で使用します。ただし、複数のインスタンスを作成する場合は、
MongoClientインスタンスごとに、すべてのリソース使用制限(最大接続など)が適用されます。
インスタンスを破棄するには、MongoClient.close()を呼び出してリソースをクリーンアップします。
1つのMongoClientインスタンスで使いまわせってめっちゃ書いてるのをね、見落としてたんですよね……。
対策1:MongoClientインスタンス
新しい接続のときはMongoClientインスタンスを作成させて、既にあれば作成しないようにします。
また、むやみにクローズさせないようにします。
実装の方はカンタン。1文追加。close()メソッドは呼び出さないようにします。
public class MovieInfoService { /** * MongoClient */ private MongoClient mongoClient; public MongoDatabase mongoSample() { // Mongoクライアントオブジェクト取得 []内は環境設定 if(this.mongoClient==null) this.mongoClient = new MongoClient( new MongoClientURI("mongodb://" + [hostPort] + "/?replicaSet=" + [replicaSetName])); // MongoDBに対する処理 // Mongoクライアントクローズ this.mongoClient.close(); } }
対策2:接続するmaxPoolSizeの調整
MongoDBに接続する際、接続プール内の最大接続数を設定することができます。アプリケーションによっては調整が必要になってくる箇所だと思う。
public class MovieInfoService { /** * MongoClient */ private MongoClient mongoClient; public MongoDatabase mongoSample() { // Mongoクライアントオブジェクト取得 []内は環境設定 if(this.mongoClient==null) this.mongoClient = new MongoClient( new MongoClientURI("mongodb://" + hostPort + "/?replicaSet=" + replicaSetName+"&maxPoolSize="+[maxPoolSize])); // MongoDBに対する処理 // Mongoクライアントクローズ this.mongoClient.close(); } }
こちらは検証した結果、既存のアプリでは結果的にはデフォルトの値(100)で問題ないことになりました。
Connection String URI Format – MongoDB Manual
You can specify the MongoDB connection string using either: This section describes the standard format of the MongoDB connection URI used to connect to a MongoDB deployment: standalone, replica set, or a sharded cluster. The standard URI connection scheme has the form: Examples For more examples, see .
aggregateでMongoCommandExceptionエラー
MongoDBに蓄積しているデータをaggregateを使って集計したりしているのですが、前日まで正常に動いていたのに、MongoCommandExceptionエラーが発生。
MongoCommandExceptionエラーのメッセージが以下のとおり。
Command failed with error 16819: ‘Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. Aborting operation. Pass allowDiskUse:true to opt in.’ on server ip-127-0-0-1.localdomain:27017.
The full response is { “ok” : 0.0, “errmsg” : “Sort exceeded memory limit of
104857600 bytes, but did not opt in to external sorting. Aborting operation.
Pass allowDiskUse:true to opt in.”, “code” : 16819, “codeName” :
“Location16819” }
原因:aggregate処理で取得したデータ量が大きかった
通常のMongoDBへの接続だと一時ファイルへの書き込みをせずaggregate処理を行う。
その取得したデータをソートしているのだが、そのデータの上限が104857600 バイト、つまり約100MBとのこと。
対策:aggregate処理にallowDiskUseオプションを使用する
aggregate処理のとき、一時ファイルへの書き込みを有効にできるallowDiskUseオプションを使用して接続する。
db.sampleCollection.aggregate( [ {aggregateの処理} ], {allowDiskUse: true} );
JavaだとgetCollection().aggregateメソッドを使用するときにallowDiskUseオプション使用することになる。たとえば、下のような感じ。
List<BsonDocument> pipeline = new ArrayList<BsonDocument>(); // pipelineの処理作成 //MongoDB接続後、allowDiskUseをtrueにして接続する AggregateIterable<Document> iterable = getCollection().aggregate(pipeline).allowDiskUse(true); // aggregate処理で取得した値の処理
一時ファイルへの書き込みを許可することになるので、処理を行っているサーバーのHDDの容量には注意が必要です。
HHDの容量があまりにも小さい場合は、容量増加の検討をしてみてください。
aggregate – MongoDB Manual
The command has following syntax: Changed in version 3.6. The command takes the following fields as arguments: string The name of the collection or view that acts as the input for the aggregation pipeline. Use for collection agnostic commands. array An array of aggregation pipeline stages that process and transform the document stream as part of the aggregation pipeline.
まとめ
参考記事などが英語が多く、ノウハウが落ちていることが少ないMongoDB×Java。
なにかの参考になれば幸いです。
MongoDB×Javaのアプリケーション開発で立ち上げたときに苦労したことだったり、いろいろまとめたのがあるので、そちらもよければどうぞ。
参考URL
What’s New
This release includes full support for the upcoming MongoDB 3.4 server release. Key new features include: import org.bson.types.Decimal128; The Decimal128 format supports numbers with up to 34 decimal digits (i.e. significant digits) and an exponent range of −6143 to +6144. To create a Decimal128 number, you can use import com.mongodb.client.model.Collation; Collation allows users to specify language-specific rules for string comparison.