☆ 本記事は、 PostgreSQL: Update 時、トランザクション有無や ORM 利用でどれくらい性能差が出るかを検証する の続編です。
はじめに
最近パフォーマンスの問題に重点的に取り組んでおり、DB 関連の問題に対応することが多いです。そんな中で、update のパフォーマンスに影響を及ぼす各項目について、実際に検証してみたくなりました。
前回の記事では、生 SQL とアプリケーションの ORM の PostgreSQL update のパフォーマンス比較について、ローカル環境上で検証しました。
本記事では、その続編として AWS 環境でも RTT や update のパフォーマンスに関連する実験を行い、検証結果を共有します。
主な検証項目は以下の 2 点です。
- サーバ (EC2) と DB を同一 AZ に配置した場合と別 AZ に配置した場合の、RTT (round trip time) の差
- トランザクションの張り方と実行環境 (ローカルか AWS 上か) が、それぞれ SQL 実行時間にどう影響するか
なお、以下の GitHub リポジトリに、検証環境を用意するための terraform 設定を公開しています。
再現実験を行いたい方は、 ぜひ terraform を使用して同様の環境を立ちあげてみてください。
TL;DR
- 同一AZ配置でRTTが8.5倍改善した。(異なるAZの場合 1.7ms, 同一AZの場合0.2ms)
- Auroraのコミットコストは重い。1回あたり約2ms(ローカルの 20 倍前後)
- トランザクションを活用することで、10,000件のUPDATE実行時間が 25秒 → 3秒 と劇的に改善した。
- クラウド特有のコミットオーバーヘッドはローカルでは再現できない
検証環境
以下の 3 つの環境で比較検証を行いました。
AWS の検証環境を用意するための terraform 設定 を Github 上で公開しています。環境の立ち上げ方については、リポジトリの README を参照してください。
① ローカル環境
- OS: MacOS Sequoia v15.5
- PostgreSQL:
postgres:17コンテナ(Docker) - ベンチマーク実行:同一マシン上
② AWS, Aurora PostgreSQL (同一 AZ)
- Aurora PostgreSQL: 17.6
- インスタンスクラス:
db.r6g.large - ベンチマーク実行:EC2
t3.medium(同一 VPC 内のプライベートサブネット) - EC2 と Aurora を同一 AZ(ap-northeast-1a)に配置
③ AWS, Aurora PostgreSQL (異なる AZ)
- Aurora PostgreSQL: 17.6
- インスタンスクラス:
db.r6g.large - ベンチマーク実行:EC2
t3.medium(同一 VPC 内のプライベートサブネット) - EC2(ap-northeast-1a)と Aurora(ap-northeast-1c)を異なる AZ に配置
実験 1: RTT (round trip time) の比較
まず RTT (round trip time) を比較するため、各検証環境で 以下のとおり SELECT 1 を 1 万回実行する SQL を流した際の実行時間を計測しました。
これは、update の影響を省いたできるだけ純粋なラウンドトリップタイム(RTT)を計測するために実施しています。
SELECT 1;
SELECT 1;
...
-- 10,000 回
結果は以下のとおりです。
| 環境 | 総実行時間 (秒) | 1 回あたりの RTT (ms) |
|---|---|---|
| ① ローカル | 9.26 | 0.93 |
| ② Aurora 同一 AZ | 2.05 | 0.20 |
| ③ Aurora 異なる AZ | 16.96 | 1.70 |
この結果より、以下のことが分かります。
- 同一 AZ の場合、1 回あたり 0.20ms (ローカル実行の約 4.5 倍、別々 AZ の 1.7 倍) という非常に低いレイテンシを実現できている
- 異なる AZ の場合、同一 AZ の場合に比べ 1 回あたり約 1.5ms のオーバーヘッドが追加される(0.20ms → 1.70ms)
- このオーバーヘッドは、 AZ 間の物理的な距離によるものと考えられる
なお、 aurora 同一 AZ と異なる AZ の比較については、 こちらの記事 の ping の節での報告や、 こちらの記事 の「クエリのレスポンスタイム」の節での報告と、大幅にずれた結果にはなっていなさそうでした。
実験 2: トランザクションの張り方と実行環境が update 文実行時間に与える影響の検証
比較したケース
以下の 2 つのケースについて、各環境で実行時間を比較しました。
a. トランザクションなし(オートコミット)
各 UPDATE 文をそれぞれ独立したトランザクションとして実行するケース。この場合、毎回コミットが発生します(計 10,000 回のコミット)。
UPDATE benchmark_accounts SET status = 'active' WHERE id = 1;
UPDATE benchmark_accounts SET status = 'active' WHERE id = 2;
...
UPDATE benchmark_accounts SET status = 'active' WHERE id = 10000;
b. トランザクションあり(1 トランザクションで全件更新)
全 UPDATE を 1 つのトランザクションにまとめるケース。この場合、コミットは 1 回のみとなります。
BEGIN;
UPDATE benchmark_accounts SET status = 'active' WHERE id = 1;
UPDATE benchmark_accounts SET status = 'active' WHERE id = 2;
...
UPDATE benchmark_accounts SET status = 'active' WHERE id = 10000;
COMMIT;
比較結果
各環境での実行時間結果は以下のとおりでした。
| 環境 | a. トランザクションなし (秒) | b. トランザクションあり (秒) | a と b の差分 (秒) |
|---|---|---|---|
| ① ローカル | 10.79 | 9.86 | 0.93 |
| ② Aurora 同一 AZ | 25.45 | 2.84 | 22.61 |
| ③ Aurora 異なる AZ | 36.26 | 17.72 | 18.54 |
この結果より、以下のことが分かります。
- ローカル環境: トランザクションの有無による差は約 1 秒のみ。複数回コミットのオーバーヘッドは小さい
- Aurora 環境: トランザクションなしの場合、同一 AZ か異なる AZ かにかかわらず、20 秒以上のオーバーヘッドが発生
Aurora のコミットレイテンシの詳細分析
Aurora では aurora_stat_get_db_commit_latency() 関数を使用して、コミット処理にかかった累積時間を測定できます。 (ドキュメント: aurora_stat_get_db_commit_latency.html)
この関数を用いて、コミット処理により発生したレイテンシも検証しました。
| 環境 | a. トランザクションなし (秒) | b. トランザクションあり (秒) | a と b の差分 (秒) |
aurora_stat_get_db_commit_latency の時間(秒) |
|---|---|---|---|---|
| ② Aurora 同一 AZ | 25.45 | 2.84 | 22.61 | 21.5 |
| ③ Aurora 異なる AZ | 36.26 | 17.72 | 18.54 | 17.4 |
② (同一 AZ), ③ (異なる AZ) のどちらの場合においても、計測された aurora_stat_get_db_commit_latency() の時間と a・b の間の差分がほぼ一致することを確認できました。このことより、 コミットにかかる時間が、 a と b の差分の主な要因である と言えます。
また、この結果より、Aurora では1回あたりのコミットに 2 ミリ秒前後かかっているということがわかります。
ローカルでは 1 回あたり 0.1 ミリ秒程度だったので、この約 20 倍の時間がかかっているということです。このレイテンシはなかなかバカにできないなと感じました。 (もちろん DB のインスタンスの性能を上げればレイテンシは縮まるかもしれないので、一概には言えませんが)
クラウド環境を使用する場合は特に、アプリケーション内でコミットの乱発が発生していないか注意した方が良いと感じました。
なぜ Aurora の場合コミットレイテンシが大きいかの考察
これは単なる推測ですが、Aurora では、コミット時に複数の AZ にデータを分散永続化する必要があり、これがコミットのレイテンシに影響を与えているのではないかと思いました。
実際に検証した訳ではないので、推測に止まってしまうことをご了承ください。
この点に関して、適切な検証方法や知見を持っている方がもしいましたら、コメントなどで教えていただけますと筆者が非常に喜びます...
検証結果まとめ
今回の実験をとおし、以下の知見を得ることができました。
- アプリケーションサーバと Aurora を同一 AZ に配置することで、別々の AZ 配置に比べ RTT を大幅に削減できる
- 一回あたりの RTT が 1.70ms → 0.20ms まで削減された
- 特に Aurora を使用する場合、複数の update 文を実行する場合に、複数の update に対し明示的にトランザクションを張ることで、コミットのオーバーヘッドを大幅に削減できる
- Aurora 同一 AZ の場合、約 23 秒削減(25.45 秒 → 2.84 秒)
- 一方ローカル環境の場合、せいぜい 1 秒程度の削減にとどまった
ローカル環境でのパフォーマンステストは、クラウド環境の特性(今回の場合はコミットのオーバーヘッド)を反映できないため、プロダクション環境に近い構成での検証も重要だなと感じました。
最後に
以下のような話は既にベストプラクティスとして定着している話だと思います。そのため、わざわざ検証する必要あるのか?と最初は思っていました。
- サーバと DB を同一 AZ に配置することでレイテンシが小さくなる
- DB に対し複数の変更処理 (update, delete, insert) を実行する場合、 1 コミットにまとめた方がパフォーマンスが上がる
ただ、実際に検証してみることで「ローカルだと複数コミットをまとめるうまみはそこまでないが、クラウド上だとコミットのオーバーヘッドが大きいため、複数コミットをまとめる意義が大きい」といった発見を得ることができました。
やはり、出回っているベストプラクティスであっても、検証してみることではじめて腹落ちするし、検証してみてはじめて発見できることもあるなと感じました。
また、パフォーマンスを考える際はベストプラクティスをなぞるだけでなく、実際に計測・検証してみることが何よりも大事だと感じました。
(余談) あと、今回 Terraform をほぼ初めてちゃんと触ってみたのですが、 terraform destroy で環境を一瞬で破壊できるのが神でした。リソース削除忘れによるコスト爆発にビクビクしなくて良いの、最高ですね。

