← 所有文章
指南

Telegram getUpdates offset:避免重複處理與更新遺失

當你呼叫 getUpdates,且傳入的 offset 大於某筆更新的 update_id,Telegram 就會確認該更新。收到回應本身不等於確認。要避免工作遺失,應先持久化本批更新,再送出使用較大 offset 的下一次請求;要安全處理重新啟動,還需將下一次 offset 與更新一起儲存,並把去重與業務處理分開。

重點整理

  • offset 是確認邊界,不是頁碼或訊息數量。
  • 即將被確認的更新全部安全儲存後,才能推進檢查點。
  • 每個機器人只保留一個作用中的輪詢程序;擴充下游工作程序即可。
  • 持久化收件匣保護接收流程,但外部業務動作仍需獨立設計重試。

本文假設你已選擇輪詢。如果仍設定著 webhook,請先參考 getUpdates 與 setWebhook 切換指南。這裡處理的是兩次成功輪詢之間的資料提交,不是重新選擇接收模式。

getUpdates offset 究竟確認了什麼

Telegram 官方 Bot API 文件offset 定義為要回傳的第一筆更新識別碼。省略時,從最早尚未確認的更新開始回傳。後續呼叫的 offset 一旦超過某筆更新的識別碼,該更新便被確認。

**假設範例:**一次回應包含 810081018102。下一次傳入 offset=8103,三筆都會被確認,即使你的應用程式只處理了最後一筆。Telegram 不會查看資料庫,也不會等待 CRM 完成工作。

常見捷徑風險較安全的設計
儲存整批資料前先記錄最大的更新 ID重新啟動後可能跳過根本沒儲存的更新收件匣與下一次 offset 一起提交
最快的平行任務完成就推進尚未完成的較早更新也可能被確認依持久化進度,而非任務完成順序推進
只在記憶體保存 offset重新啟動後失去本機檢查點讀取持久化的機器人專屬檢查點
用負 offset「修復」重複丟棄佇列中更早的更新檢查輪詢控制權與去重機制

Telegram 明確說明,負 offset 會從更新佇列尾端取資料,並遺忘更早的更新。它不是恢復仍有業務價值資料的方法。

分開接收進度與業務完成狀態

建議維護兩種應用程式自己的紀錄:保存完整更新的收件匣,以及保存下一次 offset 的檢查點。這些是本機儲存概念,不是 Telegram 新增欄位。

收件匣唯一鍵可由機器人的穩定身分與 update_id 組成。不要以 bot token 本身作為資料庫鍵,也不要寫入日誌。儲存所有回傳的更新類型,包括目前工作程序尚不認識的類型;分類可在接收之後進行。

建議的交易順序:

讀取該機器人已儲存的下一次 offset。
使用該 offset 呼叫 getUpdates。
若回傳空批次,維持檢查點不變。
否則啟動資料庫交易:
  插入每筆更新;已存在的唯一鍵不重複插入。
  將本批 max(update_id) + 1 儲存為下一次 offset。
提交交易。
只有提交成功後,才發出下一次輪詢。
由獨立工作程序處理已儲存的更新。

這是設計虛擬碼,不是完整輪詢客戶端。它依賴單一作用中的輪詢程序、持久化交易儲存與唯一約束。如果交易失敗,停止推進,從已儲存的檢查點重試;不要捕捉儲存錯誤後仍用較大的 offset 繼續。

如果收件匣與檢查點位於不同系統,上述原子交易不會自動成立。應明確設計持久化交接與核對流程,不能把兩次成功寫入當成原子操作。

依當機邊界驗收

下表是建議測試情境,不是實測結果:

中斷位置應有的復原行為
收到批次後、提交前讀取舊檢查點,允許重複接收
交易執行期間回滾後不能殘留部分檢查點更新
提交後、下一次輪詢前讀取新檢查點,已儲存的工作仍可處理
外部操作成功後、記錄完成前對帳或使用下游冪等能力;接收唯一鍵無法單獨防止重複動作

重複更新應對應到既有收件匣紀錄,但不代表業務已完成。工作程序仍須尋找並重試已儲存但未完成的工作。反過來,重複收到更新也不應自動觸發另一則回覆或再次修改 CRM。

部署時明確移交輪詢控制權。被遺忘的開發程序,或帶有較大 offset 的人工診斷請求,都可能在儲存流程之外確認更新。不要讓「唯讀排查」推進正式環境的進度。

限制與 UnifyPort 的邊界

Telegram 官方說明,傳入更新最多保留 24 小時。檢查點不是無限期封存,調低 offset 也無法復原已確認或已過期的更新。中斷期間應視為可能存在資料缺口。

如果需求是連接既有帳號或整合多個通路,請先閱讀 Telegram Bot API webhook 與統一入站 webhook 比較。UnifyPort 的非官方介面使用 message.received 等標準事件,不使用機器人的輪詢 offset。

UnifyPort 投遞文件定義了獨立的確認契約:設定 signing_secret,以 HMAC-SHA256 對時間戳記、一個點及原始請求位元組驗證 X-Device-Signature,持久化事件後才回傳成功回應。一般事件重試會沿用 X-Device-Event-Id。UnifyPort 不提供 REST 訊息歷史讀取 API,也不保證重播遺漏的酬載,不能復原已確認的 Bot API 更新。

常見問題

為什麼 getUpdates 一直回傳相同更新?

檢查下一次請求是否確實使用大於這些 update_id 的 offset,以及重新啟動後是否讀取已儲存的檢查點。應安全去重,而非清空佇列。

必須等 AI 或 CRM 工作完成才能推進嗎?

若完整更新已持久化,且工作能獨立重試,就不必等待。僅保存在記憶體不算可靠交接。

這樣能保證回覆只傳送一次嗎?

不能。原子儲存解決一類接收遺失問題,但傳送可能已成功,工作程序卻尚未記錄完成。該邊界仍需冪等設計或對帳。

下一步與來源

優先檢查輪詢迴圈的資料庫提交邊界。如果使用的是訊息帳號接收端,請依 webhook 投遞契約實作,不要套用 Bot API offset 邏輯。

UnifyPort API

讓訊息接入變成一條穩定的產品管線。

先用統一 API 跑通傳送,再用標準事件把所有入站訊息接回業務系統。