クエリログを使ったPostgreSQLの負荷テスト

クエリログ

log_statement=allを設定するとpostgresql.logにクエリログされる。

$ cat postgresql.log
2022-05-30 04:59:41 UTC:10.0.3.147(57382):postgres@postgres:[12768]:LOG:  statement: select now();
2022-05-30 04:59:46 UTC:10.0.3.147(57382):postgres@postgres:[12768]:LOG:  statement: begin;
2022-05-30 04:59:48 UTC:10.0.3.147(57382):postgres@postgres:[12768]:LOG:  statement: insert into hello values (1);
2022-05-30 04:59:50 UTC:10.0.3.147(57382):postgres@postgres:[12768]:LOG:  statement: commit;
...

これを自作のツールposlogでndjsonに変換。 log_min_duration_statementで出力されたログもパースできた…はず。

github.com

$ poslog postgresql.log > data.jsonl
$ cat data.jsonl
{"Timestamp":"2022-05-30 04:59:41 UTC","Host":"10.0.3.147","Port":"57382","User":"postgres","Database":"postgres","Pid":"[12768]","MessageType":"LOG","Duration":"","Statement":" select now();"}
{"Timestamp":"2022-05-30 04:59:46 UTC","Host":"10.0.3.147","Port":"57382","User":"postgres","Database":"postgres","Pid":"[12768]","MessageType":"LOG","Duration":"","Statement":" begin;"}
{"Timestamp":"2022-05-30 04:59:48 UTC","Host":"10.0.3.147","Port":"57382","User":"postgres","Database":"postgres","Pid":"[12768]","MessageType":"LOG","Duration":"","Statement":" insert into hello values (1);"}
{"Timestamp":"2022-05-30 04:59:50 UTC","Host":"10.0.3.147","Port":"57382","User":"postgres","Database":"postgres","Pid":"[12768]","MessageType":"LOG","Duration":"","Statement":" commit;"}
...

-fingerprintオプションを付けるとpt-fingerprintのようなinsert into hello values(?+);という感じのFingerprintフィールドも付与される。 「SELECTだけ」みたいなテストデータのフィルタリングに使えると思う。 あとは、似たようなクエリの集計とか。

また、-fill-paramsを付けるとStatementフィールドのプレースホルダ$1 $2…が実際の値で置換される。*1

負荷テスト

自作のツール qrn を使って、上記ndjsonをテストデータとした負荷テストを実行できる。

https://github.com/winebarrel/qrn

こんな感じ。

qrn -dsn "postgres://pgx_md5:secret@localhost:5432/pgx_test" \
  -data data.jsonl -rate 5 -time 300 -nagents 10

上の場合、5エージェント(ユーザー)で5分間、エージェントごとに5 qpsでDBに負荷を与える。

トランザクション

テストデータを複数与えるとエージェントごとに異なるテストデータを実行できるが、簡易化のために -commit-rate というオプションを用意していて、エージェントがNクエリ実行するごとにcommit; beginを実行して新しいトランザクションを開始することができるようにしている。

*1:適当なテキストの置換なのでプレースホルダー以外の$1を含むクエリだとうまく置換できないかも