- A DuckDB or
dbt-duckdbmodel that aggregates across many days of history. - An entity-resolution batch (for example Splink) that needs a stable, immutable input.
- Ad-hoc analytical queries from a notebook that should not contend with production traffic.
How it works
A nightly job runs DuckDB with thepostgres_scanner, spatial, and httpfs extensions loaded. For each table in the allowlist it executes a COPY (SELECT * FROM pg.<table>) TO 's3://…/<table>.parquet' (FORMAT PARQUET, COMPRESSION ZSTD) against Postgres and streams the result straight into object storage. No intermediate disk staging.
Each run also writes a tiny CSV manifest at s3://<bucket>/latest/<table>.path whose single snapshot_date column points consumers at the most recent dated partition. S3-compatible stores don’t support symlinks, so the manifest serves the same purpose.
Snapshotted tables
The allowlist is intentionally narrow. Rawais_positions is never snapshotted — it is the high-volume telemetry table and its inclusion would dominate storage and I/O. For position history, query the live database (see data retention) or request a rehydration from the archive.
Output layout
snapshots/<ISO_DATE>/ partitions are immutable; the latest/ manifests are overwritten on each run.
Reading a snapshot from DuckDB
Point DuckDB’shttpfs extension at the bucket and scan the Parquet file directly:
latest/.
Storage and retention
cell_scores is the largest table at roughly 50 MB per day; the other tables are smaller. The bucket is configured with a 90-day lifecycle policy, so dated partitions older than 90 days are deleted automatically. If you need older history, copy the partition you need into a long-term archive before it expires.
Snapshots are an analytical convenience, not a backup. The live Postgres database and the AIS archive remain the systems of record. See data retention for how raw operational data is preserved.