PostgreSQL JSON/JSONBの文書操作とMongoDBの文書操作

昨日からスパ付きホテルに泊まってまったりとMongoDBを勉強してますよと。
MongoDBチョットデキルまでの道のりは果てしなく遠そうだが・・・

ついでに、PostgreSQL JSONBの最大のライバル?である、MongoDBについて文書操作などの観点でちょっと比較してみました。
もちろん、そもそも用途が違うかもしれない2つの異種のDBMSを比較することに本当に意義があるのか?というツッコミはもっともではあるけど、ちょっと自分で気になったので、なんとなくまとめてみた。
(そして来月末の勉強会資料にも転用する)

なお、比較対象となるそれぞれのDBMSのバージョンは

です。

PostgreSQLとMongoDBのデータ管理単位の対応。

これが正しい対応なのかはわかんないけど、大雑把にいえばこんな感じになるはず。

ぽすぐれ もんご
データベースクラスタ --dbpath指定のフォルダ
データベース 対応なし
スキーマ 対応なし
テーブル コレクション
レコード ドキュメント

この場合、レコードにはJSON/JSONBカラムが1つのみ存在するものとする。

CRUD機能+αの比較

レコード/ドキュメントに対するCRUD操作+αについて簡単にまとめてみた。
ざっくり言えば、

  • C/Dはほぼ同等
  • Uに関してはMongoDBのほうに分がありますね。
  • Rに関してはPostgreSQLのほうが扱い易い?(単に慣れの問題かも)
操作 ぽすぐれ もんご
作成(C) INSERT INTO テーブル名 VALUES ( JSON文字列 ) db.コレクション名.insert( JSON文字列 )
読み込み(R) SELECT 列リスト FROM テーブル名 [条件式] db.コレクション名.find([条件式])
更新(U) UPDATE テーブル名 SET ( JSON文字列 ) [条件式] *1 db.コレクション名.update(条件式,JSON文字列) *2
削除(D) DELETE テーブル名 [条件式] db.コレクション名.remove(条件式)
UPSERT (PostgreSQLの素のコマンドでは不可) db.コレクション名.update(条件式,JSON文字列, true)
テーブル生成 CREATE TABLE テーブル名 (定義) use コレクション名 (新規指定時)*3
テーブル空化 TRUNCATE TABLE テーブル名 db.コレクション名.remove({}) *4
テーブル削除 DROP TABLE テーブル名 db.コレクション名.drop() *5
  • *1 後述のように、PostgreSQLではJSONデータに対する部分更新インタフェースはないので、更新後のイメージを何らかの方法で作成して、それを渡す必要がある。
  • *2 これは全体更新の例。部分更新の例は後述。
  • *3 実際には、ここで生成するわけでなく、そのコレクションに最初に文書を作成するときにコレクション自体も一緒に作成するらしい。
  • *4 以前のバージョンでは、引数なしremove()でTRUNCATE(というか条件なしDELETE)相当が動作したみたいだけど、最近のバージョンではremove()は引数必須みたい?
    • 引数なしremove()を発行すると、"remove needs a query"エラーになる。
> db.foo.remove()
2014-12-28T11:20:29.298+0900 remove needs a query at src/mongo/shell/collection.js:299
  • *5 実際のところ、remove()で全文書を削除しても、dropでコレクションを削除しても、db.コレクション名.count()では、両方0が返却されるので、MongoDBではコレクション空化=コレクション削除と考えるべきかもしれない。

ここから、読み込みと更新について、もうちょっと詳しく説明してみる。

読み込みのバリエーション

読み込みのバリエーションについて、PostgreSQLとMongoDBをちょと比較してみた。

操作 ぽすぐれ もんご
射影(第1階層) SELECT JSONカラム ->> 'キー名' FROM テーブル名 db.コレクション名.find(null, {キー名:1})
射影(第N階層) SELECT JSONカラム #>> '{キー名,キー名}' db.コレクション名.find(null, {キー名.キー名:1})
選択 SELECT JSONカラム FROM テーブル名 WHERE JSONカラム->>'キー名' 演算子 db.コレクション名.find({キー名:値})
並べ替え(昇順) SELECT JSONカラム FROM テーブル名 ORDER BY JSONカラム->>'キー名' db.コレクション名.find().sort({キー名:1}
並べ替え(降順) SELECT JSONカラム FROM テーブル名 ORDER BY JSONカラム->>'キー名' db.コレクション名.find().sort({キー名:-1}
カウント *1 SELECT COUNT(*) FROM テーブル名 db.コレクション名.count()
演算 SELECT JSONカラム ->> 'キー名' 演算子 値 FROM テーブル名 不明 *2
  • *1 他の集約に関しては、別途調査する。MongoDBにはAggregate Frameworkというものがあるっぽい。
  • *2 というより、値だけを取得する方法がまだ分からない・・・何か方法はありそうなんだけど・・・。
MongoDBの部分更新

残念ながら文書の一部を変更するということが、現状のPostgreSQL JSON/JSONBでは出来ない。
それに、永続化のためには結局行全体のUPDATEが必要になるからなあ。

さて、MongoDBだと部分更新の様々なバリエーションを持っている。

操作 もんご
キー&値の追加 db.コレクション名.update(条件式, {$set:{キー:値}})
キー&値の更新 db.コレクション名.update(条件式, {$set:{キー:値}}) *1
キー&値の削除 db.コレクション名.update(条件式, {$unset:{キー:値}} *2
配列に要素を追加(重複可) db.コレクション名.update(条件式, {$push:{キー:値}})
配列に要素を追加(重複不可) db.コレクション名.update(条件式, {$addToSet:{キー:値},false,true})
配列から要素を削除 db.コレクション名.pop(条件式, {$pop:{キー:値}} *3
  • *1 既存のキーが存在した場合には置換する。つまりキーと値の更新となる。
  • *2 値はテキトーなものでいいみたい。1でも0でも動作は一緒だった。
  • *3 最後に追加した配列要素が除去されるっぽい。特定の配列値を狙って除去できるのかは不明・・・。

まあ、この更新に関する機能だけでも、PostgreSQL JSON/JSONBとMongoDBの使いどころの差異が見えてきそうな気もする。
やっぱり、PostgreSQL JSON/JSONBは格納したら、以降更新されず、検索しかされないようなJSONデータの格納に使うのが無難なのかなあ。

  • あるいは、そういう用途の場合にはMongoDBのFDWを使うという選択肢もあるのか・・・?
    • mongo_fdwについては、どこまでの機能をサポートしているのか、別途調べてみないといけないかねえ。

今後は・・・

  • 挿入、更新、検索性能の比較はやっておきたい。PostgreSQL全体更新 vs MongoDB全体更新/部分更新の比較は面白そうではあるが。
    • 俺PCのVM上での比較なので、あくまでも参考程度って感じだけど。
  • レプリケーションとシャーディングの比較もやらないとだなあ。
    • 1台のVMでやるのはちょいしんどいか。
    • 少なくとも、Postgres-XCを使ったシャーディング構成とMongoDBとの比較とか面倒でやる気もでない。
  • あとはRuby(使ったことない)からのPostgreSQL JSON/JSONBと、MongoDBアクセスの比較とかかなあ。

すごくどうでもいいけど

参考にしたもの