PostgreSQL JSONB/MongoDB 更新性能

先日の第七回 中国地方DB勉強会 in 松江の発表で、自分のノートPC上で測定したpg_nosql_benchmarkの結果を発表したのですが、そのときにはPostgreSQL JSONB型がMongoDBと比較して、
格納、検索、いずれもマッハ!!
みたいな感じの結果を出したわけですが、pg_nosql_benchmark自体には更新を測定するクエリがなかったんですよね。
ということで、そのときには更新性能は提示しなかったのですが、正直、更新に関してはPostgreSQLの方が分が悪そうだよなーとは思ってました。
じゃあ、どのくらい悪いのか一応確認してみようかと。

環境

今回はノートPC上のVMで測定。

マシン Let's note CF-SX4 SSDモデル
実メモリ 8GB
VM動作環境 VMWare player 6.6
ゲストOS CentOS 7.0
ゲストOSメモリ 4GB

PostgreSQLもMongoDBもほぼノーチューニング。というか、この環境だと、たぶんチューニングをしてもあんまり変わらない気がする。

モデル

今回の測定モデルでは、YCSB(Yahoo! Cloud Servicr Benchmark)を参考にして構築した。
YCSBでは以下の様な感じの1つのキーと10個のほどほどに長いテキストフィールドを持っている。

{"ycsb_key":"user00000000000000000001","field1":"f03560cbc9e18b3d62106646f5ac3533f03560cbc9e18b3d62106646f5ac3533f03560cbc9e18b3d62106646f5ac3533f03560cbc9e18b3d62106646f5ac3533f03560cbc9e18b3d62106646f5ac3533","field2":"f63c7ac594907fd5ae161a646148464af63c7ac594907fd5ae161a646148464af63c7ac594907fd5ae161a646148464af63c7ac594907fd5ae161a646148464af63c7ac594907fd5ae161a646148464a","field3":"6a3957fe03711c6ee46c47a3017a120f6a3957fe03711c6ee46c47a3017a120f6a3957fe03711c6ee46c47a3017a120f6a3957fe03711c6ee46c47a3017a120f6a3957fe03711c6ee46c47a3017a120f","field4":"1a2fe84a6f3b0d3b3db704261b453f911a2fe84a6f3b0d3b3db704261b453f911a2fe84a6f3b0d3b3db704261b453f911a2fe84a6f3b0d3b3db704261b453f911a2fe84a6f3b0d3b3db704261b453f91","field5":"6545bbec3c99e54034b20b142519915a6545bbec3c99e54034b20b142519915a6545bbec3c99e54034b20b142519915a6545bbec3c99e54034b20b142519915a6545bbec3c99e54034b20b142519915a","field6":"58209cb84597bea4c575eaee3480fb3e58209cb84597bea4c575eaee3480fb3e58209cb84597bea4c575eaee3480fb3e58209cb84597bea4c575eaee3480fb3e58209cb84597bea4c575eaee3480fb3e","field7":"60284bccd9ba15f12f6993c6ad8433cc60284bccd9ba15f12f6993c6ad8433cc60284bccd9ba15f12f6993c6ad8433cc60284bccd9ba15f12f6993c6ad8433cc60284bccd9ba15f12f6993c6ad8433cc","field8":"941ac5f05860137fe6c96dcdbe1d1e73941ac5f05860137fe6c96dcdbe1d1e73941ac5f05860137fe6c96dcdbe1d1e73941ac5f05860137fe6c96dcdbe1d1e73941ac5f05860137fe6c96dcdbe1d1e73","field9":"70b9c9871fb2043a4ff979adbb2dfd7770b9c9871fb2043a4ff979adbb2dfd7770b9c9871fb2043a4ff979adbb2dfd7770b9c9871fb2043a4ff979adbb2dfd7770b9c9871fb2043a4ff979adbb2dfd77","field10":"bff6f1e3a1ed6af047c5c17920e70b16bff6f1e3a1ed6af047c5c17920e70b16bff6f1e3a1ed6af047c5c17920e70b16bff6f1e3a1ed6af047c5c17920e70b16bff6f1e3a1ed6af047c5c17920e70b16"}
  • キー名はycsb_key。キー値は24文字のユニークな文字列。
  • field1〜field10という文字列フィールドを持つ。それぞれのフィールドは百数十文字のテキスト値。
  • YCSBベンチマークの更新処理では、ycsb_keyを条件としてヒットした1件の文書のfield1の値を更新する。

さて、今回はMongoDBの強み?の一つである部分更新の機能を使えるような更新をしてみる。更新時にfield11という新規のキーと32文字の値をセットするというものだ。

PostgreSQL

  • カラムとしてはjsonbカラムを1つだけ定義する。
  • 10万件をCOPYでロードする。
  • COPY後にbtree式インデックスを設定する。
CREATE TABLE usertable(data jsonb);
COPY usertable(data) FROM '/tmp/json.txt';
CREATE INDEX ycsb_key_idx ON usertable USING btree ((data->>'ycsb_key'));
  • この状態で以下のようなUPDATE文を発行する。
UPDATE usertable
  SET data =
  json_build_object(
    'ycsb_key', data->>'ycsb_key',
    'field1',  data->>'field1',
    'field2',  data->>'field2',
    'field3',  data->>'field3',
    'field4',  data->>'field4',
    'field5',  data->>'field5',
    'field6',  data->>'field6',
    'field7',  data->>'field7',
    'field8',  data->>'field8',
    'field9',  data->>'field9',
    'field10', data->>'field10',
    'field11',  'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
  )::jsonb
  ;
  • もうね、うんざりしますよね。
    • PostgreSQLのJSONBに部分更新的なインタフェースがないので、field11を追加した文書をjson_build_object()を使って再生成する必要がある。
    • で、その過程で各キーの値で"->>"演算子を使って値を取り出してセットしないといけない。
    • しかも、この関数はJSON型を返却するので、JSONBに型変換が必要。(´・ω・`)
      • なお、PostgreSQL 9.5-develではjsonb_build_object()が追加されるので、型変換が不要になる。どうして9.4に入らなかった。

MongoDB

db.usertable.ensureIndex({ycsb_key : 1})
  • 更新は以下の様に行う。
    • 記述もPostgreSQLと比較すると非常にシンプルですねー。
db.usertable.update({},{$set : {"field11":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}},false,true)

測定

  • それぞれ3回ずつ測定して、その平均値をとりあえず出してみた。
  • 参考のため、PostgreSQLについては以下のバリエーションも測定
    • JSONB型、btreeインデックスあり、-->演算子なし(ベタに定数をセット)
    • JSONB型、ginインデックスあり、-->演算子あり ⇒btree/ginの比較
    • JSON型、btreeインデックスあり、-->演算子あり ⇒jsonb/jsonの比較
    • MongoDB

測定結果

DBMS/Type/index 100K rows update(sec)
PostgreSQL/JSONB/btree 8.321
PostgreSQL/JSONB/gin" 20.415
PostgreSQL/JSON/btree" 16.457
MongoDB 1.765

  • むう・・・予想通りとはいえ、更新処理に関してはMongoDBのほうが圧倒的に高速だな・・・
  • あと、これも予想通りではあるけど、ginインデックスの更新が結構遅いなあ。
  • JSON型がJSONB型より遅いのは、"->>"演算子の処理時間が響いているのだと思う、

JSONB更新のどこが遅いのか?

  • ということで、以下の様なパターンも測定。
    • btreeインデックスなし、定数値をセット
    • btreeインデックスなし、"->>"演算子を使用
    • btreeインデックスあり、定数値をセット
    • btreeインデックスあり、"->>"演算子を使用
  • この結果を元に処理時間をざっくり積み上げてみた。


測定結果からの考察

  • JSONB型の更新性能はあまりよろしくない。
  • ついでにいうと、このスキーマの場合、HOT更新が効かない。不要領域も一気に増大する。非常によろしくない。
    • もちろんycsb_keyの値を別カラムに展開し、これを通常のbtreeインデックスを設定すればHOT更新が効くようになるのだろうけど、それはJSONB使いとしては、なんか負けな気がw
  • 元々の更新自体も結構遅いが、"->>"演算子による処理も結構なオーバヘッドになっている。
    • 故に、JSONB型でなくJSON型にするとそれが顕著に現れる。
  • やっぱり、PostgreSQLのJSONB型は更新がかかるような用途にはあまり向かなくて、ログ蓄積モデルのように、一旦挿入したあとは検索のみ行うというモデルに向いているのかも。
    • クエリ設計者の立場からみても、あのUPDATE文はちょっとなー。
    • まあ、更新後JSONイメージの構築はPostgreSQL SQL関数ではなく、アプリケーション側でやれということかもしれないが。