Conversation
Before shipping API surface for #673 (per-stream reaction priority), measure whether priority-aware claim ordering actually beats the existing dual-frontier strategy on a saturated workload — and whether it costs us anything elsewhere. - New `test/priority-claim.bench.ts` runs two arms back-to-back on identical seeded data: baseline (live `claim()` SQL) vs. priority-aware (lag CTE adds `priority DESC, at ASC`). - Workload: 1 source × 500 events, 50 cold-replay targets, streamLimit=5 (worker is 10x saturated). - Captures TTF for the priority target *and* total drain time, plus per-stream progress at both moments. - `PERFORMANCE.md` summarizes the methodology + decision (go). Findings — three back-to-back runs: priority TTF speedup: 11.28× / 11.40× / 10.66× total drain delta: −6.6% / −6.7% / −5.3% (priority arm faster) starvation: zero — final state identical to baseline Decision: ship the feature. Implementation in a follow-up commit. No production code touched in this commit; only a bench file and the bench config wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #673. Adds an optional `priority` field on the resolver target, biases the `claim()` lagging-frontier ordering by `priority DESC, at ASC`, and exposes `app.prioritize(filter, n)` for runtime overrides. Default `0` keeps existing apps unchanged. Research-led: PostgresStore benchmark first (separate commit) measured ~11× speedup on the priority target with no aggregate-throughput cost. Implementation followed the data. Surface - `.to({ target, source?, priority? })` on the resolver. - `Store.subscribe` extended with optional `priority`; max() invariant across reactions targeting the same stream. - `Store.prioritize(filter, n)` — bulk update with QueryStreams-shaped filter (regex on stream/source, exact-match flags, blocked state). Sets priority outright (operator override of the build-time max). - `app.prioritize(filter, n)` — orchestrator wrapper. - `StreamPosition.priority` for query_streams introspection. - PG schema migration via ALTER TABLE ADD COLUMN IF NOT EXISTS, composite index `(blocked, priority DESC, at)` for the lag CTE. - SQLite schema migration via try/swallow ALTER (no IF NOT EXISTS). Per-stream event ordering is unchanged — priority biases which streams `claim()` picks first, never reorders events within a stream. Only meaningful under saturation; idle systems are unaffected. Coverage: 100% across the board (1065 tests). Also: rename PascalCase framework source files to kebab-case for consistency with the rest of `libs/*/src/` — PostgresStore.ts → postgres-store.ts, SqliteStore.ts → sqlite-store.ts, PinoLogger.ts → pino-logger.ts. React component files in packages/ keep PascalCase per the React community convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inspector consumes `StreamPosition` via `query_streams` but currently drops the new `priority` field on the way to the client. Add it to: - `streamMeta` query — per-stream priority alongside the existing watermark / blocked / leased fields. - `drainStatus` query — surfaces priority on `blockedStreams` and `activeLeases` rows, plus a new `priorityCounts` aggregate (number of streams per priority lane, sorted highest-first). Compatibility: priority is a new column in the response objects, backwards-compatible additive change. UI work (column rendering, inline editing, prioritize() mutation surface) is intentionally deferred to a separate inspector PR — that's a real design exercise that doesn't belong in ACT-102. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
|
🎉 This PR is included in version @rotorsoft/act-v0.35.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version @rotorsoft/act-pg-v0.20.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version @rotorsoft/act-sqlite-v0.5.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version @rotorsoft/act-pino-v0.4.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #673. Adds an optional
priorityfield on the resolver target, biases theclaim()lagging-frontier ordering bypriority DESC, at ASC, and exposesapp.prioritize(filter, n)for runtime overrides. Default0keeps existing apps unchanged.Research-led. Before shipping API surface, the research benchmark measured the proposed change against the existing dual-frontier on a saturated workload. Findings (3 back-to-back runs):
(Negative = priority arm finished sooner overall.) Zero starvation cost — same final state in both arms. Decision to ship was data-driven; if the numbers had been ambiguous the feature would have been deferred.
Surface
.to({ target, source?, priority? })on the resolver — static or dynamic.Store.subscribeextended with optionalpriority; max() invariant across reactions targeting the same stream.Store.prioritize(filter, n)— bulk update withQueryStreams-shaped filter (regex onstream/source, exact-match flags,blockedstate). Sets priority outright (operator override of the build-time max).app.prioritize(filter, n)— orchestrator wrapper.StreamPosition.priorityforquery_streamsintrospection.ALTER TABLE ... ADD COLUMN IF NOT EXISTS, composite index(blocked, priority DESC, at)for the lag CTE. Replaces the old(blocked, at)index.ALTER(noIF NOT EXISTSsyntax in libSQL).priority DESC, at ASCinclaim().Inviolate
Per-stream event ordering is unchanged. Priority biases which streams
claim()picks first, never the order events within a stream are processed. Within a stream, events still drain byid ASC— that's a foundational ES guarantee.When it matters
Only under saturation. With
streamLimit≥ candidate streams every cycle, every stream gets a slot every cycle and priority is irrelevant. Cold-start replays under contention is the primary scenario.Bonus — file naming consistency
Three framework source files in
libs/*/src/were PascalCase (PostgresStore.ts,SqliteStore.ts,PinoLogger.ts). Renamed to kebab-case for consistency with the rest of the framework (in-memory-store.ts,correlate-cycle.ts, etc.). React component files underpackages/keep PascalCase per the React community convention.Coverage
100% statements / branches / functions / lines workspace-wide. 1065 tests.
Docs
CLAUDE.md— new "Reaction Priority Lanes" section,Storecontract amendment.libs/act/README.md,libs/act-pg/README.mdupdated.docs/docs/architecture/priority-lanes.md(new Docusaurus page)..claude/skills/scaffold-act-app/server.md— priority lane guidance.Resolved.priority,PrioritizeFilter,Store.prioritize,Act.prioritize.libs/act-pg/PERFORMANCE.md— benchmark methodology + results.Test plan
pnpm test) — 1065/1065pnpm lint)pnpm build)pnpm -F @rotorsoft/act-pg exec vitest run --config vitest.bench.config.ts test/priority-claim.bench.ts🤖 Generated with Claude Code