リトライロジックの正しい実装:信頼性のあるシステムのための指数バックオフ

ソフトウェア開発において、信頼性のあるリトライロジックは、ネットワークの問題や一時的な障害などの間欠的な障害を処理するために不可欠です。最近、あるコードベースで、開発者が固定の時間間隔で失敗した操作をリトライするためにforループを使用しているのを見かけました。このアプローチは一見単純に思えるかもしれませんが、実際のアプリケーションに必要なレジリエンスが不足しています。そこでエクスポネンシャルバックオフが登場します。これは、リトライをよりスマートで効率的にするために設計された戦略です。

この記事では、エクスポネンシャルバックオフがどのように機能するのか、基本的なリトライループに対する利点、そしてシステムの信頼性を向上させるためにどのように実装できるかを見ていきます。また、エクスポネンシャルバックオフを使用して、よりレジリエンスのあるエラーハンドリングを保証する方法を示す実用的な例として、メール送信モジュールを使った説明も行います。

エクスポネンシャルバックオフは、リトライを試みる間の待機時間が各失敗の後に指数関数的に増加するリトライ戦略です。固定の間隔でリトライするのではなく、各次の試みは前の試みよりも長く待機します。通常、毎回遅延が倍増します。たとえば、初期の遅延が1秒の場合、次のリトライは2秒、4秒、8秒と続きます。このアプローチは、システムへの負荷を軽減し、高需要時に外部サービスを圧倒するリスクを最小限に抑えるのに役立ちます。

リトライの間により多くの時間を与えることで、エクスポネンシャルバックオフは一時的な問題が解決する機会を提供し、より効率的なエラーハンドリングとアプリケーションの安定性の向上をもたらします。

  • システム負荷の軽減: リトライを間隔を空けて行うことで、指数バックオフはサーバーが過負荷になる可能性を最小限に抑え、特にレート制限や一時的な障害の処理に便利です。

  • 効率的なエラーハンドリング: 遅延が増加することで一時的な問題が自然に解決される時間が増え、成功するリトライの可能性が向上します。

  • 安定性の向上: 特にトラフィックの多いシステムでは、リトライの試行が氾濫するのを防ぎ、アプリケーションが過剰なリソース消費なしにスムーズに動作するように保ちます。

  • 遅延の増加: 各リトライが徐々に長くなるため、指数バックオフは成功の前に多くのリトライが必要な場合に遅延を引き起こす可能性があります。

指数バックオフは、システムが外部サービスと対話したり、大量のトラフィックを管理したりするシナリオで特に有用です。以下は他の一般的な使用ケースです:

  1. レート制限されたAPI: 一部のAPIにはレート制限があり、特定の時間内にリクエストが制限されます。指数バックオフは、制限を超える可能性のある即時再試行を避けるのに役立ち、制限がリセットされる時間を確保します。

  2. ネットワークの不安定性: 一時的なネットワーク障害やタイムアウトの場合、指数バックオフは試行の間隔を長くすることにより、ネットワークが安定するのを助けます。

  3. データベース接続: 高負荷のデータベースに接続する際、指数バックオフは再試行を遅らせることによりさらなる負荷を防ぎ、データベースが回復する時間を確保します。

  4. キューシステム: メッセージキューシステムでは、エラーによってメッセージが失敗した場合、再試行に指数バックオフを使用することで迅速な再処理を防ぎ、一時的な問題が解決される時間を確保できます。

指数バックオフを示すために、エラーが発生した場合にメールの送信を再試行する基本的なメール送信者を構築します。この例では、指数バックオフが単純なforループと比較して再試行プロセスをどのように改善するかを示しています。

import nodemailer from "nodemailer";
import { config } from "../common/config";
import SMTPTransport from "nodemailer/lib/smtp-transport";

const emailSender = async (
  subject: string,
  recipient: string,
  body: string
): Promise<boolean> => {
  const transport = nodemailer.createTransport({
    host: config.EMAIL_HOST,
    port: config.EMAIL_PORT,
    secure: true,
    auth: { user: config.EMAIL_SENDER, pass: config.EMAIL_PASSWORD },
  } as SMTPTransport.Options);

  const mailOptions: any = {
    from: config.EMAIL_SENDER,
    to: recipient,
    subject: subject,
  };

  const maxRetries = 5; // maximum number of retries before giving up
  let retryCount = 0;
  let delay = 1000; // initial delay of 1 second

  while (retryCount < maxRetries) {
    try {
      // send email
      await transport.sendMail(mailOptions);
      return true;
    } catch (error) {
      // Exponential backoff strategy
      retryCount++;
      if (retryCount < maxRetries) {
        const jitter = Math.random() * 1000; // random jitter(in seconds) to prevent thundering herd problem
        const delayMultiplier = 2
        const backOffDelay = delay * delayMultiplier ** retryCount + jitter;
        await new Promise((resolve) => setTimeout(resolve, backOffDelay));
      } else {
        // Log error
        console.log(error)
        return false; // maximum number of retries reached
      }
    }
  }
  return false;
};

指数バックオフを実装するには、再試行戦略がアプリケーションのニーズに適切に機能するように特定のパラメータを調整する必要があります。次の主要なパラメータは、再試行メカニズムにおける指数バックオフの動作とパフォーマンスに影響を与えます:

  1. 初期遅延
  • 目的: 最初の再試行の前に待機する時間を設定します。即時再試行を防ぐためには十分長く、目立つ遅延を避けるためには十分短くする必要があります。

  • 推奨設定: 500 msから1000 msの間で遅延を開始します。重要なシステムでは短い遅延を使用し、あまり緊急でない操作では長い遅延を設定できます。

  1. 遅延倍率
  • 目的: 各リトライ後に遅延がどれだけ早く増加するかを制御します。倍率が2の場合、遅延が倍になります(例:1秒、2秒、4秒)。

  • 推奨設定: 通常、1.5から2の間の倍率が応答性と安定性のバランスを取ります。より高い倍率(例:3)は、システムがリトライ間の遅延を長く処理できる場合に適しているかもしれません。

  1. 最大リトライ回数
  • 目的: リソースを消耗したりシステム負荷を増加させたりする過剰なリトライを防ぐために、リトライの試行回数を制限します。

  • 推奨設定: 大多数のアプリケーションには、通常3回から5回のリトライが十分です。これを超えると、操作を失敗としてログに記録する必要があるか、ユーザーに通知したりアラートを発動させたりするなど、別の方法で管理する必要があるかもしれません。

  1. ジッター(ランダム化)
  • 目的: 各遅延にランダム性を追加し、リトライが集中して雷鳴の群れ効果を引き起こすのを防ぎます。

  • 推奨設定: 各リトライ間隔に0から500ミリ秒のランダムな遅延を追加します。このジッターは、リトライの試行を時間を通じてより均等に分散させるのに役立ちます。

指数バックオフを使用することで、アプリケーションにレジリエンスを追加し、予期しない問題に対処できるように準備します。これは、小さな変更ですが、大きな影響があります。特にアプリケーションが成長するにつれて。

さて、今回はこれで終わりです。コメントを残したり、質問があれば気軽にどうぞ。より信頼性が高くレジリエントなアプリを構築することに乾杯!

コーディングを楽しんで! 👨‍💻❤️

Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems