Building a Self-Managing MBTA On-Time-Performance Lakehouse (Databricks + GCP)
Jun 23, 2026
Project Overview
This project answers a deceptively simple question — "is the MBTA late, and where?" — with a fully automated data lakehouse on Databricks + Google Cloud. Every couple of minutes it captures Boston's live transit feed, compares actual arrivals to the timetable, and publishes an on-time-performance (OTP) scoreboard. The twist: the system runs, heals, and improves itself, and an agentic layer writes its own nightly insights and opens pull requests with proposals.
The goal was to build one deep, end-to-end, production-shaped project rather than a pile of toy demos — something that survives interview scrutiny.
Key Concepts
- Medallion architecture: data flows Bronze (raw) → Silver (cleaned: how late is each stop?) → Gold (business-ready: the OTP marts), each layer with a clear job.
- OTP is a product decision: "on time" isn't given — it's a tunable band (e.g. −1 to +5 min). The hardest logic is time reconciliation: real-time arrivals are absolute UTC instants, the schedule is local time-of-day that can exceed 24:00:00 (after-midnight service). Getting that wrong silently corrupts every number — so it's covered by Spark integration tests.
- Self-managing loop (agentic): a nightly "Dreamer" reads the gold marts, narrates what's notable via an LLM, learns a drift-guarded baseline, and opens a CI-gated pull request with proposed new metrics. A failure-monitor watches the hourly job and either auto-retries it or files a deduplicated issue. Tiered autonomy: safe fixes are automatic; anything consequential is proposed for a human to merge.
- Everything is code: the cloud is Terraform; the Databricks job is a Databricks Asset Bundle; the shared transform logic ships as a tested wheel the notebooks import (no drift).
Architecture
MBTA GTFS-Realtime → Cloud Run poller (2 min) → GCS
GCS → Cloud Run copier (15 min) → Databricks Volume
Volume → Medallion job (hourly): Bronze → Silver → Gold (OTP)
Gold → AI/BI dashboard + nightly Dreamer (insights → PR)
Failure-monitor (30 min): auto-retry / auto-issue
Two infrastructure-as-code layers, each native to its platform: Terraform owns GCP (storage,
Cloud Run, schedulers, secrets, IAM); Asset Bundles own Databricks (the job + notebooks +
wheel). CI runs lint, unit + Spark integration tests, and gates terraform plan → apply using
keyless Workload Identity Federation (GitHub mints an OIDC token, GCP exchanges it for a
short-lived credential — no service-account keys stored anywhere).
Engineering Practices Worth Calling Out
- Tested transforms, not tested copies. The tricky silver/gold logic lives in one library, exercised on a real local Spark session (including the after-midnight wrap), and packaged as a dep-free wheel that the production notebooks import. The code that's tested is the code that runs.
- Dev → prod discipline. Changes are validated in an isolated bundle target before the live pipeline is touched — which paid off when a memory bug surfaced under real data volume.
- The system caught its own bug. When ingestion hit an out-of-memory error at scale, the failure-monitor detected it automatically — exactly what a self-managing layer is for.
- Cost-aware by design: Databricks Free Edition + GCP free credits, serverless throughout.
What's Next: Streaming
Today the pipeline re-reads a bounded window of recent files each hour. The next step is true
incremental ingestion using Structured Streaming + Auto Loader with Trigger.AvailableNow —
a streaming query that wakes on schedule, processes only new files (tracked by a checkpoint), and
stops. It keeps full history without the per-run work growing, and demonstrates real streaming
within free-tier limits (no always-on cluster required). Built dev-first, then cut over in one
reviewed change.
Takeaway
A modern lakehouse isn't just pipelines — it's reproducible infrastructure, tested logic, CI/CD, and increasingly an agentic layer that operates and improves the system. This project is my hands-on demonstration of all of it, end to end.