Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Prevent Telegram Broadcast Crashes by Handling 429 Retry-After and Queueing in PHP

2026年10月10日に「Dev.to」が公開したITニュース「Prevent Telegram Broadcast Crashes by Handling 429 Retry-After and Queueing in PHP」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Telegramボットで大量メッセージを一斉送信すると、APIの送信制限(429エラー)で中断や重複が発生する。PHPなどで安定して送るには、APIからの待ち時間指示に従い、データベースとワーカーでメッセージを順次処理する仕組みが重要だ。

ITニュース解説

システムエンジニアを目指す皆さんにとって、アプリケーション開発でAPIと連携することは日常茶飯事になるだろう。その際、特に気をつけなければならないのが、APIの「レート制限」である。この記事では、人気メッセージングアプリTelegramのボットAPIを例に、レート制限の具体的な問題とその賢い対処法を解説する。

まず、想像してみてほしい。皆さんが開発したTelegramのECストアで、フラッシュセールを開始する。通知を希望している5,000人のユーザーに、一斉にセール情報を送るPHPスクリプトを動かしたとする。しかし、数秒後にはスクリプトが停止し、メッセージが二重に届いたり、全く届かなかったりするユーザーが出た上、サーバーログには「HTTP 429 Too Many Requests」というエラーが大量に記録されている。これは、Telegram Bot APIが定める厳格なレート制限を無視した結果に他ならない。Telegramは、ボットがメッセージを送信できる速度に上限を設けており、この制限を超えると、ボットからのリクエストを一時的にブロックし、429ステータスコードを返して「少し待つように」と促す。それでもサーバーへの負荷をかけ続けると、ボットのAPIトークンが一時的、あるいは永久的に制限される可能性もあるのだ。

初心者開発者が陥りがちなのが、この問題に対する「ナイーブなループ」によるアプローチだ。データベースから通知対象の全ユーザーIDを取得し、それぞれのユーザーに対してHTTPリクエストを使ってメッセージを次々と送信する、といったシンプルなスクリプトである。 しかし、この方法はTelegramのレート制限に瞬時に抵触する。Telegramにはいくつかの主要な制限がある。例えば、ボットは全てのチャットを通じて1秒あたり最大30メッセージ、特定のユーザーやプライベートチャットに対しては1秒あたり1メッセージしか送れない。また、特定のグループやチャンネルには1分あたり20メッセージが上限だ。ナイーブなループでは、これらの制限を軽々と超えてしまうため、数秒でAPIからのリクエストブロックが発生し、レスポンスに「ok: false」「error_code: 429」「description: Too Many Requests: retry after 9」「parameters: retry_after: 9」といった情報が含まれて返ってくる。この「retry_after」は、次にリクエストを試行するまでに待つべき秒数を示している。もしスクリプトがこの値を確認して一時停止しなければ、ひたすらリクエストを送り続け、ブロック時間はどんどん伸びていく。最終的に、PHPスクリプト自体の実行時間制限(max_execution_timeなど)を超過し、処理が途中でタイムアウトしてしまい、誰にメッセージが届いたのか、届かなかったのかも不明なまま失敗に終わる。

この問題を解決するためには、まず「レート制限対応HTTPクライアント」を構築する必要がある。これは、Telegram APIへのHTTPリクエストを送信する際に、その応答を詳しく検査する役割を担うPHPクラスだ。このクラスは、APIからの応答がHTTP 429ステータスコードだった場合、その応答に含まれるretry_afterパラメータを解析し、その秒数に安全マージンとして1秒を加えて、スリープ(処理を一時停止)する。その後、自動的にリクエストを再試行する。これにより、ボットはAPIの指示に従って適切に待機し、サーバーに過度な負荷をかけずにメッセージを送信できるようになる。ネットワークエラーのような他の問題が発生した場合も、短時間待機して再試行する仕組みを取り入れることで、一時的な接続不良にも対応できる。しかし、このようなクライアントを導入しても、多数のユーザーにメッセージを送るために同期的なループで処理を行うと、PHPプロセス全体が一時停止してしまうため、ウェブサイトの他の機能に影響が出たり、やはりタイムアウトしたりする可能性がある。

そこで必要となるのが「データベースを使ったキューとワーカー」というシステム設計だ。これは、メッセージの送信要求と実際のAPI送信処理を完全に分離する考え方である。メッセージをブロードキャストする際には、直接APIに送るのではなく、まずメッセージ情報をデータベースの専用テーブル(例: telegram_message_queue)に「保留中(pending)」というステータスで記録する。そして、別の「ワーカー」と呼ばれるバックグラウンドスクリプトが、このデータベースキューからメッセージを順次取り出し、先ほど作ったレート制限対応HTTPクライアントを使ってTelegram APIに送信する。このワーカーは、コマンドラインインターフェース(CLI)や定期的に自動実行されるcronジョブとして動作させることが一般的だ。

このシステムを支えるデータベースには、メッセージ自体を管理するtelegram_message_queueテーブルと、各チャット(ユーザー)へのメッセージ送信間隔を管理するtelegram_chat_throttleテーブルが必要だ。telegram_message_queueテーブルには、メッセージの宛先(chat_id)、送信内容(payload)、現在のステータス(保留中、処理中、送信済み、失敗)、そして再試行が必要な場合のタイムスタンプなどを格納する。telegram_chat_throttleテーブルは、各チャットIDごとに最後にメッセージを送信した時刻を記録し、これにより特定のユーザーに対して1秒に1回以上のメッセージを送らないという制限を守れるようにする。

ワーカーのスクリプトは、無限ループで動作し続ける。ループのたびに、データベースから「保留中」のメッセージを一定量(例えば50件)取得する。メッセージを取得したら、個々のメッセージに対して処理を開始する。まず、telegram_chat_throttleテーブルを見て、そのメッセージの宛先であるユーザーに前回の送信から1秒以上経過しているかを確認する。まだ1秒経っていない場合は、そのメッセージは一時的にスキップし、次のループで再度処理を試みる。これは「ユーザーごとのスロットリング」と呼ばれる重要な機能だ。 次に、処理対象のメッセージのステータスを「保留中」から「処理中(processing)」に更新する。この際、「まだ保留中である」という条件を付けて更新することで、複数のワーカーが同時に動作していても、同じメッセージを二重に処理してしまうことを防ぐことができる。これは「並行処理の安全性」を確保するために不可欠な措置である。 メッセージのステータスを「処理中」にしたら、レート制限対応HTTPクライアントを使ってTelegram APIにメッセージを送信する。送信が成功すれば、telegram_chat_throttleテーブルの最終送信時刻を更新し、telegram_message_queueテーブルのステータスを「送信済み(sent)」に更新する。もし送信中にエラーが発生した場合は、メッセージのステータスを「保留中」に戻し、retry_after_timestampを未来の時刻に設定することで、一定時間(例えば10秒後)にワーカーが再度処理を試みるようにする。 さらに、ワーカーは「グローバルレート制御」も行う。これは、全てのメッセージ送信が終わった後、全体の送信速度がTelegramのグローバル制限(30メッセージ/秒)を超えないように、必要に応じて短い時間(ミリ秒単位)だけ一時停止する仕組みだ。これにより、Telegramに設定されたすべてのレート制限を総合的に遵守し、安定したメッセージ配信を実現する。

このようなキューとワーカーのアーキテクチャは、大規模なメッセージ送信において非常に頑健で信頼性の高いシステムを構築するための基本となる。

プロダクション環境でTelegramボットを運用する際には、レート制限以外にもいくつかの注意点がある。 まず、「Webhookの実行時間制限」だ。TelegramがボットのWebhook URLに更新情報(ユーザーからのメッセージなど)を送信する際、数秒以内にサーバーからのHTTP 200 OK応答を期待している。もしWebhookスクリプトがデータベースへの書き込みやAPIリクエストの送信といった重い処理を同期的に行ってしまい、応答が遅れると、Telegramはサーバーがダウンしたと判断し、同じ更新情報を繰り返し再送してくる。これがサーバーにさらなる負荷をかけ、悪循環に陥る可能性がある。これを避けるためには、Webhookがトリガーされた際には、重い処理は行わず、すぐにデータベースキューに処理内容を記録し、即座にHTTP 200 OKをTelegramに返すことが重要だ。実際の処理は、先述のバックグラウンドワーカーに任せるようにする。 次に、「冪等性(Idempotency)と重複防止」である。Telegramはネットワークの状況などにより、同じupdate_id(更新の識別子)を持つWebhook更新情報を複数回送ってくることがある。この重複を防ぎ、同じ処理が二重に行われるのを避けるためには、受け取ったupdate_idをデータベースやキャッシュ(Redisなど)に記録しておき、既に処理済みのupdate_idを持つ更新が来た場合は、すぐに破棄する仕組みが必要となる。 また、「安全なHTMLフォーマット」も重要だ。メッセージをHTML形式で送信する場合(parse_mode => 'HTML')、メッセージ内容に含まれる<、>、&といった特殊文字を適切にエスケープ(htmlspecialchars()関数などを使用)しないと、Telegram APIは不正なリクエストとしてメッセージを拒否し、HTTP 400 Bad Requestエラーを返すことがある。これは、せっかく構築したキューワーカーの動作を停止させてしまう可能性があるため、動的なユーザー入力などをメッセージに含める場合は、必ず安全な形式に変換するべきだ。 最後に、「Callback Queryの処理」についてだ。ユーザーがインラインキーボードのボタンをクリックすると、Telegramはcallback_queryという更新をWebhookに送ってくる。この時、ユーザーのTelegramクライアント上ではボタンにローディングスピナーが表示され、ボットがそのクリックを認識するまで回り続ける。ユーザー体験を損なわないためにも、callback_queryを受け取ったら、たとえユーザーに通知を表示する必要がなくても、すぐにanswerCallbackQueryメソッドを呼び出して、ローディングスピナーを停止させることが必須だ。

これらの技術的な対策を講じることで、皆さんのTelegramボットは、大量のユーザーを抱える本番環境でも、安定して、かつ信頼性の高いサービスを提供できるようになるだろう。システムエンジニアとしての第一歩を踏み出す上で、このようなAPI連携における「制限」とその「賢い対処法」を理解することは、非常に重要なスキルとなる。

関連コンテンツ

関連IT用語

関連ITニュース