[READ-ONLY] Mirror of https://github.com/flo-bit/contrail. atproto backend in a bottle flo-bit.dev/contrail

perf(appview): skip feed-prune sweep on idle ingest ticks master

The feed-items prune ran on every ingest tick (cron) and every sweep interval (persistent), walking every actor to issue a cutoff DELETE that almost always deleted nothing. On an operated deployment this no-op sweep was the #1 D1 query by count and runtime (~825k/day, reading ~6.3M rows to delete ~0). It is not an indexing problem; the cost is running the sweep too often. A feed can only exceed its cap right after a feed-mutating record is applied (an event fanning out to followers, or a follow backfilling a feed). Gate the sweep on that: - Cron (runIngestCycle): sweep only when the tick ingested a feed-mutating collection, or a 6h recovery interval has elapsed. The recovery clock is persisted in _contrail_meta since the isolate recycles each tick. - Persistent (streamAndFlush): a state.feedDirty flag is set when a flushed batch contains a feed-mutating record; the sweep is gated on feedDirty or the recovery interval. - getFeedMutatingNsids(config) derives the feed-mutating NSID set from the configured feed targets and follow collections, so the gate cannot drift from what actually writes feed_items. - Recovery completes a full pass per interval (tracking the last completed pass, not a single slice) and runs on no-op notify calls, so over-cap rows from a lowered cap or bulk import still drain with no live activity. Idle deployments drop from a sweep every tick (~1,440/day on a 1-min cron) to ~4/day; correctness is unchanged (feeds are caches, and the recovery interval bounds how long a feed can stay briefly over cap).


+326 -63
8 changed files