はじめに
はじめまして!
決済業界でエンジニアをしている @ykagano です!
「PAY Advent Calendar 2024」の18日目の記事ということで、3年ほど前に決済の負荷試験で学んだOracleDBの待機イベントの対処法を書こうと思います!
決済はいつかスパイクする
決済システムでは決済件数が増えてくるのを見越して、現行システムがどれぐらいのリクエストを処理できるのか負荷試験で確認し、増強計画を立てるキャパシティプランニングを行います。
当時負荷試験を実施したところ、300tpsほどで処理時間のスパイク(急激な変動) が発生していました。
■AIで生成したスパイクのイメージ画像
原因
スパイク時にOracleの待機イベントで buffer busy waits
とともに、 index contention
が発生していたと思います。
複数のセッションからBツリー索引の同一リーフブロックへの書き込みが行われ、スプリット待ち(リーフブロックの再編成待ち) になっている状態でした。
下図は1〜7までのBツリーに8を追加しようとした場合にスプリットが発生するシンプルな例です。
決済用のテーブルにはINDEXを最小限としていたため、原因になるのは主キーのみでした。
主キーにシーケンシャルな値は使用していなかったのですが、一部時系列データを使用していたことが原因でした。
例えば2024年から2025年へ変わる1秒前にデータの書き込みが発生したとします。
「20241231235959ASDFGH」と「20241231235959ZXCVBN」という文字列を主キーにINSERTしようとした場合、先頭からの文字列の大半が同一であるため、Bツリー索引により同じリーフブロックが更新されてしまう状況でした。
対策
文字列全体を分散させることが必要だったため、主キーに入る値をハッシュ化した値に変更しました。
ハッシュ化前:20241231235959ASDFGH
ハッシュ化後:6F5C6F7F3408F9FC4F91845E6C686B8ED8D61F51082B012F3C8989FD53D5F648
そしてハッシュ化前の値は新しく追加したカラムへ入れることにしました。
該当箇所のみですが、下記テーブル定義のイメージです。
テーブル定義
カラム名 | データ型 | キー | 説明 |
---|---|---|---|
id | varchar(255) | PK | 決済ID(ハッシュ化後) |
... | ... | - | 中略 |
payment_id | varchar(255) | - | 決済ID(ハッシュ化前)←カラム追加 |
またバックオフィス向けに、決済情報のデータ取得処理も以下のように変更しています。
データ取得処理
- ハッシュ化した値で主キーを検索(ハッシュ値に切り替え後のデータ)
- ヒットしなければハッシュ化前の値でも主キーを検索(ハッシュ値に切り替え前の過去データ)
ハッシュ化前の決済IDは短い文字列でしたが、ハッシュ化後の決済IDは64文字と表示するには扱いにくいものでした。
そのため、上記1のハッシュ化後の決済IDは検索用とし、ハッシュ化前の決済IDを表示用に使用するようにしました。
下図はデータ取得処理のシーケンス図です。
上記対策後、再度300tpsを超える負荷試験をしてもDBの待機イベントは発生しなくなりました。
まとめ
INSERTの多いテーブルのINDEXに、時系列やシーケンシャルな文字列は一部であっても避けましょう!
index contention についての詳細な情報はこちらが参考になりました。
以上です。