こんにちは、株式会社ラクスで先行技術検証を行っている技術推進課の@t_okkanです。 技術推進課では、新サービス立ち上げ時の開発速度アップを目的に、現在ラクスでは採用されていない新しい技術の検証を行う、技術推進プロジェクトがあります。 今回はその技術推進プロジェクトで、ドキュメントDBであるMongoDBについて検証を行いましたので、その結果の報告を行います。
なお、別テーマの取り組みや、過去の取り組みに関しては、こちらからご覧ください。
ドキュメントDB
ドキュメントDBとは、半構造データを管理するNoSQLなデータベースです。半構造データとは、ある程度データ構造が決まっているが構造が柔軟に変更できる、もしくは変更されるデータのことで、JSONやXML形式で表されます。 ドキュメントDBの特徴としては以下のようなことが挙げられます。
リレーショナルデータベースとドキュメントDBなどの非リレーショナルデータベースを併用し、データの特性ごとに適材適所のデータベースを利用する考え方をポリグロット・パーシステンス(複数モデルによる永続化)と呼びます。 今回はドキュメントDBの製品であるMongoDBについて調査し、実際にポリグロット・パーシステンスを実装してみました。
MongoDB
MongoDB Incが開発とサポートをしているドキュメントDBです。データをBSONというJSONのバイナリ版の形式で保存することができます。CouchDBやRavenDBといった、同じドキュメントDBの中では最も人気のあるデータベースとなっています。 それではMongoDBの基礎的な機能などについて説明します。
トランザクション
MongoDBではバージョン4.0からマルチドキュメントACIDトランザクションをサポートしています。(レプリカセット構成でWidredTiger利用時)デーベース、コレクション、ドキュメント単位からトランザクションを開始できます。以下のようなトランザクションレベルが提供されています。
書き込みレベル
w:1
:プライマリにコミットされるとコミット完了とするw:"majority"
:レプリカセットの過半数にコミットされると完了とする
読み込みレベル
WiredTigerストレージエンジン
バージョン3.2からMongoDBのストレージエンジンのデフォルトがWiredTigerに変更されています。 WiredTigerはMVCCアーキテクチャを採用しており、ドキュメントの書き込みの同時実行が可能になります。 これまでのMMAPv1と比べ、メモリ制限や、データとインデックスの圧縮が可能になっています。 WiredTigerの詳しい動作の説明はこちらのスライドで詳しく説明されています。
www.slideshare.net
水平方向と垂直方向へのスケーリング
MongoDBではシャーディングとレプリケーションを組み合わせた、シャードレプリカを構築することで水平方向と垂直方向のスケーリングを行うことができます。これにより、性能と可用性の両面を向上させることができます。
Aggregation Pipline
1つのコレクション内の複数のドキュメントをグループ化し、複数の処理を実行するパイプラインを実装することができます。データのフィルターや集約、ソート、変換などが可能となります。SQLのgroup by
やsum
関数といった処理を再現することができます。
MongDBのスキーマ設計
MongoDBのドキュメントのスキーマを設計するには、ドキュメントに対するアクセスパターンとクエリを理解する必要があるとされています。WiredTigerのメモリに検索結果をキャッシュするため、必要のないデータをクエリするとメモリの無駄遣いになってしまいます。 そこでMongoDBでは、スキーマ設計パターンが提供されており、データの特性やアクセスパターンから最適な設計パターンを選択することが推奨されています。
Polymorphic patter
・Attribute pattern
・Subset patter
の設計パターンが参考になるのではないかと思います。
PostgreSQLのjson型、jsonb型との比較
PostgreSQLでは9.2からjson型が、9.4からJSON形式のデータをバイナリ解析して保存するjsonb型が提供されています。 そのため、PostgreSQLでも動的なカラムの増減に対応することができます。 以下、MongoDBとPostgreSQLのjson型とjsonb型について比較しました。
比較項目 | PostgreSQL json型 | PostgreSQL jsob型 | MongoDB |
---|---|---|---|
インデックスの設定 | 不可 | 可能 | 可能 |
トランザクション | 対応 | 対応 | 対応 |
データ型 | JSON型に準拠 | 一部のデータ型に制限あり | BSON型に準拠 |
全文検索 | 対応 | 対応 | 日本語未対応 |
MongoDBはPostgreSQLのJSON型と比べ、豊富なデータ型に対応しています。しかし、MongoDBは日本語での全文検索に対応していません。(他の言語には対応)
Javaアプリケーションでの実装例
今回は以下のようなマスターデータをリレーショナルDB(PostgreSQL)に、カスタム項目などのユーザーごとに動的に変更される定義情報をMongoDBに登録するポリグロット・パーシステンスなシステムを実装してみました。
MongoDBのJava Driverは以下のようにbuild.gradle
に設定しています。
dependencies { implementation group: 'org.mongodb', name: 'mongodb-driver-sync', version: '4.2.3' }
MongoDBとの接続
JavaアプリケーションからMongoDBへ接続するには、RDB同様にURLを利用して接続します。 レプリカ構成のMongoDBを利用している場合は、メンバーのホスト名とレプリカセット名を指定して接続します。
MongoClient mongoClient = MongoClients.create("mongodb://ユーザー名:パスワード@ホスト名"); // レプリカ構成の場合 MongoClient mongoClient = MongoClients.create("mongodb://ユーザー名:パスワード@プライマリホスト,セカンダリホスト,ターシャリホスト?replicaSet=レプリカ名");
定義情報のCRUD処理
JavaアプリケーションからのCRUD処理の一例を紹介します。前提条件としてユーザーの定義情報はMap<String, Object>
型のデータとしてやりとりされる前提とします。
// 定義情報 Map<String, Object> property1 = new HashMap<>(){ { put("設定1", "オプション1"); put("設定2", 3); put("設定3", true); } }; Map<String, Object> property2 = new HashMap<>(){ { put("設定1", 1); put("設定4", false); put("設定5", "オプション5"); } }; // コレクションの取得 MongoCollection<Document> collection = mongoClient.getDatabase("データベース名").getCollection("コレクション名"); // ユーザーの定義項目の登録 Document registorData = new Document(); property1.entrySet().stream().forEach(e -> registorData.append(e.getKey(), e.getValue())); registorData.append("userId", "user1"); collection.insertOne(registorData); // ユーザーの定義項目の検索 Document query = new Document("userId", "user1"); Document result = collection.find(query).first(); // ユーザーの定義項目の更新(上書き + 追記) Document query = new Document("userId", "user1"); Document updateData = new Document(); property2.entrySet().stream().forEach(e -> updateData.append(e.getKey(), e.getValue())); collection.updateOne(query, new Document("$set", updateData)); // ユーザーの定義項目の削除 Document query = new Document("userId", "user1"); DeleteResult deleteResult = collection.deleteMany(query);
その他、CRUD処理については以下のチュートリアルで詳しい実装例が紹介されています。
RDBとMongoDBのトランザクション
MongoDBでのトランザクションの実装は以下のようになります。
ClinetSession
のstartTransaction
メソッドでトランザクション開始ClinetSession
のclose
メソッドでトランザクションを終了- MongoDBの処理に失敗すると
MongoCommandException
がthrowされるのでcatch節でClinetSession
のabortTransaction
メソッドを実行しロールバッ
// MongoCommandExceptionをロールバック対象の例外に設定 @Transactional(rollbackOn = MongoCommandException.class) public void transactionA() throws MongoCommandException { // MongoDB Sessionの取得 ClientSession session = mongoClient.startSession(); try { // MongoDB トランザクションの開始 session.startTransaction(); // PostgreSQLの処理 // MongoDBの処理 } catch (MongoCommandException e) { // ロールバック処理 session.abortTransaction(); throw e; } finally { // MongoDB トランザクションクローズ session.close(); } }
今回のようにマスターデータはRDBに設定情報はMongoDBに保存されている場合、マスターデータを変更した場合にMongoDBのデータも変更する可能性が出てきます。 上記の実装のように、PostgreSQLのトランザクション処理の中にMongoDBのトランザクション処理を実装することで、どちらかのデータ保存に失敗した場合、両方のDBをロールバックすることができるようになります。
まとめ
MongoDBの機能の解説とJavaアプリケーションでのサンプルについて解説しました。 日本語での全文検索に対応していないものの、トランザクションへの対応など豊富な機能が提供されており、データ形式によってはMongoDBが最適解となることもあるのではないでしょうか。 もしMongoDBの導入を検討されている方がいましたら参考にしていただければと思います。
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
forms.gleイベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com