The script
set -euo pipefail
trap 'rm -f /tmp/ynab_response_*.json' EXIT
cursor=$(psql ... "SELECT server_knowledge FROM ynab.sync_state WHERE endpoint='budget_detail'")
fetch budget_detail with last_knowledge_of_server=$cursor # 429 -> sleep 60, retry
INSERT INTO ynab_raw.api_responses ...
SELECT ynab.apply_response(:id);
INSERT INTO ynab.sync_runs (mode, status, rows_upserted) ...
SELECT COUNT(*) FROM ynab.v_balance_check WHERE ABS(drift) > 0.01;
Running it
- A systemd service with an
EnvironmentFileat/etc/ynab-sync/env.conf, mode 600. The token never appears in the process list or the logs. - A systemd timer, hourly. The first run with a cursor of zero is the backfill and takes a few minutes.
- Every run writes a row to
sync_runswith status, rows, and any error.
The outage
Transaction ids were typed as uuid. The documentation calls them ids; most of them look like uuids. Then the first scheduled transaction occurred, and its ledger row carried an id shaped like <uuid>_<date>. The cast failed, the transaction rolled back, and every hourly run failed the same way for four days.
Skipping those rows was not an option either. One skipped occurrence produced exactly 30.59 dollars of drift on checking. Thirty dollars is a small amount to lose and a large amount to be wrong about. The balance check view caught it. A migration retyped the id columns as text, rebuilt the dependent views, and the replay from raw landed the missing rows.
Free to borrow for your feed · Square edition
Where you are: a pipeline that runs and checks its own math. Next, you finally get to look at it.
