···11----
22-"@atmo-dev/contrail-appview": minor
33----
44-55-fix(feeds): make feed_items pruning bounded so it can't reset the D1 DO
66-77-The hourly feed prune ran a single global `ROW_NUMBER() OVER (PARTITION BY actor)`
88-window + `(actor, uri) NOT IN (...)` anti-join across the entire `feed_items`
99-table — O(n) CPU in one statement. Once the table grew large this exceeded D1's
1010-per-query CPU limit and reset the shared Durable Object, taking down any
1111-concurrent read on the same SQLite instance (unrelated user requests 500'd with
1212-`was reset` / `Network connection lost`). Because the statement reset before
1313-completing, caps were never enforced, the table kept growing, and the prune got
1414-more expensive — a death spiral.
1515-1616-Changes:
1717-1818-- **Bounded per-actor prune.** Pruning is now an index-backed cutoff delete per
1919- `(actor, collection)` using `idx_feed_actor_coll_time`, cost O(cap), never
2020- O(table). New `pruneActorFeed` / `sweepFeedItems` exports; the ingest loops
2121- run one bounded `sweepFeedItems` slice per tick (`FEED_PRUNE_SWEEP_ACTORS`
2222- actors), which also serves as recovery for already-bloated tables.
2323-- **Persisted prune cursor.** A new `feed_prune_cursor` row tracks the rolling
2424- sweep position, so progress survives the cron isolate recycling that
2525- previously made the in-memory hourly gate a no-op (it pruned on essentially
2626- every tick). The time gate is removed from the cron path; the long-lived
2727- persistent loop keeps a short in-memory throttle.
2828-- **API:** `pruneFeedItems(db, caps)` now accepts only the per-collection
2929- `Map<collection, cap>` (the legacy global-number form is removed) and is
3030- reimplemented as a bounded full-table recovery loop — keep it off the hot
3131- path.
3232-3333-The follow fan-out's `subject` lookup is already covered by `idx_<follow>_subject`,
3434-so no unbounded statement remains in the ingest path.
+41
packages/contrail-appview/CHANGELOG.md
···11# @atmo-dev/contrail-appview
2233+## 0.9.0
44+55+### Minor Changes
66+77+- 8f0b87e: fix(feeds): make feed_items pruning bounded so it can't reset the D1 DO
88+99+ The hourly feed prune ran a single global `ROW_NUMBER() OVER (PARTITION BY actor)`
1010+ window + `(actor, uri) NOT IN (...)` anti-join across the entire `feed_items`
1111+ table — O(n) CPU in one statement. Once the table grew large this exceeded D1's
1212+ per-query CPU limit and reset the shared Durable Object, taking down any
1313+ concurrent read on the same SQLite instance (unrelated user requests 500'd with
1414+ `was reset` / `Network connection lost`). Because the statement reset before
1515+ completing, caps were never enforced, the table kept growing, and the prune got
1616+ more expensive — a death spiral.
1717+1818+ Changes:
1919+2020+ - **Bounded per-actor prune.** Pruning is now an index-backed cutoff delete per
2121+ `(actor, collection)` using `idx_feed_actor_coll_time`, cost O(cap), never
2222+ O(table). New `pruneActorFeed` / `sweepFeedItems` exports; the ingest loops
2323+ run one bounded `sweepFeedItems` slice per tick (`FEED_PRUNE_SWEEP_ACTORS`
2424+ actors), which also serves as recovery for already-bloated tables.
2525+ - **Persisted prune cursor.** A new `feed_prune_cursor` row tracks the rolling
2626+ sweep position, so progress survives the cron isolate recycling that
2727+ previously made the in-memory hourly gate a no-op (it pruned on essentially
2828+ every tick). The time gate is removed from the cron path; the long-lived
2929+ persistent loop keeps a short in-memory throttle.
3030+ - **API:** `pruneFeedItems(db, caps)` now accepts only the per-collection
3131+ `Map<collection, cap>` (the legacy global-number form is removed) and is
3232+ reimplemented as a bounded full-table recovery loop — keep it off the hot
3333+ path.
3434+3535+ The follow fan-out's `subject` lookup is already covered by `idx_<follow>_subject`,
3636+ so no unbounded statement remains in the ingest path.
3737+3838+### Patch Changes
3939+4040+- @atmo-dev/contrail-base@0.9.0
4141+- @atmo-dev/contrail-authority@0.9.0
4242+- @atmo-dev/contrail-record-host@0.9.0
4343+344## 0.8.0
445546### Minor Changes