Relaylab
ACK 遺失、租約過期與冪等寫入的事件模擬。
訊息副作用成功提交,但 ACK 在途中遺失。投遞租約到期後,另一個 worker 再次取到同一筆訊息。Relaylab 以固定種子的離散事件模型,重播這段處理過程。

ACK 遺失與重複寫入
第一次處理就已提交效果,但確認訊息遺失。系統無法從沒有 ACK 這件事推論「處理失敗」,因此到期後重送。單靠重試能提高處理成功機會,卻不能保證每筆業務效果只執行一次。
| 指標 | 冪等關閉 | 冪等開啟 |
|---|---|---|
| Deliveries | 13 | 13 |
| Committed effects | 13 | 12 |
| Duplicate effects | 1 | 0 |
重複投遞沒有消失;被抑制的是第二次寫入。這組數字由程式的範例情境與固定結果測試支持,不代表實際營運統計。
事件與租約流程
新訊息依到達順序進入佇列,等待可用 worker。
每次投遞建立獨立 receipt ID,並登記 visibility timeout。
成功的 worker 先提交效果,然後才可能遇到 ACK 遺失;只有有效 receipt 能確認完成。
租約過期不代表舊 worker 停止執行;重試和舊工作可能同時進行。
排程器以虛擬時間排序事件,同時刻則依插入順序處理。從同一筆 seed 與 scenario 重新運行狀態機,可回到任何一步觀察結果,不依賴預先錄製好的影格。
npm ci
npm run simulate -- --preset lost-ack --out lost-ack.json
npm run simulate -- --preset lost-ack --idempotent --out idempotent.json
npm test
需要 Node.js 24 或更新版本。CLI 拒絕覆寫已存在的輸出檔案;匯出的情境與報告有不同格式,避免把快照誤當成可重播的輸入。
競態與不變量測試
測試在每次轉換後檢查產生數守恆、時間不倒退、重試次數限制、worker 是否只持有一筆工作,以及啟用冪等時「每筆訊息最多一筆效果」的規則。
測試還涵蓋同時刻的完成與過期、已失效 receipt 的 ACK、不允許舊失敗覆蓋新租約、worker crash/recovery,及 100 組不同種子與參數的驗證。
模型假設與死信狀態
模型採單一 queue、原子冪等鍵集合、不過期的 idempotency record、虛擬時間及預先定義的失敗位置。沒有實際網路、資料庫交易、broker 複寫、時鐘飄移或真實 I/O,不能用來聲稱模擬了 SQS、Kafka 或 RabbitMQ 的完整行為。
即使訊息因重試耗盡進入 dead state,舊 worker 仍可能在租約失效後完成副作用。「已進 DLQ」不等於「這筆操作從未發生」。
可從 Lost ACK 情境開始,比較兩種冪等模式,再切換短租約情境,對照 模型假設與測試。