Collection retention
A collection can be told to keep only its most recent rows. Retention has two layers: the author default, declared in the manifest and shipped with the app, and the owner override, set at runtime per collection without touching the manifest. What is actually enforced is the effective retention, computed per axis.
The two axes
Section titled “The two axes”Retention has two independent axes, either or both of which may be set:
maxRows: keep only the newest N live rows in the collection.maxAgeDays: keep only rows younger than N days (by creation time).
When both are set, a row is kept only if it satisfies both; a row over either bound is pruned.
Author default (manifest)
Section titled “Author default (manifest)”The author declares a collection’s default retention in the manifest under
collections.<name>.retention. See the manifest reference for the
exact retention key shape and its validation (for example, maxRows may not exceed the platform
row cap). These defaults ship with the app and are re-materialized on every deploy.
Owner override (runtime)
Section titled “Owner override (runtime)”The app owner can tighten or loosen retention on any collection at runtime, per axis, without a redeploy, through the owner surface:
# Show the effective retention nowhomespun data <app> <collection> retention --show
# Keep only the newest 500 rows (sets the maxRows override)homespun data <app> <collection> retention --max-rows 500
# Revert the rows axis to whatever the manifest declareshomespun data <app> <collection> retention --clear-rowsThe same control is available over HTTP as GET and PATCH /v1/apps/:id/collections/:name/retention (owner cookie or owning-agent key), and as
getCollectionRetention / setCollectionRetention in the SDK.
Effective retention
Section titled “Effective retention”Effective retention is computed per axis as override ?? authorDefault: the owner override
wins on its axis when set, otherwise the author default applies. Clearing an override on an axis
(sending null) reverts that axis to the author default. There is deliberately no way to express
“no retention” through an override, so a collection an ingest hook writes into keeps its
author-required bound no matter what the owner does.
Effective maxRows is capped at the account’s MAX_ROWS_PER_APP: an override above the platform’s
own row cap could never take effect, so it is rejected. The override is owner-owned: a manifest
redeploy rewrites the author default but never the override, so an owner’s setting survives every
redeploy.
The read and write responses report the effective bounds, the authorDefault, the raw
override, and wouldPrune: how many existing live rows the effective bounds would prune on the
next sweep, so a tightening’s impact is visible before it happens.
The sweeper
Section titled “The sweeper”A background sweeper enforces effective retention on a slow cadence. Its behaviour:
- Feed-silent hard delete. Over-retention rows are hard-deleted with no tombstone and no feed entry, so pruning does not double the on-disk bytes or spam every watcher. A watching client is told to resync instead: each pass that prunes for an app stamps a prune watermark and broadcasts a resync once, so a stale cursor is caught and re-snapshots.
- Seed rows are exempt. A seeded starter row (author kind
system) ships with the app and is never counted or pruned; retention bounds user-generated growth only. - Batched. At most a bounded number of rows per collection per pass, so a large first prune is spread over passes rather than done in one statement.
- Meters released. The freed rows and bytes are decremented from the app and account usage counters as each batch is deleted.
- Detached collections are skipped. A collection removed from the manifest (detached) is left alone, which is why an override on a detached collection is rejected.