アクセスのピークを過ぎても、APIのタイムアウトが減らないことがある。新規リクエストが減った後も、失敗したリクエストの再試行が、詰まったDBに仕事を送り続けるからだ。
リトライが過負荷を維持する
短時間のアクセス急増でDBの応答が遅れ、APIがタイムアウトし始める。呼び出し元が再試行するとDBに仕事が増える。タイムアウトした元の処理がDB側でまだ走っていれば、元の処理と再試行が重なる。追加の仕事で応答がさらに遅れ、次のタイムアウトと再試行を招く。
再試行が集中して負荷を増幅する現象をリトライストームと呼ぶ。この例では、アクセス急増が引き金で、リトライストームが障害を維持する仕組みだ。引き金が消えた後も、この循環で過負荷が続く状態を準安定障害(metastable failure)という。
元の処理と再試行が重なるのを抑えるには、リクエスト全体のデッドラインを下流まで伝える。各層では残り時間を使い、期限が切れた処理は開始しない。実行中に呼び出し元が待つのをやめた場合も、キャンセルを下流に伝え、対応している処理を打ち切る。
負荷制御の手法はどこに効くか
| 手法 | どこに効くか |
|---|---|
| 負荷分散 | 余力のあるレプリカに仕事を振り分ける。全レプリカが同じDBを使うなら、振り分け先を変えてもDBに届く仕事の総量は減らない。 |
| バックプレッシャー | 下流の処理能力に合わせて上流を減速させる。例えば、同時実行枠が空くまでワーカーが次のジョブを取り出さない。 |
| ロードシェディング | 処理に入る前に一部のリクエストを断る。例えば、同時実行数が上限に達したら、低優先度の処理を早めに拒否する。 |
| トークンバケット | 一定の速度でトークンを補充し、処理を許可するたびに消費する。補充速度で継続的な許可レートを、バケット容量で短時間のバーストを制御する。再試行専用にも使える。 |
| 指数バックオフ+ジッター | 指数バックオフで再試行までの待ち時間を段階的に伸ばす。ジッターは、再試行が同じ時刻に集中しないように、待ち時間をランダムにばらつかせること。例えば、待ち時間の上限が1秒なら、各クライアントが0〜1秒の範囲から待ち時間を選ぶ。再試行の回数や総量にも上限を設ける。 |
| サーキットブレーカー | 失敗が続く依存先への呼び出しを一時的に遮断し、時間を置いて少数の試行を通し、回復を確かめる。 |
DB呼び出しの同時実行数に上限を設け、実行枠が埋まったら短時間だけキューで待たせる。キューイングは一時的な負荷の山を吸収できる。流入が処理能力を上回り続けると待ち時間が伸び、タイムアウトや再試行が増えるため、キューの件数と待ち時間にも上限を設ける。キューが満杯なら早めに断り、待機中に期限が切れたリクエストはDBに送らない。
APIゲートウェイとサービスクライアントがそれぞれ最大3回試行する構成では、元のリクエスト1件から下流への呼び出しが最大9回発生する。リトライを担当する層を決めておくと、試行回数が層ごとに掛け合わさるのを防げる。
回復するまで流入を絞る
平常時にさばける負荷と、障害中に回復できる負荷は違う。GoogleのSRE本には、平常時は毎秒1万件を処理できるサービスでも、正常なサーバーが1割まで減ると、復旧のために毎秒1,000件程度まで流入を下げる必要があるという例がある。
直前のデプロイや設定変更が引き金なら、負荷を絞るのと並行して切り戻す。流入を絞っても回復しなければ、DBなど詰まっている箇所の処理能力を回復させる。
DBが詰まった例なら、まず再試行や低優先度の処理を絞り、DBに届く仕事を減らす。新規リクエスト数と再試行数を別々に数えると、どこから仕事が増えているかが見える。成功処理数とレイテンシが持ち直し、キューの滞留が減り始めたら、再び詰まらないかを見ながら流入を段階的に戻す。指標が再び悪化したら、流入を直前の水準まで絞り直す。
負荷試験では、何件まで処理できるかに加えて、一度過負荷に入ったあと、どこまで流入を落とせば復旧するかも確かめる。
参考資料
データ指向アプリケーションデザイン 第2版
Metastable Failures in the Wild
Google SRE — Addressing Cascading Failures
AWS Well-Architected Framework — Control and limit retry calls
おすすめの書籍

コメントを残す