Skip to main content
Overwatch publishes a nightly Parquet snapshot of a small allowlist of analytical tables to a DigitalOcean Spaces bucket. The live Postgres database (Supabase) remains the source of truth for application reads — these snapshots exist so batch workloads can scan flat columnar files in DuckDB instead of hammering the OLTP database. Use the snapshots when you are running:
  • A DuckDB or dbt-duckdb model 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.
For real-time reads — vessel positions, port congestion, alerts — keep using the Overwatch API and the live database.

How it works

A nightly job runs DuckDB with the postgres_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. Raw ais_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

Files are Zstandard-compressed Parquet. The snapshots/<ISO_DATE>/ partitions are immutable; the latest/ manifests are overwritten on each run.

Reading a snapshot from DuckDB

Point DuckDB’s httpfs extension at the bucket and scan the Parquet file directly:
For pinned reproducibility — for example a dbt run or an ER batch — read a specific dated partition directly instead of going through 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.

Configuration

The job is parameterized through environment variables. Postgres and Spaces credentials are required; everything else has a sensible default.