Review

HA birch-sap ledger: multi-replica API on NFS SQLite + WAL

Sapline co-op records gallons pulled from ridgeline taps during sugaring season. Ops wants the FastAPI ledger HA-ready for the March surge without standing up Postgres. This PR points all replicas at a single SQLite file on the NFS PVC, turns on WAL + busy_timeout for 'concurrent readers across pods', and flips check_same_thread=False so uvicorn workers can share the connection factory.

SQLiteTier 5sqlitewalnfsmulti-instance

Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.

sapline/db.py+11-3
11import sqlite3
22from contextlib import contextmanager
33from pathlib import Path
44
55from sapline.config import settings
66
7-# Single-node: local disk under the API pod.
8-DB_PATH = Path("/var/lib/sapline/ledger.db")
7+# Shared ledger for every replica — mount is RWX NFS (EFS) so pods see one file.
8+DB_PATH = Path("/mnt/nfs/sapline/ledger.db")
99
1010
1111def connect() -> sqlite3.Connection:
12- conn = sqlite3.connect(DB_PATH)
12+ # check_same_thread=False: uvicorn workers reuse this factory across threads.
13+ conn = sqlite3.connect(
14+ DB_PATH,
15+ check_same_thread=False,
16+ timeout=30.0, # busy_timeout via connect timeout
17+ )
1318 conn.row_factory = sqlite3.Row
19+ # WAL lets multiple pods read while one writes — no more SQLITE_BUSY storms.
20+ conn.execute("PRAGMA journal_mode=WAL")
21+ conn.execute("PRAGMA synchronous=NORMAL")
1422 return conn
1523
1624
1725@contextmanager
1826def session():
1927 conn = connect()
2028 try:
2129 yield conn
2230 conn.commit()
2331 except Exception:
2432 conn.rollback()
2533 raise
2634 finally:
2735 conn.close()