- Status: Accepted
- Date: 2026-07-08
Phase 3 needed an orchestrator and a daily incremental layer. Two decisions had a "do the impressive thing" option and a "do the honest thing" option, and this project's whole thesis is that the honest thing, documented, is worth more.
airflow standalone in a single container (scheduler + webserver + SQLite metadata DB),
one docker-compose.yml service. Three small DAGs for a single-user pipeline do not need
CeleryExecutor, a Postgres metadata DB, or worker autoscaling.
- Why not the full fleet: it would look more "production" in a screenshot, but it adds moving parts that this workload never exercises — the same sizing-theatre the Spark decision (ADR-0002) rejects. The README's limitations section states exactly what would change under real concurrency (LocalExecutor/Celery on Postgres, secrets backend, real on-call webhook), which is more valuable than pretending to have built it.
- dbt in an isolated venv inside the image (
/home/airflow/dbt-venv): dbt-core and Airflow conflict on shared transitive deps; isolating dbt is the standard, reliable fix and mirrors how teams actually run dbt under Airflow.
ingestion/daily_api_ingest.py uses requests +
snowflake-connector executemany — no Spark, no dbt-for-ingestion. A day's snapshot is
~800 dock rows + ~20 line rows. The loader:
- lands raw JSON as bronze before any parsing (replayable),
- enforces ingestion quality gates (partial API response → fail loudly, don't load a half-empty snapshot),
- is idempotent via delete+insert per
snapshot_date(safe reruns/backfills).
This is the deliberate counterweight to the Spark backfill: the README's honesty-boundary section only lands because both halves exist in the same repo and the reasoning for each is symmetric — the data size dictates the tool, in both directions.
- Failure alerting is shared across the real local operational DAGs via one
on_failure_callback(dags/alert_utils.py): daily ingest, the dbt gate, and the monthly archive-drift check. Its critical task-log event is the local proof; a webhook remains opt-in and is not exercised by this project. monthly_history_checkre-lists the bulk bucket and fails if new journey files appear — turning "extend the backfill" into an automated nudge rather than a memory.- The whole orchestrated daily run (loads + full dbt build + 48 tests) costs
0.15 credits; cumulative build cost ≈0.50 credits ($1) of the $400 trial.