2014年9月3日水曜日

PostgreSQL 9.4で強化されたJSON型 (第2回)

PostgreSQL 9.4で強化されたJSON型に関して第二回のご紹介となります。
第一回の内容はこちらをご参照ください。

Bツリーとハッシュインデックス

JSONB型のデータはPostgreSQLの複合型と同じように比較されます。

比較のルール
 −  Object > Array > Boolean > Number > String > Null
 −  Nの要素を持つObject > N-1の要素を持つobject
 −  Nの要素を持つArray > N-1の要素を持つArray

各要素が比較されるので、比較にはそれなりの時間が必要であることに注意が必要です。

Bツリーインデックスの場合、比較に等価と不等を判別する必要があります。この為、JSONBデータ型は"="オペレータのみでなく、"<"と">"も利用できます。オブジェクトの場合、要素の数が同じ場合

key1, value1, key2, value2 ...

の順番に比較され、key、valueのどれかが"不等"と判断された時点で比較が終了します。

PostgreSQLは短いキーを先に保存するため、比較の結果が予測しづらい結果となる事があります。

username@127 test=# SELECT '{ "aa": 1, "c": 1}'::jsonb > '{"b": 1, "d": 1}'::jsonb;
 ?column? 
----------
 t
(1 行)

時間: 0.247 ms
username@127 test=# SELECT '{ "z": 1, "c": 1}'::jsonb > '{"b": 1, "d": 1}'::jsonb;
 ?column? 
----------
 t
(1 行)

時間: 0.236 ms
username@127 test=# SELECT '{ "a": 1, "z": 1}'::jsonb > '{"b": 1, "d": 1}'::jsonb;
 ?column? 
----------
 f
(1 行)

時間: 0.262 ms
username@127 test=# SELECT '{ "x": 1, "z": 1}'::jsonb > '{"b": 1, "d": 1}'::jsonb;
 ?column? 
----------
 t
(1 行)

時間: 0.236 ms

補足:

'{ "aa": 1, "c": 1}'::jsonb > '{"b": 1, "d": 1}'::jsonb

は"c"と"b"のキーが最初に比較され、"c">"b"なので"t"(真)が返ります。

配列比較も結果しづらい結果かも知れません。オブジェクトと同様に要素数が同じ場合に

value1, value2 ...

の順番に比較され、valueのどれかが不等と判断された時点で比較が終了します。

username@127 test=# SELECT '[1, 2, 3]'::jsonb > '[2, 3, 4]'::jsonb;
 ?column? 
----------
 f
(1 行)

時間: 0.255 ms
username@127 test=# SELECT '[5, 2, 3]'::jsonb > '[2, 3, 4]'::jsonb;
 ?column? 
----------
 t
(1 行)

時間: 0.232 ms
username@127 test=# SELECT '[5, 2, 3]'::jsonb > '[5, 3, 4]'::jsonb;
 ?column? 
----------
 f
(1 行)

時間: 0.220 ms
username@127 test=# SELECT '[2, 4, 3]'::jsonb > '[2, 3, 4]'::jsonb;
 ?column? 
----------
 t
(1 行)

時間: 0.229 ms

関数インデックス

従来のJSON型でもJSON関数を利用して検索を高速化できました。JSONB型ではGINインデックスがサポートされていますが、GINインデックスに比べるとデフォルトのBツリーインデックスやハッシュインデックスの方が高速です。ニーズに応じて関数インデックスも利用すると良いでしょう。

JSONオペレータ

JSONデータ型用のJSON関数はJSONB型をサポートしています。JSON型と同様にJSONB型でも利用可能です。

オペレータ   オペランド型  説明                                      例
->        int        JSON配列要素の取得                          '[1,2,3]'::json->2
->        text        JSONオブジェクト要素の取得                  '{"a":1,"b":2}'::json->'b'
->>        int        JSON配列要素をテキストとして取得              '[1,2,3]'::json->>2
->>        text        JSONオブジェクト要素をテキストとして取得      '{"a":1,"b":2}'::json->>'b'
#>        text[]    パスで指定したJSONオブジェクトの取得          '{"a":[1,2,3],"b":[4,5,6]}'::json#>'{a,2}'
#>>        text[]    パスで指定したJSONオブジェクトをテキストとして取得 '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'

JSONB型に追加されたオペレータ

=          jsonb       等価比較                     '[1,2,3]'::jsonb = '[1,2,3]'::jsonb
@>        jsonb   包含比較                                  '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb
<@        jsonb    包含比較                                  '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb
?        text        このテキストのキー/要素が存在するか?          '{"a":1, "b":2}'::jsonb ? 'b'
?|        text[]    このテキストのキー/要素が存在するか?(どれでも良い)'{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c']
?&        text[]    このテキストのキー/要素が存在するか?(全て)      '["a", "b"]'::jsonb ?& array['a', 'b']

JSON関数

JSONデータ型用のJSON関数はJSONB型をサポートしています。JSON型と同様にJSONB型でも利用可能です。多数存在するので個別の解説は省略しますが、JSONデータ型用のjson_*()関数とJSONBデータ型用のjsonb_*()関数が用意されています。

簡単なベンチマーク

JSONB型はPostgreSQLネイティブのデータ型に変換されて保存されるので効率良くアクセスできます。どの程度高速化されたのか簡単なベンチマークで確認します。

環境
Fedora 20 x86_64
PostgreSQL 9.4 (gitのマスターブランチ: 2014/3/27)
PHP 5.5.10
JSONB/JSONのみの性能を計測したいのでpostgresql.confでfsync=offに設定しています。

ベンチマークスクリプト(benchmark.php)

出力結果の例

[username@dev 201403]$ php benchmark.php 
Inserting test data. Takes a while...
Inserted row counrs:
Array
(
    [json_count] => 10000
)
Array
(
    [jsonb_count] => 10000
)
Simple JSON select: 1.1431810855865
Simple JSONB select: 1.122545003891
Simple SELECT: 1.8051454800766% faster
JSON "->": 1.2706871032715
JSONB "->": 1.1284899711609
"->" Operator: 11.190570183997% faster

まとめ

JSONBで惜しい部分は要素の順序が維持されない部分です。そかし、その代わりにより高速にデータを検索できるようになりました。多くの言語では、JSONデータは順序を持たないハッシュとして実装されています。まれに順序を持たない事が問題になる場合もありますが、これは許容可能なトレードオフでしょう。

簡易なベンチマーク結果からはオペレータを使った要素の取得は1割ほど高速であることが分かりました。挿入やレコードの取得には大きな違いはありません。PostgreSQL 9.4から包含をチェックするオペレータが追加された事は、開発者にとって大きな意味があると思います。デフォルトでGINインデックスが利用できるようになった事も大きなメリットです。

■ 参考URL
http://www.postgresql.org/docs/devel/static/functions-json.html
http://www.postgresql.org/docs/devel/static/datatype-json.html
http://tools.ietf.org/html/rfc7159

0 件のコメント:

コメントを投稿