Snapshots
A Snapshot is a schema of its own inside the tenant's database, so it takes disk space and, while one is being prepared or cloned, vacuum progress. This page covers how a Snapshot behaves from creation to deletion and what it costs the tenant database. The Snapshots guide covers using them, and Upgrades covers how Snapshots are upgraded. The members a Snapshot body may carry are under Snapshots and EntityMaps in Strictness, and the q that Purge Snapshots accepts is in the Query language.
Lifecycle
A created or cloned Snapshot answers 201 in status preparing and is prepared in the background. It ends in one of four terminal statuses:
success: every query ran and the results are captured.partial: some queries captured data and at least one failed. The Snapshot is usable with what was captured.empty: every query ran and none matched. The Snapshot scopes to an empty dataset.failure: preparation failed. The Snapshot publishes an empty dataset, so it stays addressable.
A preparation captures into a staging copy, which is published, together with the terminal status, in one transaction. A reader sees either no content or all of it.
A Snapshot with an endpoint gets a status notification when it reaches a terminal status and when its metadata is updated. Notifications are delivered from a durable queue under the same retry policy as subscription notifications ([notifications.delivery]). A retryable failure is retried until notifications.delivery.max_attempts attempts have been made, and a terminal failure is not retried.
Preparation
Creating or cloning starts preparing at once. That first pass claims the new Snapshot by its identifier and waits out a brief hold on its row, such as the client reading the Snapshot it just created, rather than passing over it as the scheduled passes do, so that concurrent workers each take a different Snapshot. A Snapshot it cannot claim, because a worker already holds it or because it clones a Snapshot still preparing, is logged and left to its holder, to the pass that ends its source, or to the scheduled sweep.
- At most
snapshot.preparation.max_concurrent(default 4) Snapshots of one tenant are prepared at once. HighersnapshotPrioritygoes first, then the oldest. - A worker holds a preparation under a lease of
snapshot.preparation.lease_timeout(default 30 s) and renews it while it works. Another worker takes over a preparation whose lease lapsed. - A sweep every
snapshot.preparation.recovery_interval(default 30 s) resumes preparations that a crash or a missed run left unfinished. - A clone is not prepared while its source is still
preparing. It starts when the source ends. - Captured queries are read
snapshot.preparation.page_size(default 250) results per page.
Scoped requests
A request scoped to a preparing Snapshot waits for its terminal status for up to snapshot.preparation.scoped_operation_timeout (default 20 s). If the Snapshot is still preparing then, the request is answered 503 with Retry-After: 1. A quarantined Snapshot is refused with InternalError, see Upgrades.
Lifetime and expiry
- A Snapshot created without
snapshotLifetimelivessnapshot.default_lifetime(default 1 h). - A longer lifetime than
snapshot.max_lifetime(default 30 days) is cut down to it, not refused. A lifetime that is not positive is refused. - Patching
snapshotLifetimerestarts the lifetime from the time of the patch. - A clone takes its own
snapshotLifetime, or else its source's, counted from the clone's creation. - An expired Snapshot is never served. A read of it answers
404and deletes it, and the sweep everyexpiry.sweep_interval(default 30 s) deletes the rest.
Priority and last use
snapshotPriority is an integer from 1 to 10, default 5, and can be patched. Retrieving a Snapshot and every request scoped to it record its last use. Priority and last use decide which Snapshot a tenant loses when it goes over its quota.
Quotas
snapshot.quota.max_per_tenant bounds how many Snapshots a tenant keeps, and snapshot.quota.max_storage_per_tenant bounds how much disk they take. Both default to "unlimited". The storage figure is the on-disk size of every table of every Snapshot schema, published and staging, with indexes and TOAST. Both quotas count preparing Snapshots.
The quotas are checked after every create and clone, and after every preparation ends, since a preparation grows the storage. While a tenant is over either quota, Snapshots are deleted one at a time in this order:
- lowest
snapshotPriority - earliest expiry
- oldest last use, a Snapshot never used counting as the oldest
- smallest
id
The Snapshot just created is a candidate like any other, and so is one still preparing. A tenant that creates a Snapshot of the lowest priority over a full quota can therefore lose that Snapshot at once. Each eviction logs Snapshot evicted by tenant resource quota with the Snapshot's id.
A Snapshot being in use never changes the order. An eviction is a deletion, so when a clone is copying into or out of the Snapshot first in line, its copy is ended, as Cloning a Snapshot describes. Any other request holding that Snapshot is waited for, for up to 5 seconds, and the order is read again once the request ends. A check that fails is logged as Snapshot quota enforcement after a create or clone failed or Snapshot quota enforcement after preparation failed. The create, clone or preparation that ran the check still succeeds, and the next one checks again.
Cloning a Snapshot
A clone copies its source as of one moment. It takes the source's queries, query details, priority, endpoint and receiverInfo unless the clone request gives its own. A quarantined source is refused.
The copy is one REPEATABLE READ transaction. Every copy statement reads the snapshot taken by the transaction's first statement, so the copied entities, history and discovery counts agree with each other while writers on the source carry on. That is why the copy can't be split into smaller transactions. The copy takes as long as the source is large, and no statement timeout bounds it. It is prepared in the background.
Both Snapshots stay in place while the copy runs. Deleting or evicting either one ends the copy's database session first, so the deletion doesn't wait for the copy to finish. An expiry is different and waits for the copy. A deleted clone is gone. A clone whose source was deleted ends failure. The copy's session is identified by its process id together with the start of its transaction, so a pooled session that has moved on to other work is never ended.
To PostgreSQL, a REPEATABLE READ transaction is a client transaction, and it keeps the oldest row versions it could still see. The vacuum horizon of the tenant database can't pass them until the clone commits or fails, so no vacuum in that database can remove rows that died after the clone started, live tables included. Other tenant databases are not affected.
A clone of a large Snapshot on a busy tenant therefore shows the same symptoms as any long transaction. Dead space in current_attributes, entities and the work queues grows, and a catch-up vacuum finishes without reclaiming it.
What maintenance does about it
The maintenance controller reads the holders of the vacuum horizon before a vacuum, and a running clone is among them as a client backend.
- A clone that has been open for more than 30 seconds defers expensive maintenance work on that tenant database, the same as any other transaction open that long. The deferral is logged with its reason, and the work runs on a later pass once the clone has finished.
- A vacuum that runs anyway and leaves the table still past its dead-space threshold is logged as
ineffective, and the log lists every holder with its age. The clone appears there. - When transaction ID wraparound pressure is
overdueorcritical, the visit logs every holder of the horizon in any database, and the clone appears there too.
Maintenance describes the gates, and Maintenance describes the wraparound report.
Deletion
A Snapshot is deleted by its id, by Purge Snapshots, by expiry or by eviction. Every route drops its schema and its record in one transaction, so a Snapshot is either whole or gone.
Background work that reads every Snapshot's schema can meet a schema dropped after it listed the Snapshots. It lists the Snapshots again for as long as such a failure is explained by a Snapshot gone since, and a notification whose Snapshot's schema is gone is treated like one whose subscription was deleted.