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

Tips

NoSQLのひとつであるMongoDBを仕事で使いまして、基本設計から実装・テスト・実際にリリースまでこぎつけしました。

新しい技術はGoogle先生から先行者の記事たちのありがたみを感じる日々。感謝もこめて、参考になった記事、つまづいたところとかをまとめました。

どんな業務でMogoDBを使ったの?

スマホアプリの実績集計のデータ蓄積にMongoDBを使用しました。スマホアプリの履歴情報を別のWebシステムで蓄積したデータを集計して、グラフ表示します。

サーバサイドのシステム構成はこんな感じ。

  • Java 1.8
  • Spring Framework 4.3.4
  • MyBatis 3.3.0
  • JSONIC 1.3.10
  • MySQL5.7
  • MongoDB 3.4

マスタや基本的な業務はMySQLで実績のデータだけMongoDBを利用しています。

そもそもMongoDBについて知りたい!

MongoDBとは、ドキュメント指向データベースです。スキーマを定義しなくても使用できるスキーマレスである
複雑な検索条件でデータを取得することが可能になります。

かなり暴言的な言い方だけど、「JSON形式に似たオブジェクトデータを、入れ子になっていてもそのままDBに入れて、検索したり集計するとすっごく早いよ!」ていうDBです。

下のシリーズはMongoDBとはなんぞや、がすごくわかりやすかったですね。第5回まで読むとだいたいつかめる。

第1回 使ってみようMongoDB:MongoDBでゆるふわDB体験

第1回目となる今回は,まずMongoDBの概要と特徴的な機能を解説し,どのようなケースで有効に使えるかを紹介します。 過去20年間でCPUの処理能力は数十倍になり,ディスクの1バイトあたりの金額は1000分の1になりました。開発環境はクラウドに移行し,扱うデータ量とWebサイトのアクセス数は大幅に増加しました。このような環境の変化から,データストアへ求められるものが変化してきています。 …

あと、こっちも。「MongoDBの薄い本」

No Title

No Description

上の2つを読んだあと、オライリーの本を読んだら、飲み込みが早かったです。

JavaでMongoDBに登録する

サーバサイドがJavaなのは決まっていたので、MongoDBのドキュメントとにらめっこしながら。

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.

補足的に、以下の記事を見るとやりやすいと思う。※サーバ接続の記述が古いままなので注意です。

JavaからMongoDBへのアクセス(接続、検索、insert、update、delete) – Qiita

Java MongoDB Driver](http://docs.mongodb.org/ecosystem/drivers/java/)を使ってJavaからMongoDBにアクセスする方法について、全くはじめての人向けメモ。 M…

スキーマ設計

「できるかぎりRDBを触らず、MongoDBだけで集計が済むようにする。」というのが、携わった案件の目標だったので、蓄積していくデータを考えることが重要でした。

以下の記事がスキーマ設計の仕方がすごくわかりやすかった。

MongoDBにおける関連(Relation)のスキーマ設計 – masa_wの日記

前回、MongoDBで SNSつくるぞという記事を書いてから随分時間がたってしまいました。単に私がだらけていたということもあるのですが、一番ひっかかって時間を取られていたのが、MongoDBにおける スキーマ 設計の考え方です。 いまだに試行錯誤中ではありますが、現時点において私がこうあるべきと理解しているところをアウトプットしてみたいと思います。 1.One to Many のケース …

今回は上の記事でいうスキーマ設計は「パターン2 明細埋め込み」でした。

MongoDBに入ったデータを集計する

クエリを考える

SQLが書けるなら、ここの記事がすごくわかりやすい。検索方法はだいたいこれみればわかる。

[Mongo] findメソッドのいろいろな使い方(MySQLと比較) - YoheiM .NET
以前にフロントエンドエンジニアにオススメなデータベース、MongoDBに入門とフロントエンドエンジニアにもできるMongoDBを使ったログ分析のブログを書いたのち、MongoDBを引き続き使っていますが、findの使い方を調べることが多く、今回はそれらをブログに残しておきたいと思います。

「MongoDB Aggregation Framework」で集計する

しかしながら、膨大なデータを集計し条件が複雑になるので、集計する上のクエリではなく「MongoDB Aggregation Framework」使うことになりました。

ただ、上のオライリーは Aggregation Framework をカバーしていないので、MongoDBのマニュアルを読むことが必要になってきます。ただし、英語オンリー。

Aggregation – MongoDB Manual

No Description

使えるようになるまでかなり苦戦した。以下の記事を見ると、つかみばっちり。

MongoDBのAggregationについて基本的なこと – Qiita

More than 5 years have passed since last update. 最近RailsでMongoDBを使うことがあって、集計処理の部分でちょっと基本的な知識が不足気味な感じがしたので、公式ドキュメントを漁って理解を深めるなどしてみようかと。 http://docs.mongodb.org/manual/meta/aggregation-quick-reference/ SQLでいうgroup byのようなものを実現する。 こんな感じのドキュメントがあるとして 文具(stationery)の中で最も高価な商品を抽出します。 これに抽出条件を追加してみます。SQLでいうwhere条件みたいなものですね。 消しゴム(eraser)を除いて、最も高価な商品を抽出します。 ちなみに、この$matchや$group というものはpipelineと呼ばれています。 projection(射影とか投影とか) 出力する項目を指定することが出来る。SQLでいうSELECT col1, col2 〜 みたいな感じ。 _id列がいらない場合 一律40円値上げしてみる(price + 40)。 “raised_price”が列別名になる。 単に列別名にするだけにも使える。 サブドキュメントを含めた形式で返す。 うむむ。少し基本的なところが理解できた。 他にも$unwind, $redact, $out など気になるのがたくさんあるのでもう少し継続してみることにしますね! http://www.mof-mof.co.jp/

MongoDB の Aggregation Framework でラクラク集計生活 (2.6 対応) – Qiita

More than 3 years have passed since last update.

Moved Temporarily

読んだあとは、自分の集計したいクエリをひたすら実装するのみですな。

やっぱ、新しい技術を追うとなると英語を読むことが多くなりますね。ちょっとしたエラーなどの課題を解消するときにも英語が多くなりますし。発音できずとも、英語の意味を理解する力が必要になります。

私は英語だと毛嫌いせずに取り組める心構えは高校のときに身に付けたのは大きいですね。

Javaで「MongoDB Aggregation Framework」を使う

このへんもあまり記事になく。

Aggregation PipelineをMapに格納
→JSONに変換
→BSONに変換
→クエリ実行

上の処理をひたすらすることに落ち着きました。
JSON変換してからBSONするのは、文字エスケープ処理するためですね。

Aggregation Pipelineを作るには、Javaはひたすら変数作って格納することになるので、ソースがすっごく長ったらしくなってしまいます。これはどうしても避けられないですね。

今後、カンタンに組めるような仕組みがドライバー内で増えないかなぁ。(かゆいところに手が届くフレームワークが今後出てきそうですね。)

日付の処理

mongodbのISODateはtimezoneに対応してません。タイムゾーンが入ったのDate型を入れても、標準時間でドキュメントを登録されます。

なので、以下の記事のように「MongoDB Aggregation Framework」を使ってタイムゾーンを修正するなど、ひと手間が必要です。

mongoのISODateのtimezone問題に対処する – としたにあんの左脳

mongodbのISODateはtimezoneに対応してないらしい. いろいろ検証してみて,最後はAggregation Frameworkでどうにかしてみようと思う. 以下のスクリプトを動かすとサンプルデータがmongodbに入る. データの形式は以下の通り. 日本時間で2013/01/01 00:00:00 から 2013/01/02 23:59:00 の1分おきのデータが入る. 2013/01/01 は火曜日 2013/01/02は水曜日 { “_id” : ObjectId(“52af0bc3090fdd09be0c6ff5”), “gmt_time” : ISODate(“2012-12-31T15:00:00Z”), “gender” : “female”, “jst_strf” : “2013/01/01 00:00:00” } mongodbのISODateは現在のところ,GMTにしか対応していない. 先ほど挿入したデータも, タイムゾーン付きでdatetimeを生成したので, jst_strf は文字列なので日本時間で挿入されている(2013/01/01 00:00:00 ~ 2013/01/02 23:59:00) 一方で, gmt_time はTimezone情報が捨てられてしまうのでGMTで挿入されている.(2012-12-31T15:00:00 ~ 2013-01-02T14:59:00) このコレクションに対して,クエリをかけるときに問題が発生する. 例えば,日本時間の1月1日のデータだけを取り出したいときなどである. 以下のクエリをかけると,当然,GMTに対してクエリがかかるので,日本時間では,2013/01/01 09:01:00 ~ 2013/01/02 08:59:00の結果がかえってくる. > db.gender.find({gmt_time:{$gt:ISODate(“2013-01-01T00:00:00Z”), $lt:ISODate(“2013-01-01T23:59:59Z”)}}) { “_id” : ObjectId(“52af0bc4090fdd09be0c7212”), “gmt_time” : ISODate(“2013-01-01T00:01:00Z”), “gender” : “female”, “jst_strf” : “2013/01/01 09:01:00” } …

JSON変換をする際、jsonicを使用しています。
この変換させる処理で、上のMongoDBのISODate型と比較がどうがんばってもできない状況に……。

JSONIC POJOからJSONへの変換ルール※抜粋
変換元(Java)変換先(JSON)
Date, Calendarnumber (1970年からのミリ秒)

んで、対応策。

  • コレクションにドキュメントを追加するときに、「日付ミリ秒」のドキュメントを追加する。
  • matchパイプラインでは上で追加した「日付ミリ秒」で比較する。

「JSONがミリ秒に変換されるなら、MongoDBに持たせればいいじゃない!」
上司の発想の転換のうまさには頭が上がりませんね……!

まとめ

MongoDBを使った案件に携わって、ほんとうにNoSQLの集計機能の処理の早さにはびっくりしました。やり方をしっかりアプトプットされている先人の方々に感謝感謝です。

これからMongoDBを使う人の勉強の手助けになれば、うれしいです。