仕事でBSONについて調べる必要があったので簡単に調べた
BSONとは
- BSONはバイナリ形式で表現されるデータフォーマットで、名前の通りバイナリ版のJSONといった使われ方をしている
- JSONはテキストベースなので人間にとっては理解しやすいが、可読性が求められない場面ではBSONの方がより効率的だということだろう
- BSONはMongoDBで使われているそうで、PythonのPyMongoというパッケージにBSONまわりの便利な関数が入っている
- この記事では基本的にMongoDBでの実装を参考にしている
フォーマット
BSONはドキュメント全体のサイズ->各フィールドの名前と値>ドキュメントの終端バイトといった順番で構成されており、リトルエンディアンでエンコードされている
MongoDBのドキュメントにあった以下のサンプルを基に説明をする
\x16\x00\x00\x00 # total document size
\x02 # 0x02 = type String
hello\x00 # field name
\x06\x00\x00\x00world\x00 # field value
\x00 # 0x00 = type EOO ("end of object")
- 最初の4バイトが全体のデータ長を表している
- 各フィールドの先頭1バイトがそのフィールドの型に対応する番号が入る
- BSONの各型がどの番号に対応しているかは以下が参考になる
- 型バイトの後はフィールド名になっていて、0x00で終端されている
- その後フィールドの値が続く
- サンプルだとString型なので0x00で終端されているのが、固定長の型の場合はその型長さの分だけ値が入っている
- string型の場合最初の4バイトが終端文字を含めた文字列の長さになっている (サンプルだと"world"+0x00なので6が入っている)
- 最後に0x00でドキュメントの終了を表している
実際のバイト表現
よく使いそうな型がどのようにバイナリで表現されているのかを調べてみた
以下のようなサンプルコードを作ってBSONファイルを作り、バイナリエディタで中身をのぞいてみた
import json
import bson
bson_test = {
"int32": 2147483647,
"int64": 9223372036854775807,
"double": 1.7976931348623157e+308,
"string": "aaaa",
"bin": bson.Binary(b"bin data"),
"timestamp": bson.Timestamp(123456789, 1),
"bool": True,
"list": [2147483647, "bbbb"],
"object": {"id" : 1, "value" : "cccc"}
}
with open("output.bson", 'wb') as file:
file.write(bson.encode(bson_test))
with open("output.json", 'wb') as file:
json_data = json.dumps(bson_test, default=str, ensure_ascii=False, indent=2)
file.write(json_data.encode('utf-8'))
- 最初の4バイト
AF 00 00 00
はドキュメント全体が175バイトであることを示している - その後、最初のフィールド
10 69 6E 74 33 32 00 FF FF FF 7F
が入っている-
10
は0d16なので、型は32ビット整数であることを意味している -
69 6E 74 33 32 00
はフィールド名"int32" -
FF FF FF 7F
で4バイト分の値が入っている
-
- 少し飛ばしてバイナリ型のフィールド
05 62 69 6E 00 ...
だが、これもstring同様にfiled valueの最初の4バイトがデータ長になっている-
05 62 69 6E 00
バイナリ型0x05 + フィールド名"bin" -
08 00 00 00 00
データ長 8バイト -
62 69 6E 20 64 61 74 61
"bin data" (終端文字なし)
-
- 配列
04 6C 69 73 74 00 ...
は以下のようになっている-
04 6C 69 73 74 00
配列型 0x04+フィールド名"list" -
18 00 00 00
データ長24バイト -
10 30 00 FF FF FF 7F
は配列の第一要素-
10
最初の配列の型 (0d16で32ビット整数型) -
30 00
キーの値、つまり'0'+終端文字- BSONの仕様で、配列のキーは'0'からイクリメントするようになっている
-
FF FF FF 7F
実際のint32の値
-
-
02 31 00 05 00 00 00 62 62 62 62 00 00
は配列の第二要素-
02
はstring型を表すバイトで31 00
は二番目の要素を表すキー'1' - その後は通常のstringと同じ
-
-
- Object型もキーの値が自由に設定できる点以外は基本的には配列型と同じ
JSONとの比較
同じデータをJSONフォーマットで出力したところ292バイトだったので、基本的にはBSONの方が効率的と考えてよさそうだ
ただ可変長の型には必ず4バイト分データ長を表すブロックがはいるので、場合によってはJSONの方が効率的になる場面もあるかもしれない