INDEX に戻る🏠
事象調査
発生日: 2026-07-23 対象: 対戦管理Bot (v2) ステータス: 問題なし・調査完了

LINE Bot 予定登録時の重複メッセージ事象 調査報告

対戦予定の再登録操作でLINE応答遅延が発生し、警告メッセージが重複表示された事象の原因切り分け

調査結論
システム的な不具合は確認されず。想定通りの正常な防御動作。
LINE Messaging API 側のレスポンス遅延により、利用者が操作を重複実行。二重登録防止ロジックが正しく機能し、データ不整合は発生していない。

1 発生事象

利用者がニャースとの対戦予定日を選択した際、1回目の操作後にBotからの応答が表示されなかった。応答がないと判断した利用者が日付選択を再度行った結果、以下の表示となった。

時刻表示内容送信元
20:43日付を選択(利用者操作)利用者
20:44【予定完了】相手: ニャース / 日程: 2026-08-02Bot
20:44ニャース との試合が既に予定されています。先に結果報告を行ってください。Bot
20:45ニャース との試合が既に予定されています。先に結果報告を行ってください。Bot

2 原因の要約

0件
データ不整合
正常
重複検知ロジック
遅延
LINE API 応答
データ確認のお願い — matchesシートでニャース戦の対戦カードが 1行のみ・FIXステータス であることをご確認ください。正常であれば1行のみです。

3 発生メカニズム

利用者が日付選択を計2〜3回タップしたことで、同一操作のWebhookが複数回GASに到達。1回目が予定登録に成功した後、2回目以降は hasFIX チェックにより正しくブロックされた。警告が複数表示されたのは、操作回数分のWebhookそれぞれに対してBot が返信を行ったため。

1 時系列の再現

LINE上の表示タイムスタンプとコードの処理フローから、以下の順序で処理が進行したと推定される。

20:43:xx
利用者 → LINE
1回目の日付選択。postback で params.date = "2026-08-02" が送信される。
20:43:xx
LINE → GAS(Webhook #1)
doPostdoPostV2finalizeScheduleV2 が起動。LockService を取得し、registerSchedule を実行。matchesシートにFIXステータスで書き込み成功。
20:43〜44
LINE Messaging API
Webhook #1 のreply(【予定完了】)の配信に遅延が発生。利用者の画面には何も表示されない状態が続く。
20:44:xx
利用者 → LINE
応答がないため、2回目の日付選択を実行。Webhook #2 がGASに到達。
20:44:xx
GAS(Webhook #2 処理)
registerSchedule 内でmatchesシートを読み取り、hasFIX === true を検出。登録をブロックし、警告メッセージを返信。
20:44
LINE → 利用者画面
遅延していた Webhook #1 の【予定完了】と、Webhook #2 の警告が同タイミングで表示。
20:45
GAS(Webhook #3 処理)
利用者が3回目のタップも行っていた場合、同様に hasFIX で検知。2つ目の警告メッセージが表示。

2 二重登録を防いだ防御機構

現行コードには2層の重複防止ロジックが実装されており、今回はこれが正しく機能した。

Layer 1 — 事前チェック

checkScheduleValidity()
matches.gs : 55-75行目

対戦相手を選択した時点で、matchesシートに当該ペアの FIX 行が存在するかを確認。存在すれば日付選択画面を表示せずに警告を返す。

発動タイミング: 相手チーム選択時(日付選択の前段階)

Layer 2 — 書き込み時の再チェック

registerSchedule() 内の hasFIX 判定
matches.gs : 101-103行目

LockService で排他制御を確保した上で、書き込み直前に再度 FIX 行の有無を確認。同時リクエストによるレースコンディションを防止。

発動タイミング: 日付選択後の書き込み直前(今回はこちらが発動)

3 該当コードの詳細

registerSchedule — 書き込み時の重複チェック

// matches.gs : 90-103行目(概要) for (var i = 1; i < data.length; i++) { var row = data[i]; if (row[MC.TEAM_A] === teamA && row[MC.TEAM_B] === teamB) { var st = row[MC.STATUS]; if (st === "FIX") hasFIX = true; // ... } } if (hasFIX) { return { success: false, message: TEAM_NAME_MAP[oppCode] + " との試合が既に予定されています。\n先に結果報告を行ってください。" }; }

finalizeScheduleV2 — ロック取得と登録呼び出し

// main_v2.gs : 160-170行目(概要) var lock = LockService.getScriptLock(); lock.waitLock(30000); // 最大30秒待機 var result = registerSchedule(myTeam, oppCode, selectedDate); if (!result.success) { replyText(replyToken, "⚠ " + result.message); return; }

4 LINE側のレスポンス遅延について

LINE Messaging API の Reply API は、公式ドキュメントによりSLAが保証されていない。以下の状況で一時的な遅延が発生することが知られている。

今回はGAS側の処理自体は正常に完了しており(【予定完了】メッセージが最終的に配信されている)、遅延はLINEプラットフォーム〜クライアント間の配信遅延と判断される。

1 現状の評価

判定
即時対応は不要。データ保護は正常に機能している。
二重登録の防止ロジックが正しく動作しており、スプレッドシートのデータ不整合は発生していない。

2 改善オプション(任意)

今後同様の遅延時のUX向上および安全性強化として、以下の対策が検討可能。

内容 効果 工数 優先度
A Webhook冪等性(Idempotency)の導入
LINE Webhook の webhookEventIdCacheService に記録し、同一イベントの重複処理を防止する
推奨
B 重複警告メッセージの抑制
同一ユーザーへの同一内容の警告を短時間内に繰り返さないよう、CacheService でスロットリングする
任意
C 日付選択後の処理中メッセージ
Loading Animation(LINE Messaging API 対応)で処理中であることを即座にフィードバックする
任意

案A — Webhook冪等性の実装イメージ

CacheService を利用し、Webhookイベントごとに一意のIDを記録して重複処理を排除する。

// doPostV2 の冒頭に追加 var eventId = event.webhookEventId; if (eventId) { var cache = CacheService.getScriptCache(); var key = "webhook_" + eventId; if (cache.get(key)) { return; // 処理済みのイベント → スキップ } cache.put(key, "1", 600); // 10分間保持 }
効果: LINE側のリトライやユーザーの重複操作を根本的にブロック。警告メッセージの重複表示もなくなる。

3 推奨アクション

即時
データ確認のみ
  • matchesシートでニャース戦が1行・FIXのみであることを確認
  • 利用者への回答:「システム上の問題はなく、通信遅延による表示上の事象です」
任意・次回改修時
案Aの冪等性を導入
  • doPostV2 冒頭に webhookEventId チェックを追加(5行程度)
  • 同様の事象を根本的に防止

1 利用者からの報告(原文)

「先ほどニャースさんとの試合の予定を入力したのですが、日付を選択してしばらく表示が出なかったので連続で入れてしまったところ、画面のようなことになってしまいました。」

2 予定登録フローの全体像

ステップ利用者の操作GAS内部の処理該当ファイル
1 「予定報告」とメッセージ送信 モードを SCHED に設定
対戦相手リストを Quick Reply で表示
main_v2.gs : 45-48
2 対戦相手を選択 checkScheduleValidity() で事前チェック
→ 通過した場合、日付選択UIを送信
main_v2.gs : 125-155
matches.gs : 55-75
3 日付を選択 LockService で排他制御
registerSchedule() でmatchesシートに書き込み
syncMatchesToStarChart() で星取表に同期
main_v2.gs : 160-184
matches.gs : 81-129

3 重複防止の仕組み — 一覧

防御レイヤー実装箇所タイミング対象
Layer 1 — 事前チェック checkScheduleValidity()
matches.gs : 55-75
対戦相手選択時 同一ペアの FIX 行の有無
Layer 2 — 書き込み時再チェック registerSchedule()
matches.gs : 101-103
日付選択後・書き込み直前 同一ペアの FIX 行の有無(Lock下)
排他制御 LockService.getScriptLock()
main_v2.gs : 162-163
日付選択後の処理全体 同時実行の直列化(30秒タイムアウト)
LINE replyToken LINE プラットフォーム側 常時 同一トークンは1回のみ使用可

4 未実装の安全機構

Webhook冪等性が未実装 — LINE は Webhook 配信がタイムアウトした場合に自動リトライを行うが、現行コードは webhookEventIdx-line-request-id ヘッダを参照しておらず、リトライを新規リクエストと同等に処理する。今回は hasFIX チェックでデータ不整合を防げたが、結果報告など他のフローではこの保護がない。

5 用語

用語説明
FIXmatchesシートのステータス。対戦予定が確定済みであることを示す。
hasFIXmatchesシート内に当該チームペアの FIX 行が存在するかどうかのフラグ。
LockServiceGAS の排他制御機構。スクリプトレベルのロックにより同時実行を直列化する。
CacheServiceGAS のキー・バリューキャッシュ。ユーザーの操作状態(モード・選択相手等)を一時保持する。
webhookEventIdLINE Webhook イベントに付与される一意のID。冪等性制御に利用可能。
冪等性(Idempotency)同じ操作を複数回実行しても結果が変わらない性質。API設計における安全性の基本原則。