Cookbook
Scenario-oriented recipes that combine the four tools — downloader, schema-drift, normalize, and K6 load test — into end-to-end workflows. Each recipe is self-contained and copy-pasteable, aimed at data engineers running the stack in a real environment. Where a recipe overlaps with a per-tool guide, this page focuses on the glue between tools and the operational details (cron, proxies, dev bootstraps).
Nightly refresh via cron
Goal: keep the local TLC mirror caught up on new months, refresh normalized parquet, and log any schema drift.
Recipe — a shell script plus a scheduler entry.
-
Create
/srv/taxi/bin/nightly-refresh.sh: -
Wire it into cron (
crontab -e): -
Or use a systemd timer + service instead.
/etc/systemd/system/taxi-refresh.service:/etc/systemd/system/taxi-refresh.timer:Enable:
Notes:
--recent's stop-on-local semantic makes this fully idempotent — running it twice in the same day just skips everything.- If normalize amends the mapping (new drift detected), the script exits non-zero on that day. Consider paging on exit=1 vs. logging-only on exit=0.
raw/grows monotonically — plan capacity via the Downloader guide's sizing table.- Prefer systemd timers over cron on modern hosts:
Persistent=truecatches up missed runs after downtime, and logs land in the journal automatically.
DuckDB httpfs — no local mirror
Goal: run one-off analytics queries against TLC parquet without downloading anything.
Recipe:
-
Start DuckDB:
-
Load the
httpfsextension and query directly:
Notes:
- First query downloads the file; subsequent queries in the same session hit DuckDB's in-memory cache. Across sessions, use the on-disk cache:
SET enable_external_file_cache=true; PRAGMA cache_directory='/tmp/duckdb-cache'; - Costs: TLC's CloudFront is unmetered from the requester side, but repeated full-scans of a 50 MB file are wasteful. Use the downloader for anything you'll query more than a couple of times.
- Schema drift is on you here — DuckDB will happily union files with different schemas via
union_by_name=true, but drops columns silently. Cross-reference withschema-driftoutput. - The glob pattern hits CloudFront's directory listing, which requires HTTP range requests — some corporate proxies mangle these. If globs fail, list the URLs explicitly.
Side-by-side comparison of multiple TLC years
Goal: compute trips-per-year and average fare across a decade of yellow taxi data using the normalized mirror.
Recipe: with a full history mirror at raw-normalized/yellow/:
-
Start DuckDB in the taxi directory:
-
Aggregate across every year:
Notes:
- Uses
raw-normalized/(post-normalize output), notraw/— schemas are uniform, no need to reason about pre-2015pickup_latitudevs post-2015PULocationID. - Pre-2015 rows have NULL for
PULocationID(they were represented as lat/lon in the raw and dropped during normalize per the mapping'sacknowledged_data_loss:entry). TheWHERE PULocationID IS NOT NULLhandles that. - ~30 GB of parquet queried in one DuckDB command takes seconds — parquet + DuckDB scale surprisingly well on a laptop with plenty of RAM.
- If you're memory-bound, add
SET memory_limit='8GB'; SET threads=4;before running — DuckDB will spill to disk rather than OOM.
Load-testing the normalizer's output
Goal: stress-test SQL Server with normalized parquet instead of raw — closer to your production data path.
Recipe:
-
Edit
k6-loadtest/config.yamlto point atraw-normalized/: -
Preprocess parquet into K6 fixtures:
-
Apply DDL and run K6:
Notes:
- Load-testing normalized data is more representative if your production pipeline normalizes before loading. If you load raw parquet directly and never normalize, load-test with
raw/paths instead. max_rows: 500000caps the fixture size; K6 will iterate through it. Set based on how long you want the test to run and your available VU-hours.- SQL Server bulk-insert throughput is often bounded by the client-side JSON parsing + network before the server itself. Watch
sql_query_durationvs totaliteration_durationto see where time goes. - If throughput plateaus, try lowering
vus— SQL Server's lock contention on a single table can make more concurrency slower, not faster.
Running behind a corporate proxy
Goal: get the downloader, uv, and gh to work behind a corporate HTTP proxy at http://proxy.corp.internal:3128.
Recipe:
-
Export the standard proxy env vars once per shell:
-
Then everything just works:
-
Persist across sessions by adding to
~/.bashrc/~/.zshrc:
Notes:
- If your proxy requires authentication:
http://user:pass@proxy.corp.internal:3128. Prefer a keychain-backed helper over hardcoding. - Some corporate proxies MITM HTTPS with a self-signed CA. Point curl at the CA bundle via
CURL_CA_BUNDLE=/path/to/corp-ca.pem; python viaSSL_CERT_FILE; uv readsUV_NATIVE_TLS=truefor OS trust store on Windows/macOS. NO_PROXY=localhostmatters if you're running SQL Server locally — you don't want K6 hitting the proxy forlocalhost:1433.- Docker's daemon has its own proxy configuration (
/etc/systemd/system/docker.service.d/http-proxy.conf) — env vars in your shell don't affect image pulls.
Populating a fresh dev SQL Server
Goal: go from an empty Docker-hosted SQL Server to a populated yellow_trips table in ~30 minutes.
Recipe:
-
Start SQL Server (from the K6 Load Test guide):
-
Fetch three months of yellow (small enough to complete in a couple of minutes):
-
Normalize (first-run bootstrap + human ack pass omitted for brevity — on a stable-schema
--recent 3window it's typically a no-op): -
Configure
k6-preprocessto load normalized parquet as a one-shot bulk load. Editk6-loadtest/config.yaml: -
Preprocess, apply DDL, run:
-
Verify the load:
Notes:
iterations: -1means "keep iterating until the fixture is exhausted"; withvus: 4, four connections load in parallel.- SQL Server's default
masterdatabase is fine for a dev target. For anything real,CREATE DATABASE taxiand put the table there. - Total time is dominated by network to CloudFront (parquet download) then network to SQL Server (bulk insert). On a residential gigabit link, ~30 minutes for
--recent 3 yellow. - If you're iterating on schema and want to reset without losing the container:
sqlcmd ... -Q "DROP TABLE yellow_trips;"then re-apply the DDL — much faster than tearing down the volume.