Skip to content

last_read_at 既読化の境界が排他的で、最新の通知が既読にならず毎分再送される #100

Description

@limit7412

症状

CI 完了通知(例: ci #27「ci workflow run succeeded for master branch」)が既読にならず、毎分の実行のたびに再送され続ける。より新しい通知が届くと止まるが、今度はその新しい通知が同じように再送され続ける(常に「最新の1件」がスタックする)。

原因(実 API で再現確認済み)

PUT /notifications の last_read_at は排他的境界で、updated_at < last_read_at のスレッドだけが既読化される。updated_at == last_read_at(以降)のスレッドは未読のまま残る(API ドキュメントの "Anything updated since this time will not be marked as read" の since が等値を含む挙動)。

PR #97 の Notify::Usecase#mark_read_through は最終チャンクで last_read_at = sent.last.updated_at(=送信した最新通知自身の updated_at)を渡しているため、バッチ内の最新の通知は絶対に既読化されない。次回実行で再取得され再送 → 毎分繰り返す。より新しい通知が届いてはじめて境界がそれを追い越し、古い方は既読化される(そして新しい方がスタックする)。

再現ログ(2026-07-14 dev 環境)

  • thread 24600653953(updated_at 09:16:23Z)が 8 回連続で再送され、09:24:54Z の新着(ci change bucket #27)が届いた時点で既読化
  • その後 thread 24600759888(ci change bucket #27, updated_at 09:24:54Z)が同様に再送継続

API 検証

PUT last_read_at 対象スレッド updated_at 結果
09:24:54Z(等値 = 現行実装) 09:24:54Z unread のまま(status 205)
09:24:55Z(+1s) 09:24:54Z 既読化された(status 205)

※ 検証の副作用として ci #27 は手動で既読化済み。次の CI 通知からまた再発する。

中間チャンクにも同種の問題

中間チャンクの sent.map(&.updated_at).select(&.<(boundary)).max? も、選ばれた値と同じ updated_at を持つ送信済み通知が既読化されず(等値は残る)、次回重複送信される。

修正案

排他的境界であることを前提に境界値を選ぶ:

  1. 最終チャンク: 取得時に使っているスナップショット時刻(before パラメータの値)を last_read_at に渡す。取得フィルタ(updated before)と既読化(updated before)が同じ排他的比較になるため、「取得・送信した集合」と「既読化される集合」が正確に一致する。find_notifications_unread がスナップショットを引数で受け取るか返すよう変更が必要
  2. 中間チャンク: last_read_at = next_unsent.updated_at を直接渡す(排他的なので未送信の先頭自身は既読化されない)。現行の「境界より小さい最大値」選択より広くカバーでき、かつ安全
  3. 同一秒がチャンク境界を跨ぐ場合は該当分が未読に残り次回重複送信される(at-least-once)。通知ロストは起きない

影響

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions