履歴情報つきinteger型をpgbenchモデルで動かしてみた
ということで、先週作成した履歴情報付きinteger(疑似)型を、pgbenchモデルに組み込んで、どのくらい性能が劣化するものか確認してみた。
測定モデル
基本的にはpgbanechをベースにした。オリジナルのpgbenchとは以下のような違いがある。
- historyテーブルを削除。
- 更新記録をhistoryテーブルではなく、履歴情報付きinteger型自体に持たせるため。
- accountsのabalance, branchesのbbalance, tellersのtbalanceをintegerではなく、履歴情報つきinteger型(jsonb)で定義する。
テーブル定義
実際の定義はこんな感じ。
CREATE UNLOGGED TABLE accounts (aid int primary key, bid int, abalance jsonb, filler char(84)) WITH (fillfactor = 90); CREATE UNLOGGED TABLE branches (bid int primary key, bbalance jsonb, filler char(88)) WITH (fillfactor = 90); CREATE UNLOGGED TABLE tellers (tid int primary key, bid int, tbalance jsonb, filler char(84)) WITH (fillfactor = 90);
例によって効果を見えやすくするために、テーブルはUNLOGGED TABLEで作成してWAL出力をスキップする。
また、fillfactorを90にしてフツーならHOT更新が効く状態にしておく。
初期データ
初期データは、以下の手順で生成する。
- まず、フツーにpgbenchでデータを初期生成モード(-i -s スケール数)で生成する。
- 以下のSELECT+COPY文でダンプする。
- abalance, bbalance, tbalanceをそれぞれ create_tt_int()関数経由でJSONBデータ化した値で初期化する。
COPY (SELECT aid, bid, create_tt_int(abalance), filler FROM pgbench_accounts) TO '/tmp/accounts.txt'; COPY (SELECT bid, create_tt_int(bbalance), filler FROM pgbench_branches) TO '/tmp/branches.txt'; COPY (SELECT tid, bid, create_tt_int(tbalance), filler FROM pgbench_tellers) TO '/tmp/tellers.txt';
これをさっき作成したテーブルにCOPY FROMで叩き込む。
COPY accounts FROM '/tmp/accounts.txt'; COPY branches FROM '/tmp/branches.txt'; COPY tellers FROM '/tmp/tellers.txt';
なお、各テーブルの初期レコード数は
テーブル名 | レコード数 |
accounts | 1000000 |
branches | 10 |
tellers | 100 |
今回のベンチマークモデルでは、この件数は最後まで変動しない。
実行トランザクション
- pgbenchのカスタムクエリモードで実行する。
- カスタムクエリのファイル内容は以下。
\set scale 10 \set nbranches :scale \set ntellers 10 * :scale \set naccounts 100000 * :scale \setrandom aid 1 :naccounts \setrandom bid 1 :nbranches \setrandom tid 1 :ntellers \setrandom delta -5000 5000 BEGIN; UPDATE accounts SET abalance = add_tt_int(abalance, value(abalance) + :delta) WHERE aid = :aid; SELECT value(abalance) FROM accounts WHERE aid = :aid; UPDATE tellers SET tbalance = add_tt_int(tbalance, value(tbalance) + :delta) WHERE tid = :tid; UPDATE branches SET bbalance = add_tt_int(bbalance, value(bbalance) + :delta) WHERE bid = :bid; END;
オリジナルのpgebnchとの違いは以下。
- accountsのabalance, branchesのbbalance, tellersのtbalanceの更新方法のみ。
- オリジナルは元の値に :delta を加算した値でUPDATE
- 履歴情報つきintの場合は、value()で取り出した値を :delta で加算し、その値を、add_tt_int()で更新(履歴追加)した結果をUPDATE
- WHERE句評価用の各テーブルidはそのままなので、基本的にはUPDATE/SELECT対象のレコードを選択するまでの処理はオリジナルとは変わらないはず。
測定環境
- 今回の測定環境も例によって、Let's note SX4上のVMWare+CentOS7。
- PostgreSQL 9.5.1(ビルドバージョン)
- postgresql.confの設定は基本デフォルトで。
- (悲観的ではあるが)たぶん、性能向上につながるようなパラメータはないような気がする。
- UNLOGGED TABLEなのでWALまわりのパラメータも関係なし。
予測
- branchesテーブルへの更新劣化が原因で、徐々に性能が劣化していくはず。
- branchesテーブルが非常に巨大になるはず。
測定結果
レイテンシ
- やはりbranchesテーブルのレイテンシの劣化が際立っている。線形に劣化しているなあ。ここのレイテンシの悪化がtps低下の主要因ですね。
- 更新結果の生成処理(tt_int_add())の時間と、UPDATE処理時間の合わせ技か。
- 最終的に平均世代数は1000になる。
- tellersもbranchesほどではないが、線形に劣化している。
- 最終的に平均世代数は100になる。
- accountsは1件あたりの世代数がほとんど増えないので、更新性能の劣化はほとんどない。
- またaccountsへの参照も同様にほぼ劣化が見られない。
データサイズ
- 今回データサイズとしてはpg_relation_size(), pg_indexes_size(), pg_total_relation_size()の3つを取得した。
- accountsについては、10000トランザクションでは全くサイズの変動はなかったので、プロット対象外としている。
- また、pg_indexes_size()についても(一応測定はしたけど)サイズの変動はなし。
- branchesのpg_relation_size()の結果を見ると、早々に増加傾向が停まっている。
- が、pg_total_reration_size()の結果を見ると順調に肥大化してますなw
- つまり、branchesのレコードについては、早々に全レコードともTOAST対象になってしまったということかと。
- tellersの場合は、branchesの1/10の速度で世代数が増えていくためか、途中までは段階的にpg_relation_sizeも増加していくが、8000トランザクション以降は(おそらくは)その値のままになりそう=全レコード(100行)が全て、この時点でTOAST対象になったのかな。
- なので、以降はpg_total_reration_size()が線形に増加していくのだろう。
結論
結論も何もないような気がするが
- pgbenchの bbalance, tbalnceのような非常に更新が多いカラムに、履歴情報付き整数型を使うとカジュアルに性能劣化して死ぬ
- 記録する世代数としてはやはり数十程度の用途にしないと、ちょっと厳しそう・・・
- 今回の試作では対象のデータ型をinteger型にしたけど、それより数回〜数十回程度の更新があると思われるTEXT型を履歴情報付きデータ型のターゲットにしたほうがいいのかもなあ。