Skip to content

fix: last_read_at の排他的境界により最新通知が既読化されない問題を修正 (#100)#101

Merged
limit7412 merged 2 commits into
masterfrom
fix/last-read-at-exclusive-boundary
Jul 14, 2026
Merged

fix: last_read_at の排他的境界により最新通知が既読化されない問題を修正 (#100)#101
limit7412 merged 2 commits into
masterfrom
fix/last-read-at-exclusive-boundary

Conversation

@limit7412

Copy link
Copy Markdown
Owner

概要

issue #100 を解決する。CI 通知などバッチ内の最新の通知が既読化されず、毎分再送され続ける問題を修正する。

原因(実 API 検証済み)

PUT /notificationslast_read_at排他的境界で、updated_at < last_read_at のスレッドだけが既読化される。等値(updated_at == last_read_at)は未読のまま残る。

PUT last_read_at 対象 updated_at 結果
09:24:54Z(等値 = 従来実装) 09:24:54Z unread のまま(status 205)
09:24:55Z(+1s) 09:24:54Z 既読化

従来の mark_read_through は最終チャンクで last_read_at = sent.last.updated_at(送信した最新通知自身の時刻)を渡していたため、バッチ内の最新の1件は原理的に既読化されず、次回実行で再取得→再送を毎分繰り返していた(新しい通知が届くと境界が追い越して解消するが、今度はその通知がスタックする)。

修正内容

排他的境界を前提に境界値を選ぶ:

  1. 最終チャンク(全件送信済み): 境界を取得スナップショット fetched_at にする
    • 取得フィルタ(before = updated < fetched_at)と同じ値・同じ排他的比較のため、「取得・送信した集合」と「既読化される集合」が正確に一致する
    • スナップショット以降の新着は既読化されず次回取得される(取りこぼしの窓も従来より狭い)
    • find_notifications_unread がスナップショットを引数で受け取るよう変更し、Notify::Usecase が取得と既読化で同じ値を使う
  2. 中間チャンク(未送信が残る): 境界を未送信先頭の updated_at にする
    • 排他的なので未送信先頭自身は巻き込まれない
    • 従来の「境界未満の最大 updated_at」は等値の送信済み通知を取り残す同種の問題があったが、これも同時に解消(従来より広くカバー)
    • 同一秒がチャンク境界を跨いだ送信済み分は未読に残り次回再送される(稀な重複を許容して通知ロストを避ける。従来と同じトレードオフ)

途中失敗時の保証(従来どおり)

  • 送信済みチャンクまでは既読化済み、未送信分だけが次回再取得 → 重複投稿なし
  • 境界は常に控えめ(排他的)に倒れるため通知ロストは起きない

テスト

  • spec/notify/usecase_spec.cr を新しい境界選択に合わせて更新
    • 中間チャンク境界 = 未送信先頭の updated_at / 最終チャンク境界 = 取得スナップショット
    • 取得時と既読化時で同じスナップショットを使うことの検証を追加
    • 途中失敗・同一タイムスタンプ・既読化失敗時の送信停止は従来の保証を維持
  • crystal spec 59 examples 0 failures / ameba 0 failures / crystal build src/main.cr 成功

動作確認方法

マージ後 dev にデプロイし、次の CI 通知(CheckSuite)が 1 回だけ送信されて既読化されること(再送が続かないこと)を確認する。

Closes #100

🤖 Generated with Claude Code

PUT /notifications の last_read_at は updated_at < last_read_at のスレッド
だけを既読化する排他的境界(等値は未読のまま残る。実 API 検証済み)。
最終チャンクで送信済み最新の updated_at 自身を境界にしていたため、バッチ内の
最新通知が絶対に既読化されず毎分再送され続けていた。

- 最終チャンクの境界を取得スナップショット(fetched_at)にする。取得フィルタ
  (before)と同じ値・同じ排他的比較のため、取得・送信した集合と既読化される
  集合が一致する
- 中間チャンクの境界を未送信先頭の updated_at に変更。排他的なので未送信先頭は
  巻き込まれず、従来の「境界未満の最大値」より広くカバーできる(等値の
  取り残しも解消)。同一秒がチャンク境界を跨ぐ場合は稀な重複を許容しロストを
  避ける
- find_notifications_unread がスナップショットを引数で受け取るよう変更し、
  Notify::Usecase が取得と既読化で同じ値を使う
- spec を新しい境界選択に合わせて更新、スナップショット共有のテストを追加

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the notification fetching and marking-as-read logic to use a consistent snapshot time (fetched_at) for both operations, ensuring that the fetched and marked-read sets match and resolving issues with exclusive boundaries. The review feedback highlights a potential risk of notification loss due to sub-second precision mismatches between the GitHub GET and PUT APIs, and suggests truncating fetched_at to second precision to mitigate this risk.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/notify/usecase.cr Outdated
before / last_read_at の一致をシリアライズ精度(現状はどちらも秒単位の
RFC 3339)に依存させないよう、スナップショット生成時に秒へ切り詰める。
サブセカンドの解釈差による取りこぼしを構造的に防ぐ。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@limit7412

Copy link
Copy Markdown
Owner Author

dev 環境での動作確認結果:

(確認中に判明した別事象=ウォームコンテナの接続固定による取得不能は #102 として切り出し)

@limit7412
limit7412 merged commit 8e96ccd into master Jul 14, 2026
2 checks passed
@limit7412
limit7412 deleted the fix/last-read-at-exclusive-boundary branch July 14, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant