レッツトライ!しもしも

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

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

      2017/10/05

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

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

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

No Title

No Description

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

JavaでMongoDBに登録する

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

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))));

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

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

More than 1 year has passed since last update. Java MongoDB Driverを使ってJavaからMongoDBにアクセスする方法について、全くはじめての人向けメモ。 MongoDBには色々機能があるようですが、今回はJava MongoDB Driverを使った簡単なCRUDの記述方法を解説します。 RDBとは用語が異なります。 何も知らずにJava MongoDB Driverのドキュメント読んでも???となってしまうので、最低限、RDBとの用語の違いを把握しておきましょう。 RDBとMongoDBのデータベース構造の比較は以下の通り。 また、MongoDBの特徴などは以下のようなサイトで把握します。 build.gradleに以下の設定を追加します dependencies { … compile group: ‘org.mongodb’, name:’mongo-java-driver’, version:’2.12.3′ …

スキーマ設計

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

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

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

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

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

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

クエリを考える

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

[Mongo] findメソッドのいろいろな使い方(MySQLと比較) – YoheiM .NET

こんにちは、@yoheiMuneです。以前にフロントエンドエンジニアにオススメなデータベース、MongoDBに入門とフロントエンドエンジニアにもできるMongoDBを使ったログ分析のブログを書いたのち、MongoDBを引き続き使っていますが、findの使い方を調べることが多く、今回はそれらをブログに残しておきたいと思います。https://flic.kr/p/55XJ83

「MongoDB Aggregation Framework」で集計する

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

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

Aggregation – MongoDB Manual 3.4

Aggregation operations process data records and return computed results. Aggregation operations group values from multiple documents together, and can perform a variety of operations on the grouped data to return a single result. MongoDB provides three ways to perform aggregation: the aggregation pipeline , the map-reduce function , and single purpose aggregation methods .

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

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

この記事は最終更新日から1年以上が経過しています。 最近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 など気になるのがたくさんあるのでもう少し継続してみることにしますね!

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

この記事は最終更新日から1年以上が経過しています。 MongoDB 2.6 で使い放題になった Aggregation Framework で集計してみたよ 簡単!(笑) MongoDBのデータを集計するなら、 Aggregation Framework がファーストチョイス 自分が関わった MongoDB を使ったサービス(某ウザいと評判のサービス)が最近リリースされました。 MongoDBに不定形データを保存するのですが、開発は昨年からスタートされており、 途中で MongoDB が 2.6 にメジャーバージョンアップされていろいろ変わった中で、 今回は集計にフォーカスして Aggregation Framework を使ったお話を。 既に MongoDB をバリバリ利用している方にとっては今更感は否めませんが、未使用の方に雰囲気を感じていただければ幸い。 集計といえば MapReduce よねという感じで MapReduce を使っていたのですが、複雑な集計が可能な反面、 遅い Map 用関数、Reduce 用関数の管理が面倒(パラメータをちょっとずつ変えて、似たような集計を何回も集計したいときに頭にくる) といった不満がありました。 しかし 2.6 以前にあった Aggregation Framework (2.2 で登場)は 記述が( MapReduce に比べて)簡潔で集計用パラメータの流用が容易 という利点はありましたが 16MB

MongoDB University講義メモ M101P Week 5

Aggregation Framework Aggregation Example まずは例。productsコレクションから製造社ごとの製品数を確認する方法。 db.products.aggregate([{$group:{“_id”:”$category”, “…

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

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

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

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, Calendar number (1970年からのミリ秒)

んで、対応策。

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

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

まとめ

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

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

シェアする

 - Tips , , , ,