KDOC 652: 明示的なトランザクションと自動コミットで、INSERT の時間が Top SQL のどの文に数えられるかを試す

この文書のステータス

  • 作成
    • 2026-10-11 貴島
  • レビュー
    • 2026-10-11 貴島

概要

Aurora PostgreSQL の Performance Insights で、Top SQL の1位が COMMIT だった。SELECTのクエリは集計の項目として出ていたが、DMLのクエリは出てないように見えた。アプリケーションは RLS のために、すべてのクエリを BEGIN〜COMMIT のトランザクションで実行していた。書き込みの時間は、どんな書き方のときに COMMIT の行に出るのかを確かめる。

pgbench で INSERT を流しながら、別のセッションが 10 ms ごとに pg_stat_activity でアクティブなセッションの文を数える。2つのセッションが同時に要るので、この実験は pgpg のプロキシを通さず、PostgreSQL に直接つなぐ。

# 書き込む側と標本を取る側の2つのセッションが同時に要るので、pgpg のプロキシを通さず PostgreSQL に直接(15433)つなぐ
sample() {
  psql -h localhost -p 15433 -U postgres -d postgres -qAt -F ' | ' <<'SQL'
CREATE TABLE samples (query text, wait text);
DO $$
BEGIN
  FOR i IN 1..400 LOOP
    PERFORM pg_stat_clear_snapshot();
    INSERT INTO samples
    -- 待機イベントがなければ CPU で動いている
    SELECT query, coalesce(wait_event_type || ':' || wait_event, 'CPU')
    FROM pg_stat_activity
    WHERE application_name = 'pgbench' AND state = 'active';
    PERFORM pg_sleep(0.01);
  END LOOP;
END $$;
SELECT query, wait, count(*) FROM samples GROUP BY 1, 2 ORDER BY 3 DESC;
DROP TABLE samples;
SQL
}

# $1: 説明、$2: pgbench に流す SQL、$3: pgbench のオプション
run() {
  echo "== $1"
  docker exec pgpg-postgres-1 psql -U postgres -qAt \
    -c "DROP TABLE IF EXISTS w" -c "CREATE TABLE w (id int, pad text)" \
    -c "CREATE INDEX ON w (id)" -c "CREATE INDEX ON w (pad)" 2>/dev/null
  printf "$2" | docker exec -i pgpg-postgres-1 sh -c 'cat > /tmp/bench.sql'
  out=$(mktemp)
  # 拡張プロトコル(-M extended)で書き込む
  docker exec pgpg-postgres-1 pgbench -U postgres -n -M extended $3 \
    -f /tmp/bench.sql postgres > "$out" 2>/dev/null &
  sleep 0.3
  sample
  wait
  grep 'latency average' "$out"
  rm -f "$out"
}

run "小さい INSERT を明示的なトランザクションで(4並列、5秒)" \
    'BEGIN;\nINSERT INTO w VALUES (1, md5(random()::text));\nCOMMIT;\n' "-c 4 -T 5"
run "小さい INSERT を自動コミットで(4並列、5秒)" \
    'INSERT INTO w VALUES (1, md5(random()::text));\n' "-c 4 -T 5"
run "50万行の INSERT を明示的なトランザクションで1回" \
    'BEGIN;\nINSERT INTO w SELECT i, md5(i::text) FROM generate_series(1, 500000) i;\nCOMMIT;\n' "-c 1 -t 1"
docker exec pgpg-postgres-1 psql -U postgres -qAt -c "DROP TABLE w"
== 小さい INSERT を明示的なトランザクションで(4並列、5秒)
COMMIT; | LWLock:WALWrite | 1128
COMMIT; | IO:WalSync | 395
INSERT INTO w VALUES (1, md5(random()::text)); | CPU | 21
COMMIT; | CPU | 8
INSERT INTO w VALUES (1, md5(random()::text)); | Client:ClientRead | 3
BEGIN; | CPU | 3
BEGIN; | Client:ClientRead | 2
COMMIT; | IO:WalWrite | 2
INSERT INTO w VALUES (1, md5(random()::text)); | IO:DataFileExtend | 1
latency average = 1.684 ms
== 小さい INSERT を自動コミットで(4並列、5秒)
INSERT INTO w VALUES (1, md5(random()::text)); | LWLock:WALWrite | 1144
INSERT INTO w VALUES (1, md5(random()::text)); | IO:WalSync | 394
INSERT INTO w VALUES (1, md5(random()::text)); | CPU | 38
INSERT INTO w VALUES (1, md5(random()::text)); | Client:ClientRead | 6
INSERT INTO w VALUES (1, md5(random()::text)); | IO:WalWrite | 3
latency average = 1.678 ms
== 50万行の INSERT を明示的なトランザクションで1回
INSERT INTO w SELECT i, md5(i::text) FROM generate_series(1, 500000) i; | CPU | 148
INSERT INTO w SELECT i, md5(i::text) FROM generate_series(1, 500000) i; | IO:DataFileExtend | 6
INSERT INTO w SELECT i, md5(i::text) FROM generate_series(1, 500000) i; | IO:WalWrite | 4
INSERT INTO w SELECT i, md5(i::text) FROM generate_series(1, 500000) i; | IO:WalSync | 4
INSERT INTO w SELECT i, md5(i::text) FROM generate_series(1, 500000) i; | LWLock:WALWrite | 3
COMMIT; | IO:WalSync | 1
latency average = 1934.744 ms
  • 小さい INSERT を明示的なトランザクションで繰り返すと、標本の約98%が COMMIT だった。COMMIT の標本のほとんどは、WAL の確定を待つ IO:WalSync と LWLock:WALWrite だった
  • 同じ INSERT を自動コミットにすると、標本はすべて INSERT になった。待機イベントの内訳も、1回あたりの時間も変わらない
  • 50万行の INSERT を1回でコミットすると、標本のほとんどは INSERT の CPU で、COMMIT は1回だけだった。INSERT の最中にも IO:WalWrite と IO:WalSync が出ている。

WalSync: WALファイルが永続的ストレージに達するのを待機しています。

WALWrite: WALバッファがディスクに書き込まれるのを待機しています。

27.2. 累積統計システム

関連