レッツトライ!しもしも

エンジニアときどきイラストレーターのしもしもがレッツトライ!したことを描くブログ

MongoDB×Javaの保守開発でつまづいたところをまとめてみました

      2018/06/27

定期的にまとめたい、技術系のおはなし。
スマホアプリの実績集計のデータ蓄積に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だったり、いろいろ省略していますが、だいたいこんなかんじ。

原因:MongoDBClientインスタンスを接続するたびにclose()していた

色々調べてみたんだけど、MongoDBClientインスタンスをクローズしていて、インスタンスを失くしてしまうことが悪いっぽい。

[JAVA-2609] mongo throws IllegalStateException: state should be: open – MongoDB

log] 2017-09-15 17:57:17.505 ERROR 14373 [SQSConsumerFixed-18] – c.p.s.worker.sqs.DiagLogWorkerHandler :[2118482-264832996-317561309355740509, OSDiagLogWorkerHandler] Got exception that will be …

だったり。

以下、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()メソッドは呼び出さないようにします。

対策2:接続するmaxPoolSizeの調整

MongoDBに接続する際、接続プール内の最大接続数を設定することができます。アプリケーションによっては調整が必要になってくる箇所だと思う。

こちらは検証した結果、既存のアプリでは結果的にはデフォルトの値(100)で問題ないことになりました。

Connection String URI Format – MongoDB Manual

The mongodb+srv option will fail if there is no available DNS with records that correspond to the hostname identified in the connection string. In addition, use of the connection string modifier sets the option to automatically for the connection. This can be overridden by explicitly setting the option to with in the query string.

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オプションを使用して接続する。

JavaだとgetCollection().aggregateメソッドを使用するときにallowDiskUseオプション使用することになる。たとえば、下のような感じ。

一時ファイルへの書き込みを許可することになるので、処理を行っているサーバーのHDDの容量には注意が必要です。
HHDの容量があまりにも小さい場合は、容量増加の検討をしてみてください。

https://docs.mongodb.com/v3.4/reference/command/aggregate/

まとめ

参考記事などが英語が多く、ノウハウが落ちていることが少ないMongoDB×Java。
なにかの参考になれば幸いです。

MongoDB×Javaのアプリケーション開発で立ち上げたときに苦労したことだったり、いろいろまとめたのがあるので、そちらもよければどうぞ。

MongoDB×Javaを仕事でつかったのでまとめてみました

NoSQLのひとつであるMongoDBを仕事で使いまして、基本設計から実装・テスト・実際にリリースまでこぎつけしました。 新しい技術はGoogle先生から先行者の記事たちのありがたみを感じる日々。感謝もこめて、参考になった記事、つまづいたところとかをまとめました。 …

参考URL

What’s New

Collation collation = Collation.builder() .collationStrength(CollationStrength.SECONDARY) .locale(“fr”).build(); collection.count(Filters.eq(“category”, “cafe”), new CountOptions().collation(collation)); collection.updateOne(Filters.eq(“category”, “cafe”), set(“stars”, 1), new UpdateOptions().collation(collation)); collection.bulkWrite(Arrays.asList( new UpdateOneModel (new Document(“category”, “cafe”), new Document(“$set”, new Document(“x”, 0)), new UpdateOptions().collation(collation)), new DeleteOneModel (new Document(“category”, “cafe”), new DeleteOptions().collation(collation))));

シェアする

 - Tips ,