📌

MongoDB プロトコル実装調査 - v3.6以降の現状について

2024/08/23に公開

MongoDBは、DB-Enginesの2019年8月のランキング[1]で第5位と、近年でも安定した人気を得ているドキュメントデータベースです。また、マイクロソフトのCosmosDBや、FoundationDBの「Document Layer」に代表されるように、既存のデータベースでもMongoDBインターフェースを互換APIで対応する動向も見受けられます[2][3]

ただし、2019年8月現在、MongoDB公式の最新バージョンは4.0系であるにも関わらず、CosmosDBはv3.2ベース、Document Layerはv3.0ベースの実装に留まっています。

今回は、このようなMongoDB互換APIの実装背景を理解する上で必要となる、MongoDBの基本通信プロトコルであるWire Protocol[4]の概要と、MongoDB公式のv3.6以降の通信プロトコル実装状況についての調査結果をまとめてみます。

MongoDB Wire Protocolの概要

MongoDB Wire Protocolは、MongoDBの基礎となる要求応答の通信プロトコル仕様です。 MongoDBクライアントは、TCP/IPソケットでMongoDBサーバーと接続し、この基本プロトコルを用いて通信しています。

共通ヘッダ

MongoDBの通信プロトコルのWire Protocloでは、通信メッセージの共通ヘッダが規定されています。共通ヘッダは、メッセージ総合計バイト数(Message Length)、メッセージ要求識別子(RequestID)、メッセージ応答識別子(ResponseTo)、メッセージ種別(OpCode)の4種類の整数(int32)から構成されます。

メッセージ総合計バイト数(Message Length)のデータ形がini32であるため、仕様的には2GB以上のデータの送受信にはメッセージの分割必要となります。MongoDB公式の標準的な実装としては46MB(48,000,000byte)[5]程度を上限としているようです。

なお、Wire Protocloの整数型は、TCP/IPヘッダのバイトオーダーとは異なり、リトルエンディアン順序での送受信となります。エンディアン選択については、BSON仕様[6]と同じ経緯で、実装効率メインで仕様が決定された感があります。

メッセージ種別 (OpCode)

MongoDBのメッセージ種別(OpCode)は、廃止(Deprecated)されたものを含めると、現在まで以下の11種類の送受信メッセージが規定されています。

OpCoce 説明
OP_REPLY 1 クライアント要求応答
OP_UPDATE 2001 ドキュメント更新
OP_INSERT 2002 ドキュメント挿入
OP_GET_BY_OID 2003 予約済み (Deprecated)
OP_QUERY 2004 コレクション問い合わせ
OP_GET_MORE 2005 追加応答 (カーソルによるデータ分割応答)
OP_DELETE 2006 ドキュメント削除
OP_KILL_CURSORS 2007 カーソル終了通知
OP_COMMAND 2010 コマンド要求 (内部プロトコル)
OP_COMMANDREPLY 2011 OP_COMMANDへの応答 (内部プロトコル)
OP_MSG 2013 汎用メッセージの送信 (MongoDB 3.6から導入)

Wire Protocolは、クライアントが送信する要求メッセージを、サーバー側がOP_REPLY(または後述するOP_MSG)にて応答するのが、MongoDBの基本通信シーケンスとなります。

CRUDメッセージ要求部 (MongoDB v3.6以前)

Wire Protocolではデータベースの基本操作であるCRUD(Create, Read, Update, Delete)操作が個別メッセージとして定義された経緯があり、それぞれOP_INSERT, OP_READ, OP_UPDATE, OP_QUERYが対応しています。

前述した共通ヘッダ以降に、メッセージ種別(OpCode)毎に規定された要求メッセージ部(Operation Body)のメッセージ本体が続きます。例として、Create(生成)操作のOP_INSERT要求メッセージ部は、以下のように規定されています。

OP_INSERTの要求メッセージ部は、要求制御フラグ(flags)、データベース名をふくむC言語スタイル(cstring)の可変長文字列コレクション名の後に、挿入するBSON形式ドキュメント配列が続きます。

ただし、規定されているCRUDメッセージ仕様(OP_INSERT, OP_READ, OP_UPDATE, OP_QUERY)については、後述するように現状(v3.6以降)は利用頻度が低い(Deprecated)仕様で、現在のMongoDB(4.x)の通信シーケンスでの利用は見受けられませんでした。

MongoDB Wire Protocolの現状

Wire Protocolは明確な仕様として公式に規定はされていますが、OP_GET_BY_OIDのように仕様上明確に廃止(Deprecated)されているものに加えて、現状(v3.6以降)では実質的に廃止(Deprecated)されている仕様も見受けられます。

具体的には、前述の規定されるCRUDメッセージ(OP_INSERT, OP_READ, OP_UPDATE, OP_QUERY)要求は、現状のMongoDB公式のクライアント/サーバーでは、MongoDB v3.6で導入されたOP_MSGによる実装に移行しています。

CRUDメッセージ要求部 (MongoDB v3.6以降)

現状のMongoDB公式クライアント(MongoDB Go Driver v1.0.4 [7])やサーバー(v4.0.12)の通信シーケンスを確認すると、前述のCRUD操作は以下に示すOP_MSGによる実装に全面的に移行しているようです。

OP_MSGの要求メッセージ部は、要求制御フラグ(flagBits)以降に、各CRUD操作要求がふくまれる(2種類の)BSON形式ドキュメント配列が続きます。

例として、MongoDB公式のチュートリアルにある[8]ドキュメント追加操作は、OP_MSGに以下のBSON形式の要求メッセージ部(Sections)を追記して送信されます。

{
	"insert": "trainers",
	"ordered": true,
	"$db": "test"
}
documents: {
	"_id": {
		"$oid": "5d5104f78f1858330cb49343"
	},
	"name": "Ash",
	"age": {
		"$numberInt": "10"
	},
	"city": "Pallet Town"
}

このメッセージ要求部は、MongoDBデータベースコマンドのinsert仕様[9]に準じた内容となっています。MongoDBクライアントからのOP_MSGによるCRUD要求に対して、サーバーは同じくOP_MSGにて応答します。上記のクライアントからの挿入要求に対する、サーバーのOP_MSG応答は以下の内容となります。

{
 	"ok": {
 		"$numberDouble": "1.0"
 	},
 	"n": {
 		"$numberInt": "1"
 	}
}

MongoDBの通信シーケンスを確認すると、現状はこのOP_MSGおよびOP_QUERYでの実装に集約されているようです。

両者の切り分けは明確な仕様はないものの、MongoDBのデータベースコマンド仕様[10]のユーザー操作(User Commands)系コマンドはOP_MSG要求によるOP_MSGO応答、データベース操作(Database Operations)コマンドはOP_QUERY要求によるOP_REPLY応答による切り分けが成されているようです。

最期に

今回、MongoDB Wire Protocol仕様と、この仕様をベースとした現状のMongoDBの公式実装を調査し、現状は一部のメッセージ形式(OP_MSG, OP_QUERY, OP_REPLY)による実装に移行していることが確認できました。

MongoDB公式のOP_MSGによる実装の移行は、柔軟性が高く、今後の仕様拡張を見据えたもの推察されます。ただし、MongoDBのデータベースコマンドは公式な仕様[10]があるとは言え、実際にはBSONデータ型が未規定であるなどアンドキュメントな内容があり、実装には実際の通信シーケンスの解析が必須となります。

裏を返せば、CosmosDBがMongoDB v3.2、FoundationDBのDocument Layerがv3.0ベースの仕様で実装されている背景は、MongoDB v3.6で導入されたOP_MSGによる仕様の柔軟さ(=仕様が明記されいない)に起因するところがありそうです。

今回は、MongoDBプロトコル仕様と、その実情についての概要をまとめてみました。MongoDBプロトコルについては、もう少し調査を継続するつもりですので、また特記事項があれば整理してみたいと思います。

参考資料

Discussion