osc2015 hokkaido postgresql-semi-stuructured-datatype

161
オープンソースカンファレンス 2015 Hokkaido(2015-06-13) PostgreSQL で 非構造化データを使う XML, hstore, JSON, JSONB ぬこ@横浜 (@nuko_yokohama)

Upload: toshi-harada

Post on 30-Jul-2015

500 views

Category:

Technology


2 download

TRANSCRIPT

オープンソースカンファレンス2015 Hokkaido(2015-06-13)

PostgreSQL で非構造化データを使う

XML, hstore, JSON, JSONB

ぬこ@横浜 (@nuko_yokohama)

2

自己紹介SELECT '{"名前 ":"ぬこ@横浜 ",

"住所 ":"横浜市 ",

"Twitter":"@nuko_yokohama","所属 ":{"会社 ":"某通信系ソフトウェア会社 ",

"部署 ":"クラウドな感じ(仕事はクラウドじゃない) ",

"勤務地 ":"ミナトミライ・スゴイトオイビル "},

"仕事 ":"PostgreSQL あれこれ ",

"趣味 ":["猫 ","スパ ","原付旅 ","PostgreSQL"," ラーメン "],

"メモ ":["PostgreSQL は 7.4の頃からの腐れ縁 ",

"ときどき PostgreSQL イベントで喋ってます ","PostgreSQL チョットデキル "],

"作ったもの ":["漢数字型 (ksj)"," ゆるい文字型 (ntext)", "new4j_fdw",

"hit_and_blow BGW"]}'::jsonb;

北海道は初めて!ラーメンも楽しみ!

3

自己紹介 ( こんなの作ってた )SELECT data, data + '拾 ' FROM ksj_sample; data | ?column? ------------+---------------- 五百壱拾六 | 五百弐拾六 零 | 壱拾 参万壱百壱 | 参万壱百壱拾壱(3 rows)

SELECT data, data * '拾 ' FROM ksj_sample; data | ?column? ------------+-------------- 五百壱拾六 | 五千壱百六拾 零 | 零 参万壱百壱 | 参拾壱千壱拾(3 rows)

SELECT sum(data) FROM ksj_sample; sum ---------------- 参万六百壱拾七(1 row)SELECT * FROM ksj_sample ORDER BY data; data ------------ 零 五百壱拾六 参万壱百壱(3 rows)

ksj (漢数字型)

漢数字で四則演算や比較演算できる、誰得データ型

4

自己紹介 ( こんなの作ってた )

SELECT * FROM foo WHERE data = 'エバンゲリオン '; id | data ----+------------ 3 | エヴァンゲリヲン(1 row)

SELECT * FROM foo WHERE data = 'センヌリティウス '; id | data ----+------(0 rows)

SELECT * FROM foo WHERE data /= 'センヌリティウス '; id | data ----+------------------ 4 | セリヌンティウス(1 row)

ntext( ゆるい文字型 )

日本語正規化ゆるい演算子 (/=)

類似度計算で typo を許容

5

自己紹介 ( こんなの作ってた )

CREATE FOREIGN TABLE bar3 ( my_name text, my_gender text, follower_name text, follower_gender text) SERVER foo OPTIONS (query '{"query":"START n=node(*) MATCH p=fm<-[]-n<-[]-fm RETURN n.name as my_name, n.gender as my_gender, fm.name as follower_name, fm.gender as follower_gender" }');

SELECT my_name, my_gender, follower_name, follower_gender FROM bar3; my_name | my_gender | follower_name | follower_gender ---------+-----------+---------------+----------------- Akagi | Famale | Hiryu | Famale Nagato | Male | Mutsu | Male Mutsu | Male | Nagato | Male Hiryu | Famale | Soryu | Famale Hiryu | Famale | Akagi | Famale Soryu | Famale | Hiryu | Famale(6 rows)

new4j_fdw

グラフデータベース Neo4j をわざわざ PostgreSQL 経由で

SQL 検索する誰得 FDW

neo4j_fdw

6

自己紹介 ( こんなの作ってた )

postgres=# LISTEN HB_CL;LISTENpostgres=# NOTIFY HB_SV,'xxxx';;NOTIFYAsynchronous notification "hb_cl" with payload "Invalid data.(xxxx)" received from server process with PID 29520.postgres=# NOTIFY HB_SV,'0123';;NOTIFYAsynchronous notification "hb_cl" with payload "2 Hit / 1 Blow." received from server process with PID 29520.postgres=# NOTIFY HB_SV,'0813';;NOTIFYAsynchronous notification "hb_cl" with payload "4 Hit! Conguratulatoins!, next new game." received from server process with PID 29520.postgres=#

hb_worker

psql などから数当てができるBackground Worker Process

7

自己紹介

PostgreSQL を無駄に使うのが好き

8

アジェンダ

PostgreSQL と非構造化データXML

hstoreJSON/JSONB

PostgreSQL JSONB と MongoDB

JSONB 多めです

9

PostgreSQL と非構造化データ

10

と、非構造化データのお話をする前に

PostgreSQL の概要と歴史について簡単におさらいします

11

PostgreSQL の概要MySQL と並ぶ OSS RDBMS

ライセンスは BSD ライクなもの高度なクエリにも対応

性能面でも商用 DBMS とも遜色なし9.0 以降はレプリケーションも対応

多種多様なデータ型サポート非常に高い拡張性

活発な開発コミュニティ

12

PostgreSQL の歴史

POSTGRESプロジェクト

の時代

Postgres95の時代

※SQL サポート

PostgresSQLの時代

※6.0 から開始

6.0 ~発展途上の時代

GEQOマルチバイト文字セット

MVCC

7.0 ~やっと実用的に

WALTOAST

VACUUM 改善

8.0 ~商用レベルへWindows 対応

PITR自動 VACUUM

HOT

9.0 ~エンタープライズ化

レプリケーション外部表

メニーコア性能向上マテリアライズドビュー

JSON 対応

PostgreSQLPostgreSQL のの進化はこれからも続く…進化はこれからも続く…

1986年1995年

1997年

1997年

2000年

2005年

2010年

意外と古い?

13

PostgreSQL 9.4そして

PostgreSQL 9.5

現在の最新版

現在、開発中

14

PostgreSQL 9.4JSONB データ型

ALTER SYSTEM コマンドマテリアライズド・ビューの改善

ロジカル・デコーディングバックグラウンドワーカプロセスの動的操作

WAL バッファへの並列挿入

etc... 200 項目近くの改善 / 修正

最新版は 9.4.4( 昨晩アナウンス )

15

PostgreSQL 9.5( 開発中 )IMPORT FOREIGN SCHEMARow-Level Security Policies

BRIN Indexes(Block Range Index)Foreign Table Inheritance

JSONB-modifying operators and functionsUPSERT(INSERT ... ON CONFLICT DO NOTHING/UPDATE)

Parallel VACUUMingetc...

今後、変更される可能性はあり

Release Notesも先日公開されました!http://www.postgresql.org/docs/devel/static/release-9-5.html

16

非構造化データ?

17

構造化の観点から見た分類

あり

なし

完全

不完全

構造化データ構造化定義

半構造化データ

非構造化データ構造の定義を持たない

構造の定義が完全ではない

完全に定義された構造を持つ

18

構造化の観点から見た分類種別 内容 例構造化データ 何らかの定義(スキーマ定義等)に

より、構造を事前に決定しておくデータ。

CREATE TABLE 文で定義されたテーブル

半構造化データ 事前の定義はあるが、定義内の一部の構造が存在しなくても良いデータ。

XML スキーマで定義された XML 文書

非構造化データ 事前の構造定義を持たないデータ。 任意の JSON 文書

通常、 RDBMS では上記の種別のデータのうち、構造化データを扱う。

19

PostgreSQL と非構造データ型

PostgreSQL で通常扱うのは、「構造化データ」

さらに、それに加えて PostgreSQL では「半構造化データ」「非構造化データ」(以降、両方とも「非構造化データ」と記述)を扱うしくみも用意されている。

20

PostgreSQL と非構造データ型

PostgreSQL で使用可能な、非構造化データ型は以下の 4 種類

型名 対応バージョン 位置づけ 備考XML 8.2 ~ 本体機能 libxml2 依存hstore 8.3 ~ contribJSON 9.2 ~ 本体機能JSONB 9.4 ~ 本体機能

21

XML 型

XML を格納するデータ型本体機能 (configure で指定 )格納時に XML パースを行なう型自体に比較演算機能はないxpath によるアクセスが可能SQL 関数による XML 型構築libxml2 ライブラリに依存

22

XML 型の処理イメージXML 文字列

XML パーサ

XML 型(文字列)

XML 文字列

関数の結果

(そのまま出力)

格納時

取り出し時

xpath関数

XML パーサ関数処理

xpath 処理

パーサではチェックのみを行う

PostgreSQLストレージ

23

XML 型でできること

XML 文字列のパースと格納xpath による結果の取り出し

SQL 関数による XML 型の構築

24

XML 型の使用例XML 型カラムを持つテーブルother=# \d xml_t Table "public.xml_t" Column | Type | Modifiers --------+---------+---------------------------------------------------- id | integer | not null default nextval('xml_t_id_seq'::regclass) data | xml |

other=# SELECT data FROM xml_t; <rdb_t><email>[email protected]</email><created_at>1987-08-21T18:42:02.269Z</created_at><country>Paraguay</country><id>0</id><full_name>Carolyne Kohler</full_name></rdb_t> <rdb_t><email>[email protected]</email><created_at>1989-03-16T14:37:36.825Z</created_at><country>France</country><id>1</id><full_name>Paul Weber DVM</full_name></rdb_t> <rdb_t><email>[email protected]</email><created_at>1980-02-19T04:16:52.113Z</created_at><country>Uzbekistan</country><id>2</id><full_name>Florence Murphy</full_name></rdb_t>(3 rows)

25

XML 型の使用例xpath 関数による XML 文書からの抽出other=# SELECT (xpath('/rdb_t/email/text()', data)::text[])[1] as email,(xpath('/rdb_t/full_name/text()', data)::text[])[1] as fullnameFROM xml_t; email | fullname --------------------------+----------------- [email protected] | Carolyne Kohler [email protected] | Paul Weber DVM [email protected] | Florence Murphy(3 rows)

XML や xpath関数は強力だけど、書くのは少々面倒・・・

(例) xpath関数は XML配列を返却するので、テキストを取り出す場合に、 TEXT

配列にキャストして最初の要素を取り出すなどの操作が必要。(例)名前空間 url と prefix の組を配列化して xpath関数に渡す必要がある。

26

hstore 型

Key-Value store データ型contrib モジュールネストはできない。

部分更新インタフェースあり※ 作者は JSONB と同じ Oleg 氏

27

hstore 型の処理イメージhstore 文字列

hstore パーサ

hstore 型(バイナリ)

hstore 文字列

関数の結果

文字列化

格納時

取り出し時

hstore関数

関数処理 バイナリを直接解釈して関数実行

パーサは動作しない

PostgreSQLストレージシリアライズ

28

hstore 型でできること

hstore 文字列のパースと格納キーによる値の取り出し

キーと値のセットで追加 / 更新指定キーのキー&値を削除

レコードからの生成JSON への変換

GIN インデックス対応

29

hstore 型の使用例hstore 型カラムを持つテーブルother=# \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+-------------------------------------------------- hstore | 1.3 | public | data type for storing sets of (key, value) pairs plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language(2 rows)

other=# \d hstore_t Table "public.hstore_t" Column | Type | Modifiers --------+---------+------------------------------------------------------- id | integer | not null default nextval('hstore_t_id_seq'::regclass) data | hstore |

30

hstore 型の使用例hstore 型カラムの検索other=# SELECT data FROM hstore_t; data ---------------------------------------------------------------------------------------------------------------------------------------------- "id"=>"0", "email"=>"[email protected]", "country"=>"Paraguay", "full_name"=>"Carolyne Kohler", "created_at"=>"1987-08-21T18:42:02.269Z" "id"=>"1", "email"=>"[email protected]", "country"=>"France", "full_name"=>"Paul Weber DVM", "created_at"=>"1989-03-16T14:37:36.825Z" "id"=>"2", "email"=>"[email protected]", "country"=>"Uzbekistan", "full_name"=>"Florence Murphy", "created_at"=>"1980-02-19T04:16:52.113Z"(3 rows)

31

hstore 型の使用例hstore 型カラムから特定のキーのみ抽出other=# SELECT data->'email' as email, data->'full_name' as full_name FROM hstore_t; email | full_name --------------------------+----------------- [email protected] | Carolyne Kohler [email protected] | Paul Weber DVM [email protected] | Florence Murphy(3 rows)

条件式を使う例other=# SELECT data->'id' as id, data->'email' as email FROM hstore_t WHERE data->'id' = '1'; id | email ----+-------------------------- 1 | [email protected](1 row)

XML 型 /xpath関数を使うよりシンプルに書ける

32

hstore に関する補足

hstore については、 JPUG の「第 20 回しくみ + アプリケーション勉強会 (2011年 6 月 4 日 ) 」でも解説しているので、そちらも参考にしてください ( 多少情報

は古いかもしれませんが…)。

http://www.postgresql.jp/wg/shikumi/sikumi_20/http://www.postgresql.jp/wg/shikumi/study20_materials/hstore_introduction/view

33

JSON/JSONB概説

34

JSON とは

JavaScript Object NotationXML よりも軽量

構造化されたデータを文字列で表現可能

35

PostgreSQL におけるJSON への取り組み

9.2 から順調に進化。 9.5 でも…?

●JSON 型の導入。●2 つの JSON 型構築関数。●JSON 内の値を使った条件検索はできなかった。

●JSON 型関数・演算子の大幅な強化。

●JSON データ型へのパスによるアクセス。

●JSON 内の値を使った条件検索が可能に。

●JSONB 型の導入。●検索の高速化●独自演算子の追加●GIN インデックス対応

9.29.3

9.4

36

JSON 型でできること

JSON 文字列のパースキーによる値の取り出しパスによる値の取り出し

PostgreSQL 配列や行との変換etc ・・・

37

JSON 文字列のパース

PostgreSQL へのデータ格納時に格納される JSON が正しい

書式かチェックする。正しくない書式の場合

PostgreSQL がエラーにする。

38

JSON 文字列のパース例正しい JSON 文字列jsonb=# SELECT '{"key1":"value1", "key2":[100, 20, 5]}'::json; json ---------------------------------------- {"key1":"value1", "key2":[100, 20, 5]}(1 row)

誤った JSON 文字列jsonb=# SELECT '{"key1":"value1", "key2":[100, 20, ]}'::json;ERROR: invalid input syntax for type jsonLINE 1: SELECT '{"key1":"value1", "key2":[100, 20, ]}'::json; ^DETAIL: Expected JSON value, but found "]".CONTEXT: JSON data, line 1: {"key1":"value1", "key2":[100, 20, ]...

※ パースは JSON/JSONB 型に変換されるときに行われる。

カンマの後に値がない

39

キーによる値の取り出し

JSON 型に対してキーを指定して対応する値を取得する。”->”, ”->>” 演算子など

40

キーによる値の取り出しJSON 型全体を取得jsonb=# \pset null (null)Null display (null) is "(null)".jsonb=# SELECT data FROM test ORDER BY id LIMIT 3; data --------------------------------------------------------------------------------------------------------------------------------------------- {"Id": 0, "Email": "[email protected]", "Country": "Paraguay", "Full Name": "Carolyne Kohler", "Created At": "1987-08-21T18:42:02.269Z"} {"Id": 1, "Country": "France", "Full Name": "Paul Weber DVM", "Created At": "1989-03-16T14:37:36.825Z"} {"Id": 2, "Email": "[email protected]", "Country": "Uzbekistan", "Full Name": "Florence Murphy", "Created At": "1980-02-19T04:16:52.113Z"}(3 rows)

41

キーによる値の取り出しキーから JSON オブジェクトを取得jsonb=# SELECT data->'Email' as email , data->'Full Name' as fullname FROM test ORDER BY id LIMIT 3; email | fullname ----------------------+------------------- "[email protected]" | "Carolyne Kohler" (null) | "Paul Weber DVM" "[email protected]" | "Florence Murphy"(3 rows)

キーから文字列を取得jsonb=# SELECT data->>'Email' as email , data->>'Full Name' as fullname FROM test ORDER BY id LIMIT 3; email | fullname --------------------+----------------- [email protected] | Carolyne Kohler (null) | Paul Weber DVM [email protected] | Florence Murphy(3 rows)

42

パスによる値の取り出し

JSON 型に対してパスを指定して対応する値を取得する。“#>”, “#>>” 演算子

43

パスによる値の取り出しperson, name, first のパスで取得jsonb=# SELECT '{"person":{"name":{"first":"Tom","last":"Lane"},"age":59}}'::json #>> '{person, name, first}'; ?column? ---------- Tom(1 row)

person

name

first last

age

Tom Lane

59

(root)

44

パスによる値の取り出しContributes の 2 番目 (0 相対 ) を取得jsonb=# SELECT '{"Name":{"First":"Oleg","Last":"Bartunov"},"Country":"Russia","Contributes":["hstore","JSON","JSONB","GIN"]}'::json #>> '{Contributes,2}'; ?column? ---------- JSONB(1 row)

Contributes

name

first last

Oleg

hstore

(root)

JSON JSONB GIN

[0] [1] [2] [3]

Bartunov

45

式インデックスとの組合せ

キーやパスによって取り出した値は式インデックスとしても

使用可能⇒JSON 型を条件列としてインデックス検索も可能!

46

式インデックスとの組合せ式インデックスの設定jsonb=# SELECT data FROM json_t LIMIT 1; data ---------------------------------------------------------------------------------------------------------------------------------------------------------- { "Email": "[email protected]", "Created At": "1987-08-21T18:42:02.269Z", "Country": "Paraguay", "Id": 0, "Full Name": "Carolyne Kohler"}(1 row)

jsonb=# CREATE INDEX json_id_idx ON json_t USING btree ((data->>'Id'));CREATE INDEXjsonb=# \d json_t Unlogged table "public.json_t" Column | Type | Modifiers --------+---------+----------------------------------------------------- id | integer | not null default nextval('json_t_id_seq'::regclass) data | json | Indexes: "json_id_idx" btree ((data ->> 'Id'::text))

47

式インデックスとの組合せ式インデックスを利用した検索jsonb=# EXPLAIN ANALYZE SELECT data FROM json_t WHERE data->>'Id' = '1000'; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on json_t (cost=4.68..130.11 rows=50 width=32) (actual time=0.078..0.078 rows=1 loops=1) Recheck Cond: ((data ->> 'Id'::text) = '1000'::text) Heap Blocks: exact=1 -> Bitmap Index Scan on json_id_idx (cost=0.00..4.66 rows=50 width=0) (actual time=0.072..0.072 rows=1 loops=1) Index Cond: ((data ->> 'Id'::text) = '1000'::text) Planning time: 0.248 ms Execution time: 0.106 ms(7 rows)

48

その他の JSON 関数

PostgreSQL 配列と JSON 配列との変換行から JSON への変換

JSON から PostgreSQL 行への展開キー集合の取得

49

PostgreSQL 配列と JSON 配列との変換

jsonb=# SELECT array_to_json(ARRAY[1, 3.14, -1.2e3]); array_to_json ---------------- [1,3.14,-1200](1 row)

jsonb=# TABLE bar; id | n_datas | t_datas ----+------------------+------------------------ 1 | {1.32,9.76,5.55} | {bdho,fjoal} 2 | {6.43,0.48} | {vbwdahoi,3dsai,cfjia} 3 | {} | {}(3 rows)

jsonb=# SELECT array_to_json(bar.t_datas) FROM bar; array_to_json ------------------------------ ["bdho","fjoal"] ["vbwdahoi","3dsai","cfjia"] [](3 rows)

PostgreSQL 配列をJSON 配列に変換

テーブル内の特定の列をJSON 化した例

50

行から JSON への変換

jsonb=# TABLE foo; id | n_data | t_data ----+--------+---------- 1 | 8.93 | 366fd4cf 2 | 5.23 | 3f0a243b 3 | (null) | (null)(3 rows)

jsonb=# SELECT row_to_json(foo.*) FROM foo; row_to_json -------------------------------------------- {"id":1,"n_data":8.93,"t_data":"366fd4cf"} {"id":2,"n_data":5.23,"t_data":"3f0a243b"} {"id":3,"n_data":null,"t_data":null}(3 rows)

カラム値が null の場合はJSON の null に変換

テーブル全体をJSON 化した例

51

JSON から PostgreSQL 行への展開

jsonb=# select * from json_each_text('{"id": 2, "age": 59, "name": {"last": "Lane", "first": "Tom"}}'); key | value ------+---------------------------------- id | 2 age | 59 name | {"last": "Lane", "first": "Tom"}(3 rows)

※内側のキー ("first", "last") と値は展開しない。 ※ key, value という固定の列名として返却される。 ※ key も value も TEXT 型として返却される。

52

キー集合の取得

jsonb=# TABLE test; {"id": 1, "name": {"first": "Oleg"}, "distribute": ["GIN", "hstore", "json", "jsonb"]} {"id": 2, "age": 59, "name": {"last": "Lane", "first": "Tom"}} {"id": 3, "name": {"nickname": "nuko"}, "distribute": ["ksj", "neo4jfdw"]}

jsonb=# SELECT DISTINCT jsonb_object_keys(data) FROM test; distribute age id name

以下のような JSONB を含む全レコードからキーを取得する。

jsonb_object_keys関数の結果

※内側のキー ("first", "last") は取得できない点に注意!※ 自分で Outerキーを指定して取得した JSONB に対して  jsonb_object_keys を発行することは可能。

53

JSON と JSONB

54

JSON 型格納形式は文字列

JSONB 型格納形式は規定のバイナリ形式

データ型の導入専用演算子の追加

55

JSON と JSONB の格納状態例

00001fb0 95 7b 22 61 61 61 22 20 3a 20 22 41 41 41 41 22 |.{"aaa" : "AAAA"|00001fc0 2c 20 22 62 62 62 31 22 20 3a 20 7b 22 63 63 63 |, "bbb1" : {"ccc|00001fd0 63 22 3a 20 22 43 43 43 43 31 22 7d 2c 20 22 62 |c": "CCCC1"}, "b|00001fe0 62 62 32 22 20 3a 7b 22 63 63 63 63 22 20 3a 20 |bb2" :{"cccc" : |00001ff0 22 43 43 43 43 32 22 7d 20 7d 00 00 00 00 00 00 |"CCCC2"} }......|

00001fa0 b5 03 00 00 20 03 00 00 80 04 00 00 00 04 00 00 |.... ...........|00001fb0 00 04 00 00 00 16 00 00 50 18 00 00 50 61 61 61 |........P...Paaa|00001fc0 62 62 62 31 62 62 62 32 41 41 41 41 00 01 00 00 |bbb1bbb2AAAA....|00001fd0 20 04 00 00 80 05 00 00 00 63 63 63 63 43 43 43 | ........ccccCCC|00001fe0 43 31 00 00 00 01 00 00 20 04 00 00 80 05 00 00 |C1...... .......|00001ff0 00 63 63 63 63 43 43 43 43 32 00 00 00 00 00 00 |.ccccCCCC2......|

{"aaa" : "AAAA", "bbb1" : {"cccc": "CCCC1"}, "bbb2" :{"cccc" : "CCCC2"} }

入力 JSON 文字列

JSON 型の格納状態

JSONB 型の格納状態

入力文字列そのまま

バイナリ化されたデータが格納される

PostgreSQL 9.4で確認

56

バイナリ形式で格納すると

何が嬉しいのか?

57

JSONB は格納効率を優先はしていない

格納サイズに関してはむしろ JSON より大きくなる

ケースもある。

58

JSONB の格納性能もJSON と比較すると遅い。

・格納容量が大きい・シリアライズ化処理

(性能検証結果は後述)

59

JSONB は検索効率を優先

JSON 関数を使ってキーによる検索を行ったときに

JSON と比較して非常に高速(性能検証結果は後述)

60

JSONB データ型JSON 自体にはデータ型の概念がある。

PostgreSQL の JSONB 型もデータ型に一部対応している。

JSON 型 対応する PostgreSQL 型 備考

文字型 text エンコーディングの問題は注意。なお、キーは文字型扱い。

数値型 numeric 浮動小数点型の NaN や Infinity の表記は許容されない。

時刻型 (未対応) PostgreSQL では文字列扱い。

真偽値 boolean 小文字の true/false のみ許容。(on/off とかもダメ )

null - 小文字 null のみ許容。SQL NULL とは別概念。

61

JSONB は入力したJSON 文字列は保持されない。

JSON は入力文字列をそのまま保持する。

62

JSONB 入力結果が保持されない例(数値型)

jsonb=# INSERT INTO jsonb_t VALUES (1, '{"key":1, "value":100}'),(2, '{"key":2, "value":100.0}'),(3, '{"key":3, "value":1.00e2}'),(4, '{"key":4, "value":1.000e2}');INSERT 0 4jsonb=# SELECT * FROM jsonb_t ; id | data ----+---------------------------- 1 | {"key": 1, "value": 100} 2 | {"key": 2, "value": 100.0} 3 | {"key": 3, "value": 100} 4 | {"key": 4, "value": 100.0}(4 rows)

小数なしのnumeric として同値

小数点ありのnumeric として同値

指数表記でなくなる

63

JSON だと単に文字列として格納されているだけなので、入力形式そのままで出力される

jsonb=# INSERT INTO json_t VALUES jsonb-# (1, '{"key":1, "value":100}'),jsonb-# (2, '{"key":2, "value":100.0}'),jsonb-# (3, '{"key":3, "value":1.00e2}'),jsonb-# (4, '{"key":4, "value":1.000e2}')jsonb-# ;INSERT 0 4jsonb=# SELECT * FROM json_t; id | data ----+---------------------------- 1 | {"key":1, "value":100} 2 | {"key":2, "value":100.0} 3 | {"key":3, "value":1.00e2} 4 | {"key":4, "value":1.000e2}(4 rows)

64

JSONB 入力結果が保持されない例(重複キー、キー名の順序)

jsonb=# SELECT '{"key_z":99, "key_q": 20, "key_q" :25, "key_a" : 3}'::json, '{"key_z":99, "key_q": 20, "key_q" :25, "key_a" : 3}'::jsonb;-[ RECORD 1 ]---------------------------------------------- json | {"key_z":99, "key_q": 20, "key_q" :25, "key_a" : 3} jsonb | {"key_a": 3, "key_q": 25, "key_z": 99}

jsonb=# SELECT '{"key_x":[ 1, 400, 8888, 2, 99, 25, 3]}'::json, '{"key_x":[ 1, 400, 8888, 2, 99, 25, 3]}'::jsonb ;-[ RECORD 1 ]---------------------------------- json | {"key_x":[ 1, 400, 8888, 2, 99, 25, 3]} jsonb | {"key_x": [1, 400, 8888, 2, 99, 25, 3]}

同一階層で重複したキーがある場合、後に記述したもののみ有効となる。また、キー名でソートされている。

配列の場合、順序は保証される。 ”最初の key_q”:20の組み合わせはなくなる。

65

JSONB 入力結果が保持されない例(空白の扱い)

jsonb=# SELECT '{"key_z" : 99, "key q": 20, "key_a" : "aa aa"}'::json, '{"key_z" : 99, "key q": 20, "key_a" : "aa aa"}'::jsonb; -[ RECORD 1 ]-------------------------------------------------- json | {"key_z" : 99, "key q": 20, "key_a" : "aa aa"} jsonb | {"key q": 20, "key_a": "aa aa", "key_z": 99}

キーまたは値でない JSON 文字列中の空白は、JSONB では無視される。また、 JSONB の検索結果として出力される JSON 文字列ではキーと値のセパレータである“ :” の後に 1 つ空白文字を入れるという規則で文字列を生成している。

66

JSONB 専用演算子 (9.4)演算子 意味

<@ 左辺の JSON が右辺のキー&値の組を含むかを評価する。

@> 右辺の JSON が左辺のキー&値の組を含むかを評価する。

? 左辺の JSON が右辺の(一つの)キーまたは要素を含むかどうかを評価する。

|? 左辺の JSON が右辺のキーまたは要素を 1 つでも含むかどうかを評価する。( ANY 評価)

&? 左辺の JSON が右辺のキーまたは要素を全て含むかどうかを評価する。( ALL 評価)

67

JSONB 専用演算子 (9.5 追加 )演算子 意味

|| 指定したキーが存在しなければ、キー&値の組み合わせを追加し、存在すれば指定したキー&値の組み合わせを置換する。

- 指定したキーと合致するキー&値の組み合わせを削除する。。

- 指定した番号の配列要素を削除する。

#- 指定したパスに合致するキー&値の組み合わせ、または配列要素を削除する。

文書の部分更新が可能に!

68

<@ 演算子と GIN

GIN インデックスとの組み合わせで、お手軽な

インデックスの作成が可能

69

GIN インデックス

汎用転置インデックス配列型での利用

全文検索等でも利用※ これも JSONB と同じ開発チームで開発している。

http://www.sai.msu.su/~megera/wiki/Gin

70

<@ と GIN インデックスJSONB カラム自体に GIN インデックスを設定jsonb=# SELECT data FROM jsonb_t LIMIT 1; data ------------------------------------------------------------------------------------------------------------------------------------------ {"Id": 0, "Email": "[email protected]", "Country": "Paraguay", "Full Name": "Carolyne Kohler", "Created At": "1987-08-21T18:42:02.269Z"}(1 row)

jsonb=# CREATE INDEX jsonb_idx ON jsonb_t USING gin (data jsonb_ops);CREATE INDEXjsonb=# \d jsonb_t Unlogged table "public.jsonb_t" Column | Type | Modifiers --------+---------+------------------------------------------------------ id | integer | not null default nextval('jsonb_t_id_seq'::regclass) data | jsonb | Indexes: "jsonb_idx" gin (data)

JSONB型用のGINインデックスメソッドの指定

実は指定しなくても後述の演算子はOK

71

この状態で @> 演算子(右辺にはキーと値の組み合わせ)を使うと GIN インデックスによる効率的な検索が可能になる

JSONB カラムに <@ 演算子で条件を記述jsonb=# EXPLAIN SELECT data->'Full Name' as fullname, data->'Country' as country FROM jsonb_t WHERE data @> '{"Country" : "Monaco"}'; QUERY PLAN -------------------------------------------------------------------------- Bitmap Heap Scan on jsonb_t (cost=20.08..54.18 rows=10 width=160) Recheck Cond: (data @> '{"Country": "Monaco"}'::jsonb) -> Bitmap Index Scan on jsonb_idx (cost=0.00..20.07 rows=10 width=0) Index Cond: (data @> '{"Country": "Monaco"}'::jsonb) Planning time: 0.074 ms(5 rows)

<@ と GIN インデックス

72

JSONB カラムに <@ 演算子で条件を記述(検索結果)jsonb=# SELECT data->'Full Name' as fullname, data->'Country' as country FROM jsonb_t WHERE data @> '{"Country" : "Monaco"}'; fullname | country --------------------------+---------- "Romaine Hickle" | "Monaco" "Mr. Joana Huel" | "Monaco" "Jeff Wilkinson" | "Monaco"(以下略)

<@ と GIN インデックス

"Country” が "Monaco” の行だけ選択されている。

73

今までの JSON 型でも"->“,“->>“ 演算子と

関数インデクスの組み合わせでbtree インデクスによる

インデクス検索は出来たが@> + GIN の組み合わせは

キーが不定でも有効!

74

しかも、 PostgreSQL 9.4 では GIN インデックスのサイズ縮小 / 性能改善も行われている

のでさらに効果的!

(参考: GIN インデックスの改善もJSONB 型と同じメンバが実施している)

75

JSONB 型の実装

76

JSON 型と JSONB 型の処理

JSON 型と JSONB 型のデータ格納データ取得

JSON 関数処理の処理イメージを次ページ以降に示す。

77

JSON 型の処理イメージJSON 文字列

JSONツリーJSON パーサ

JSON 型(文字列)

JSON 文字列

関数の結果

(そのまま出力)

格納時

取り出し時

JSON関数

JSON パーサ関数処理

JSONツリー

パーサで生成したJSONツリーは捨てられる

PostgreSQLストレージ

再度パースが必要

78

JSONB 型の処理イメージJSON 文字列

JSONツリーJSON パーサ

JSONB 型(バイナリ)

JSON 文字列

関数の結果

文字列化

格納時

取り出し時

JSONB関数

関数処理 バイナリを直接解釈して関数実行

パーサは動作しない

PostgreSQLストレージシリアライズ

79

JSON ツリーparseState=0

pstate

res

res

type=jbvObject val.object.nPairs=3

keytype=jbvString

pair[0]

valtype=jbvString

len=3 val

AAA

len=2 val

aa

keytype=jbvString

valtype=jbvString

pair[1]

keytype=jbvString

pair[2]

len=2 val

bb

・・・

上記の JSON文字列をパースすると以下のようなツリー構造が生成される

※ PostgreSQL 9.4-beta2の場合

{"aa":"AAA","bb":"BBB","cc":"CCC"}

80

格納サイズ

さっき説明した JSON と JSONB の処理内容から格納サイズを予想すると

以下のようになる。

格納サイズは JSON のほうが小さい?( JSONB はツリー構造情報も保持)

81

以下のような JSON データ{ "Email": "[email protected]", "Created At": "1987-08-21T18:42:02.269Z", "Country": "Paraguay","Id":"0", "Full Name": "Carolyne Kohler"}

測定内容TEXT/JSON/JSONB カラムのみをもつテーブルを作成

上記 1万件を 10 回 COPY した後のテーブルサイズを pg_relation_size() で測定

82

格納サイズ

TEXT 型と JSON 型のサイズは同じJSONB は JSON 型と比較すると 7%増加

PostgreSQL 9.4で確認

TEXT 型 JSON 型 JSONB 型0

2,000,000

4,000,000

6,000,000

8,000,000

10,000,000

12,000,000

14,000,000

16,000,000

18,000,000

20,000,00018,366,464 18,366,464

19,587,072

10万件ロード時のテーブルサイズ

83

インデックスサイズJSON 型のキーに対するbtree 関数インデックス

VsJSONB の GIN インデックス

さっきの JSON 型データの 5 種のキーに対する btree インデックスの総サイズと

JSONB 型の GIN インデックスサイズを比較してみる。

84

各キーから値を取得する btree インデックス群を設定

インデックスサイズ

JSONB 列自体に GIN インデックスを設定

CREATE INDEX jsonb_id_idx ON jsonb_t USING btree ((data->>'Id'));

CREATE INDEX jsonb_fullname_idx ON jsonb_t USING btree ((data->>'Full Name'));

CREATE INDEX jsonb_email_idx ON jsonb_t USING btree ((data->>'Email'));

CREATE INDEX jsonb_created_idx ON jsonb_t USING btree ((data->>'Created At'));

CREATE INDEX jsonb_country_idx ON jsonb_t USING btree ((data->>'Country'));

CREATE INDEX jsonb_data_idx ON jsonb_t USING gin (data jsonb_ops);

85

インデックスサイズ

全てのキーに btree インデックスを設定するより GIN インデックスのほうがインデックスサイズは大幅に小さくなる。

PostgreSQL 9.4で確認

JSONB+btree JSONB+GIN0

5,000,000

10,000,000

15,000,000

20,000,000

25,000,000

18,366,46419,587,072

18,055,168

6,897,664

btee式インデックスサイズとGINインデックスサイズ

pg_relation_size()

pg_indexes_size()

86

性能比較

さっき説明した JSON と JSONB の処理内容から処理性能を予想すると

以下のようになる。

格納処理は JSON のほうが高速単なる SELECT は JSON のほうが高速?JSON 関数を使った場合は JSONB が高速

87

測定環境

PC:Let's note SX2(SSD)OS:CentOS 6.3( メモリ 4GB)

PostgreSQL パラメータshared_buffers=128MB

checkpoint_segments=30

88

以下のような JSON データ{ "Email": "[email protected]", "Created At": "1987-08-21T18:42:02.269Z", "Country": "Paraguay", "Id": 0, "Full Name": "Carolyne Kohler"}

測定内容JSON/JSONB カラムをもつテーブルを UNLOGGED で作成

上記 1万件を 10 回 COPY したときの平均性能SELECT(10 万件 ) の平均性能

SELECT(10万件 ) + JSON 演算子を使ったときの平均性能btree vs GIN インデックス検索性能

89

COPY 処理時間

TEXT と JSON の差分が 10000 件分の JSON パース時間?JSON と JSONB の差分が 10000 件文のシリアライズ時間?GIN インデックスありの場合はかなり遅くなる。

PostgreSQL 9.4で測定

TEXT JSON JSONB JSONB +GIN0.00

50.00

100.00

150.00

200.00

250.00

300.00

23.67 31.00

74.68

269.80

COPY平均処理時間 (10000件:単位ms)

90

JSON関数なし JSON関数あり0

20406080

100120140160180200

20.27

173.16

17.79

50.01

全件 SELECT時間( 10万件:単位ms )

JSON型JSONB型

SELECT 処理時間

単なる SELECT はほとんど差はない?JSON 関数を使用した場合の性能差は明確。

PostgreSQL 9.4で確認

91

btree vs GINEXPLAIN ANALYZE SELECT * FROM jsonb_t WHERE data->>'Country' = 'Monaco'; Bitmap Heap Scan on jsonb_t (cost=12.29..1240.26 rows=500 width=161) (actual time=0.180..0.692 rows=310 loops=1) Recheck Cond: ((data ->> 'Country'::text) = 'Monaco'::text) Heap Blocks: exact=280 -> Bitmap Index Scan on jsonb_country_idx (cost=0.00..12.17 rows=500 width=0) (actual time=0.112..0.112 rows=310 loops=1) Index Cond: ((data ->> 'Country'::text) = 'Monaco'::text) Planning time: 0.607 ms Execution time: 0.767 ms(7 rows)

EXPLAIN ANALYZE SELECT * FROM jsonb_t WHERE data @> '{"Country":"Monaco"}'; Bitmap Heap Scan on jsonb_t (cost=28.77..362.50 rows=100 width=161) (actual time=1.709..2.502 rows=310 loops=1) Recheck Cond: (data @> '{"Country": "Monaco"}'::jsonb) Heap Blocks: exact=280 -> Bitmap Index Scan on jsonb_data_idx (cost=0.00..28.75 rows=100 width=0) (actual time=1.630..1.630 rows=310 loops=1) Index Cond: (data @> '{"Country": "Monaco"}'::jsonb) Planning time: 0.086 ms Execution time: 2.566 ms(7 rows)

GIN インデックス使用時

btree 式インデックス使用時

GIN より btree 式インデックスのほうが若干速い

92

測定結果まとめ

格納: JSON が高速SELECT :ほぼ同じ?

JSON 関数: JSONB が高速GIN より btree 式インデックスが高速

93

格納効率優先なら JSON検索効率優先なら JSONB

入力形式を保持するなら JSON

⇒だいたいの場合、 JSONB で OK?

結局、 JSON と JSONB はどう使い分けるのか?

94

XML, hstore, JSON/JSONB 比較データ型 表現能力 格納サイズ 参照性能 更新性能XML ◎ △ △ △hstore △ ○ ◎ ◎JSON ○ ○ ○ ◎JSONB ○ ○ ◎ ○

XML は表現能力は高いが扱いにくいhstore はシンプルだが速いJSONB は良いとこ取り?⇒ 用途で使い分け可能

95

pgbench で各データタイプの性能の傾向を確認

96

測定環境とモデルLet's note SX4

VMWare + CentOS 7.0PostgreSQL 9.5-devel

Scale Factor = 10( accounts:100 万件)

カスタムクエリ機能を使用

ホントはAWS EC2のいい感じのインスタンスを使いたかっけど間に合わなかった

※ 各型に対応したカスタムクエリについては付録 1 を参照

97

ロード時間とインデックス

作成時間

98

XML のロードとインデックス作成は遅いJSON と hstore はやや速い

XML

hstore

JSON

JSONB

0.000 5000.000 10000.000 15000.000 20000.000 25000.000 30000.000

ロード時間およびインデックス作成時間

インデックス作成時間 (ms)

ロード時間 (ms)

ロード時間とインデックス作成時間

99

このモデルだとカラム数が少ないからJSONB はやや冗長になるのかも

XML

hstore

JSON

JSONB

0 50000000 100000000 150000000 200000000 250000000

テーブルサイズ /インデックスサイズ

インデックスサイズ

テーブルサイズ

テーブルサイズとインデックスサイズ

100

全件更新の性能も XML は非常に悪いhstore はかなり速い

JSONB-9.5 部分更新の効果も見える

XML

hstore

JSON

JSONB

JSONB-9.5

0 10000 20000 30000 40000 50000 60000 70000 80000

76042

5292

12508

12241

8829

100万件更新時間 (ms)

accounts(100 万件)全件更新時間

101

pgbench 走行トランザクション内容

・ UPDATE 3 回・ SELECT 1 回・ INSERT 1 回同時接続数 =2走行時間 =300秒

102

XML は非常に遅いhstore は速い

JSON/JSONB はこのモデルだとあまり差はない。

XML

hstore

JSON

JSONB

JSONB-9.5

通常レコード

0 200 400 600 800 1000 1200 1400 1600 1800 2000

467.436

1360.219

1066.437

1149.694

1103.781

1708.898

pgbenchスループット (tps)

pgbench 走行 スループット

103

参照も XML は遅いhstore は速い

このモデルだと思ったより JSON/JSONB の差がない?

UPDATE accounts

SELECT accounts

UPDATE tellers

UPDATE branches

INSERT history

END

0 0.2 0.4 0.6 0.8 1 1.2 1.4 1.6 1.8 2

pgbenchレイテンシ (ms)

XML

hstore

JSON

JSONB

JSONB-9.5通常レコード

pgbench 走行 レイテンシ

104

JSON 型の想定用途

105

様々なログの収集( fluentd との組み合わせ等)

JSON化

ログ

ログ

ログ

JSONBカラムに格納GIN/btreeインデックス設定

ログ検索アプリケーション

syslog等ログ

既存の表JSONB表

106

微妙なテーブル設計の救済?CREATE TABLE foo { id integer primary key, name text,・・・ memo1 text, memo2 text, memo3 text,・・・ memo100 text}

CREATE TABLE foo { id integer primary key, name text,・・・ memo jsonb}

可変の個数設定されるような予備カラム群

JSONB 型にまとめてしまう

※ きちんと設計をしていれば不要だろうけど・・・

107

MongoDB 等のNoSQL ドキュメント

DB の代替?

or

108

PostgreSQL JSONBと

MongoDB との比較

と、いうことで

109

ドキュメント指向 DB として比較した

MongoDB とPostgreSQL との違い

110

データ管理単位CRUD 操作高度な検索

性能レプリケーションシャーディング

111

データ管理単位PostgreSQL MongoDB

データベースクラスタ ( --dbpath 指定フォルダ)データベース データベース

スキーマ (対応なし)テーブル コレクションレコード 文書

他の NoSQL 系に比べると、RDBMS に比較的近い印象あり。

112

CRUD 操作操作 PostgreSQL MongoDB挿入 ○ ○読み込み ○ ○更新(全体) ○ ○更新(部分) ✗( ~ 9.4) ○UPSERT ✗( ~ 9.4) ○削除 ○ ○

部分更新の有無がポイント⇒逆に言えば更新がなければ差異はない

113

挿入PostgreSQL の場合

MongoDB の場合

INSERT INTO テーブル名 VALUES ( JSON文字列 )

db.コレクション名.insert( JSON文字列 )

114

読み込みPostgreSQL の場合

MongoDB の場合

SELECT JSONB列 FROM テーブル名 [WHERE 条件式]

db.コレクション名.find([条件式])

->>' キー名 ' 演算子 値@> ' キー名 : 値 '

115

更新(全体)PostgreSQL の場合

MongoDB の場合

UPDATE テーブル名 SET ( JSON文字列 )

db.コレクション名.update(条件式,JSON文字列)

116

更新(部分)PostgreSQL の場合 (9.5 以降 )

UPDATE テーブル名 SET 値 = 値 || '(追加するキーと値)'

UPDATE テーブル名 SET 値 = 値 || '(追加するキーと値)'

UPDATE テーブル名 SET 値 = 値 - '(削除するキー名)'

キー追加

キー更新

キー削除

117

更新(部分)MongoDB の場合

db.コレクション名.update(条件式, {$set:{キー:値}})

db.コレクション名.update(条件式, {$set:{キー:値}})

db.コレクション名.update(条件式, {$unset:{キー:値}}

キー追加

キー更新

キー削除

118

削除PostgreSQL の場合

MongoDB の場合

DELETE テーブル名 [条件式]あるいはTRUNCATE TABLE テーブル名

db.コレクション名.remove(条件式)

119

高度な検索操作 PostgreSQL MongoDBソート ○ ○重複排除 ○ ○グルーピング ○ ○集約演算 ○ ○

PostgreSQL の場合は SQL で上記を実現MongoDB は MapReduce や

Aggregation-Framework で実現

120

レプリケーション機能 PostgreSQL MongoDB

レプリケーション方式

本体機能マスタースレーブ

本体機能マスタースレーブ

同期方式 同期 / 非同期 非同期

フェールオーバ機構 他クラスタ製品・ pgpool-II・ Pacemaker

本体機能

121

シャーディング機能 PostgreSQL MongoDBシャーディング方式

本体機能にはない。他製品でカバー。(pgpool-II,pg_shard 等 )今後は、外部表継承によるシャーディング相当が可能になるかも!? (9.5 だとまだJSONB への適用は難しい )

本体機能クライアントドライバ

レプリケーション / シャーディングに関しては、最初からそれを前提として開発されているMongoDB のほうが扱いやすいのかも?

122

性能YCSB ベンチマークを用いて

PostgreSQL JSONB とMongoDB の性能を比較してみた

用途が異なるものを同じベンチマークで測定する意味はあるのか、というツッコミは

あるかと思いますが、参考までに測定しました。

※ JSONB 対応 YCSB については付録 2 を参照

123

YCSB 測定AWS EC2(m3.2xlarge on-demand)

CPU 数 8 、メモリ 30GB

PostgreSQL 9.4.1 (yum)PostgreSQL の環境設定は checkpoint_segments=30

に変更したくらい。

MongoDB 3.0.2 (yum)MongoDB は特に設定なし。

124

YCSB 測定

測定内容ロード (load)⇒10万件

更新と参照 (workload-a )参照のみ( workload-c )⇒それぞれ 10万回実行

125

YCSB 測定ロード処理

0 5 10 15 20 250

2000

4000

6000

8000

10000

12000

14000

16000

YCSB load Throughput

PostgreSQL 9.4

MongoDB 3.0

number of concurrency

Th

rou

gh

pu

t(o

ps

/se

c)

126

YCSB 測定更新と参照

0 5 10 15 20 250

5000

10000

15000

20000

25000

YCSB load Workload-a Throughput

PostgreSQL 9.4

MongoDB 3.0

number of concurrency

Th

rou

gh

pu

t(o

ps

/se

c)

10000(opt/sec)程度になるはず?

127

YCSB 測定参照のみ

0 5 10 15 20 250

10000

20000

30000

40000

50000

60000

YCSB workload-c Throughput

PostgreSQL 9.4

MongoDB 3.0

number of concurrency

Th

rou

gh

pu

t(o

ps

/se

c)

128

YCSB 測定まとめ

PostgreSQL は CPU 数があれば同時実行数が多い場合にスループット向上。

MongoDB は CPU 数が多いマシンで動かしても同時実行性能は上がらない。

⇒その代わりシャードで書き込み分散?

129

MongoDB との比較(まとめ)

130

PostgreSQL と NoSQL

DBMS 得意 不得意PostgreSQL スケールアップ

SQL との連携 ( 結合など )トランザクション

スケールアウト集計処理

MongoDB スケールアウト書き込み並列化集計処理?

スケールアップトランザクション結合

それぞれ向き不向きがある

131

サーバ構成 / スペック次第?

コア数が多いサーバが用意できるならPostgreSQL を MongoDB の代わりに

使うという解もあり?ほどほどのスペックのマシンを多数用意

できるなら MongoDB ?

132

PostgreSQL(+JSONB) の強み

既存の PostgreSQL 環境に新たに JSONB 型を追加することで

SQL レベルで統一して扱えるのは強み。

PostgreSQL + MongoDB 構成をPostgreSQL のみの構成にできるかも?

133

まとめ

134

PostgreSQL は非構造化データを扱う手段がいくつかある。

XML/hstore/JSON/JSONB のどれを使うかは要件次第

要件によっては、 JSON 文書格納先としてMongoDB 等の NoSQL ではなく、PostgreSQL を使うのもありうる。

135

ご清聴ありがとうございました

136

FAQ

137

Q. JSONB って日本語扱える?

A. キーにも値にも日本語は使えます。ただし、サーバエンコーディングがUTF-8 環境で使うのが無難そう。

日本語キーのパスを使って日本語を取得する例jsonb=# SELECT '{"種別 ":"ラーメン ", "値段 " : 650, "スープ ":"豚骨醤油 ", "トッピング ":["チャーシュー ","ほうれん草 ","海苔 ","葱 "]}'::jsonb #> '{トッピング , 2}'; ?column? ---------- "海苔 "(1 row)

138

Q. JSONB ってトランザクションに対応しているの?

A. してます。 JSONB も PostgreSQL 上では他のデータ型と一緒の扱いです。

139

Q. PostgreSQL のクライアントライブラリでは JSONB を

オブジェクトとして扱えるの?

A. すいません、きちんと調べてません。少なくとも libpq としては特別な扱いは

してないみたいです。

140

Q. 結局、どういうときにJSONB 型を使えばいいの?

A. 開発時にスキーマが決定できない情報を管理する場合。

( XML や hstore も検討してみる)あるいは、外部データとして JSON を

使っていて、それを PostgreSQL で管理したい場合とか・・・?

141

Q. JSON データ管理ならMongoDB とかじゃダメなの?

A. それも一つの選択肢です。スケールアウト重視なら MongoDB 。スタートアップ開発にも向いている。

トランザクションなど堅牢性の重視やSQL アクセスしたいなら PostgreSQL 。

142

参考情報●“PostgreSQL 9.4 Documentation”

http://www.postgresql.org/docs/9.4/static/index.html●“Schema-less PostgreSQL”

http://www.sraoss.co.jp/event_seminar/2014/20140911_pg94_schemaless.pdf

●SRA OSS. “PostgreSQL 9.4 評価検証報告”http://www.sraoss.co.jp/event_seminar/2014/20140911_pg94report.pdf

●第 20 回しくみ+アプリケーション勉強会"PostgreSQL の KVS hstore の紹介”http://www.postgresql.jp/wg/shikumi/study20_materials/hstore_introduction/view

●EDB 社ブログ “ Open Enterprise: The PostgreSQL Open Source Database Blog from EnterpriseDB”http://blogs.enterprisedb.com/2014/09/24/postgres-outperforms-mongodb-and-ushers-in-new-developer-reality/

●”7 つのデータベース 7 つの世界”ISBN 978-274-06907-6

143

付録付録 1 pgbench モデル用クエリ付録 2 YCSB JSONB 用クエリ

144

付録 1pgbench モデル用

カスタムクエリ

145

各データ型を使ってpgbench_accountspgbench_branches

pgbench_tellerspgbench_history

を 1 カラムで表現する

146

pgbench モデルのテーブル

bench=# \d pgbench_accounts; Table "public.pgbench_accounts" Column | Type | Modifiers ----------+---------------+----------- aid | integer | not null bid | integer | abalance | integer | filler | character(84) | Indexes: "pgbench_accounts_pkey" PRIMARY KEY, btree (aid)

通常の pgbench モデル

XML モデルbench_xml=# \d accounts Table "public.accounts" Column | Type | Modifiers --------+------+----------- data | xml | Indexes: "xml_aid_idx" btree (((xpath('/accounts/aid/text()'::text, data)::text[])[1]::integer))

147

pgbench モデルのテーブルbench_hstore=# \d accounts Table "public.accounts" Column | Type | Modifiers --------+--------+----------- data | hstore | Indexes: "hstore_aid_idx" btree (((data -> 'aid'::text)::integer))

hstore モデル

JSONB モデル

bench_json=# \d accounts Table "public.accounts" Column | Type | Modifiers --------+------+----------- data | json | Indexes: "json_aid_idx" btree (((data ->> 'aid'::text)::integer))

JSON モデル

( JSON モデルとだいたいおなじ)今回は GIN インデックスパターンは

使いません

148

pgbench モデルのクエリ

BEGIN;UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;SELECT abalance FROM pgbench_accounts WHERE aid = :aid;UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);END;

通常の pgbench モデル(抜粋)

149

pgbench モデルのクエリ

UPDATE accounts SET data = xmlelement(name accounts, null, xmlelement(name aid, null, ((xpath('/accounts/aid/text()', data))::text[])[1]), xmlelement(name bid, null, ((xpath('/accounts/bid/text()', data))::text[])[1]), xmlelement(name abalance, null, (((xpath('/accounts/abalance/text()', data))::text[])[1])::int + :delta ), xmlelement(name filler, null, ((xpath('/accounts/filler/text()', data))::text[])[1])) WHERE (xpath('/accounts/aid/text()', data)::text[])[1]::int = :aid;

SELECT ((xpath('/accounts/abalance/text()', data))::text[])[1]::int FROM accounts WHERE ((xpath('/accounts/aid/text()', data))::text[])[1]::int = :aid;

INSERT INTO history (data) VALUES ( xmlelement(name history, null, xmlelement(name tid, null, :tid), xmlelement(name bid, null, :bid), xmlelement(name aid, null, :aid), xmlelement(name delta, null, :delta), xmlelement(name mtime, null, CURRENT_TIMESTAMP), xmlelement(name filler, null, ' ')));

XML の pgbench モデル(抜粋)

150

pgbench モデルのクエリ

UPDATE accounts SET data = data || hstore('abalance', ((data->'abalance')::int + :delta)::text ) WHERE (data->'aid')::int = :aid;

SELECT data->'abalance' FROM accounts WHERE (data->'aid')::int = :aid;

INSERT INTO history (data) VALUES ( hstore( ARRAY['tid','bid','aid','delta','mtime', 'filler'], ARRAY[ ( :tid )::text, ( :bid )::text, ( :aid )::text, ( :delta )::text, CURRENT_TIMESTAMP::text, ' ']));

hstore の pgbench モデル(抜粋)

151

pgbench モデルのクエリUPDATE accounts SET data = json_build_object( 'aid', (data->>'aid')::int, 'bid', (data->>'bid')::int, 'abalance', (data->>'abalance')::int + :delta , 'filler', data->>'filler') WHERE (data->>'aid')::int = :aid;

SELECT data->>'abalance' FROM accounts WHERE (data->>'aid')::int = :aid;

INSERT INTO history (data) VALUES ( json_build_object( 'tid', :tid, 'bid', :bid, 'aid', :aid, 'delta', :delta, 'mtime', CURRENT_TIMESTAMP, 'filler', ' '));

JSON の pgbench モデル(抜粋)

152

pgbench モデルのクエリUPDATE accounts SET data = json_build_object( 'aid', (data->>'aid')::int, 'bid', (data->>'bid')::int, 'abalance', (data->>'abalance')::int + :delta , 'filler', data->>'filler')::jsonb WHERE (data->>'aid')::int = :aid;

SELECT data->>'abalance' FROM accounts WHERE (data->>'aid')::int = :aid;

INSERT INTO history (data) VALUES ( json_build_object('tid', :tid, 'bid', :bid, 'aid', :aid, 'delta', :delta, 'mtime', CURRENT_TIMESTAMP, 'filler', ' ')::jsonb);

JSONB の pgbench モデル(抜粋)

153

pgbench モデルのクエリ

UPDATE accounts SET data = data || jsonb_build_object('abalance', (data->>'abalance')::int + :delta) WHERE (data->>'aid')::int = :aid;

SELECT data->>'abalance' FROM accounts WHERE (data->>'aid')::int = :aid;

INSERT INTO history (data) VALUES ( jsonb_build_object( 'tid', :tid, ' bid', :bid, 'aid', :aid, 'delta', :delta, 'mtime', CURRENT_TIMESTAMP, 'filler', ' '));

JSONB(9.5) の pgbench モデル(抜粋)

PostgreSQL 9.5 では hstore 型と似たような部分更新の演算子 ( || ) が追加された!(あと jsonb_build_object が追加されたっぽい)

154

付録 2YCSB JSONB

モデル用クエリ

155

YCSB とは

Yahoo! Cloud Service Benchmark の略

いろんな NoSQL 系に対応したベンチマーク

https://github.com/brianfrankcooper/YCSB

156

YCSB JSONB 対応版

改造方針YCSB JSONB 版はないので

JDBC 版の SQL を JSONB を使うモデルに合わせて修正

157

YCSB JSONB 対応版JDBC 版テーブル定義CREATE TABLE usertable (ycsb_key text, field1 text, field2 text, field3 text, field4 text, field5 text, field6 text, field7 text, field8 text, field9 text, field10 text);CREATE INDEX ycsb_key_idx ON usertable (ycsb_key);

JSONB 版テーブル定義CREATE TABLE usertable(data jsonb);CREATE INDEX ycsb_pk_idx ON usertable USING btree ((data->'YCSB_KEY'));

158

YCSB JSONB 対応版JDBC 版挿入 SQLINSERT INTO usertable VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

JSONB 版挿入 SQLINSERT INTO usertable VALUES(json_build_object('YCSB_KEY', ?, 'FIELD1', ?, 'FIELD2', ?, 'FIELD3', ?, 'FIELD4', ?, 'FIELD5', ?, 'FIELD6', ?, 'FIELD7', ?, 'FIELD8', ?, 'FIELD9', ?, 'FIILD10', ?)::jsonb)

9.5-develだとjsonb_build_object()があるので、 JSONBへのキャストは不要

159

YCSB JSONB 対応版JDBC 版更新 SQLUPDATE usertable SET FIELD1 = ? WHERE YCSB_KEY = ?

JSONB 版更新 SQLUPDATE usertable SET data = json_build_object('YCSB_KEY', data->>'YCSB_KEY', 'FIELD1', ?, 'FIELD2', data->>'FIELD2', 'FIELD3', data->>'FIELD03', 'FIELD4', data->>'FIELD4', 'FIELD5', data->>'FIELD5', 'FIELD6', data->>'FIELD6', 'FIELD7', data->>'FIELD7', 'FIELD8', data->>'FIELD8', 'FIELD9', data->>'FIELD9', 'FIELD10', data->>'FIELD10')::jsonbWHERE data->>'YCSB_KEY' = ?

見ただけでうんざりしますよね。

160

YCSB JSONB 対応版JDBC 版更新 SQLUPDATE usertable SET FIELD1 = ? WHERE YCSB_KEY = ?

PostgreSQL 9.5 JSONB 版更新 SQLUPDATE usertable SET data = data || jsonb_build_object('FIELD1', ?)WHERE data->>'YCSB_KEY' = ?

まだ試してないけど9.4 版と比べると

非常にスッキリ書けるはず

参考

9.5-devel だとjsonb_build_object() があるので、 JSONB へのキャストは不要になる

161

YCSB JSONB 対応版JDBC 版参照 SQLSELECT * FROM usertableWHERE YCSB_KEY = ?

JSONB 版参照 SQLSELECT * FROM usertableWHERE data->>'YCSB_KEY' = ?