BraintreeのエンジニアのAnthony Ross氏は、最近の記事で、失敗したタスク対する再試行間隔にランダムなジッターを導入することで、どのようにThundering Herd問題を解決したかを説明した。これは、支払い問題(Dispute)管理APIの効率に影響を与えていた。
Thundering Herd問題は、複数のプロセスが1つのイベントを待機している場合に発生する。イベントが発生すると、プロセスは多かれ少なかれ同時に目覚める。これは、そのうちの1つだけが最終的にイベントを処理する場合でも、すべてのプロセスが利用可能なリソースを求めて競合し、システム全体の効率を損なうことを意味する。
Braintreeの場合、Thundering Herdと同様の問題が、システムによる失敗したジョブの対処によって発生していた。つまり、Disputes APIが受け取るリクエストが多すぎると、自動スケーリングメカニズムがトリガーされていた。それでも、システムがスケールインしている間は、多くのジョブが最終的に「デッドレターキュー」(DLQ)に入れられていた。通常、DLQ内のジョブは自動的に再試行されるが、常にそうであるとは限らない。
1日の特定の時点で、ジョブが処理サービスに到達できないのはなぜでしょうか。実際、これらの正確なエラーに対して再試行があったにもかかわらず、なぜ再試行が機能しなかったのでしょうか。ジョブが実際に再試行されていることを再確認したときに、別のことが起こっていることに気付きました。
関係する要因の1つは、DLQ内のジョブに対する再試行の間隔であることが判明した。具体的には、2つの要素が関係していた。それは固定の再試行間隔を使っていたことと、Disputes APIとProcessorサービスの間の結合である。
固定の再試行間隔を使うことが、Thundering Herdの振る舞いの主な原因であった。実際、固定の再試行間隔は、リソースの競合が少ない場合は正常に機能するが、同時に再試行するジョブが多すぎる場合には機能しない。これは、ジョブの一部が失敗してDLQに戻ることを意味する。この問題の解決策は、再試行間隔にランダム性を追加することであった。これには、Ruby on Railsへのパッチが必要であった。Ross氏によると、ジッターを追加すると再試行がより効率的になり、DLQがより早く空になるようになった。
固定の再試行間隔だけが効率を妨げる要因ではなかった。2つ目は、再試行間隔と採用しているスケールイン/アウトポリシーとの間の相互作用であった。
スケールイン・アウトのポリシーは、時間とお金とのトレードオフです。スケールイン・アウトが高速であるほど、費用効率が高くなります。しかしトレードオフは、リソースが一定期間、必要量を下回る可能性があることです。
何が起こったのかを簡単に言うと、システムが利用可能なサービスプロセスの数をスケールアウトするまでの長いインターバルの後に、多くの再試行が実行されたということである。つまり再試行の過負荷を処理できなかったのである。これを改善するために、Braintreeチームはシステムのアーキテクチャを再設計する必要があった。再設計では、独立したプロセッササービスを取り除き、そのロジックの一部をDisputes API自体に含めており、それによって効率的なスケールアウトができるようになった。
詳細を全て確認できるため、元の記事をお見逃しなく。そこには、Disputes APIに関するより正確な説明や、再試行を処理するためにBraintreeに実装されている全体アーキテクチャなどが含まれている。