Skip to content

feat(prefilter): stale-while-revalidate refresh thread#210

Merged
JustMaier merged 1 commit intomainfrom
ivy-prefilter-swr
Apr 14, 2026
Merged

feat(prefilter): stale-while-revalidate refresh thread#210
JustMaier merged 1 commit intomainfrom
ivy-prefilter-swr

Conversation

@JustMaier
Copy link
Copy Markdown
Contributor

Summary

Closes design Goal 2 ("Stale-while-revalidate — periodic refresh without blocking queries") and the last Phase-1 gap from the post-merge Plan Review.

A single dedicated thread per engine, spawned from the server's boot phase 7 (after eager preload + bound cache). Each tick (default 10s), walks the registry and recomputes any entry past its `refresh_interval_secs`. Recompute runs off the query path; the new bitmap is published atomically via `ArcSwap` so in-flight queries holding the old `Arc` continue reading the old bitmap.

Lifecycle safety

  • Cycle break: holds `Weak`. Engine drop → next upgrade returns `None` → thread exits.
  • Shutdown: checks `self.shutdown` both per-tick and between entries. Worst-case exit delay = one refresh computation (~300-900 ms at prod scale), which is the unavoidable cost of not interrupting an in-flight recompute.

Depends on / builds on

Test plan

  • Integration test `swr_thread_refreshes_stale_prefilters`: register 10 docs → insert 5 more → manually rewind `last_refreshed` → assert cardinality flips to 15 within 5s. Green.
  • All 4 integration tests + 17 unit tests green.
  • Local 109 M: boot server, register `civitai_safe` with `refresh_interval_secs=60`, watch `bitdex_prefilter_age_seconds` oscillate 0 → 60 → 0. Confirm no query latency spike on refresh tick.

Operational notes

With the age gauge from PR #209, ops can alert on SWR thread wedging: `bitdex_prefilter_age_seconds > 2 * refresh_interval_secs` means something's stuck (thread panic, upgrade failure, etc.). That's the SLO for "the prefilter is being maintained."

Follow-ups after this

Phase 2 work — nothing on the critical path for civitai_safe.

  • Incremental maintenance: subtract slots from prefilter on clean-delete, add on insert, so drift shrinks to "between op applications" not "between refreshes"
  • Include prefilter version in unified cache key to eliminate cache-hit vs cache-miss drift bifurcation

🤖 Generated with Claude Code

Closes design Goal 2 ("Stale-while-revalidate — periodic refresh without
blocking queries") and the last Phase-1 gap called out by the post-merge
Plan Review.

A single dedicated thread per engine, spawned from the server's boot
phase 7 (after eager preload + bound cache). Each tick (default 10s):

  for entry in registry.entries():
      if entry.is_stale(now): refresh_prefilter(entry.name)

`refresh_prefilter` recomputes off the query path and atomically swaps
the bitmap via ArcSwap — in-flight queries holding the old Arc continue
reading; next query sees the new one.

Lifecycle safety:
- Holds `Weak<ConcurrentEngine>`, so engine drop breaks the cycle.
- Checks `self.shutdown` between entries (not just per-tick), so the
  thread can exit promptly even if it's mid-refresh on a stale entry.

Integration test `swr_thread_refreshes_stale_prefilters` verifies
end-to-end: register 10 docs → insert 5 more → rewind last_refreshed →
cardinality flips to 15 within 5s.

With the age gauge from PR #209, ops can alert on SWR thread liveness
(bitdex_prefilter_age_seconds > 2 × refresh_interval = something is
wedged).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JustMaier JustMaier merged commit 0498359 into main Apr 14, 2026
1 check failed
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.

1 participant