Batches, locks and idempotency windows. No solutions.md before submitting.
Exercise 1 — The catalog and its owner
- Write TicketFlow's complete
beat_schedule(the lesson's table) in the repo. Comment each entry with its business window and its idempotency mechanism. - Hunt orphan crontabs:
crontab -lon your dev/server and in the containers (a Dockerfile withCRONis a common sin). Document what you find and migrate what is yours into the repo. - Decide the TZ: which two tasks of your catalog depend on the TZ, and how do you make them TZ-immune (scan window)?
Exercise 2 — The batch
- Implement
liquidar_mes(mes)with the full pattern: iterator + 500 batch + commit per batch + sleep. Test with 1200 fake orgs: verify 3 commits (a transaction.atomic mock or the fake's counter) and that the process doesn't load 1200 orgs into memory (measurelen(qs)vs the cursor: explain the difference). - Add the checkpoint: if the run dies at org #843, the resume continues from there (
id > ultimo). Test: run it "halfway" (an exception on the 3rd), relaunch, and verify entries are NOT duplicated. - Measure the time with 10k fake rows and count the queries (
CaptureQueriesContext): how many queries PER batch? How many would there be withoutvalues_list+iterator?
Exercise 3 — Window idempotency
- Add the
UniqueConstraint (org, mes)to the ledger with its migration (11: expand-contract if you already have data). Test:liquidar_mes("2027-03")twice → same number of rows, zero duplicates. - The 24h reminder: implement with the key
(reserva, kind, fecha_local_evento)and Redis dedup. Test with FakeClock: run the task 4 times on the same day → ONE email per reservation.
Exercise 4 — Run locks
- Implement
with_run_lock(Redis) and apply it toliquidar_mesand the daily closing. Test: with the lock taken (cache.add fails), the task returns None with a "skip" log and does NOT raise. - Repeat with a Postgres advisory lock (
pg_try_advisory_lock) in a management command. Which do you prefer in your deployment (43) and why? Write it in the 5-line ADR (48). - Scenario: the lock's TTL is 4 h and the run takes 5 h. What exactly happens (who gets in, what gets corrupted)? Propose the fix (a heartbeat renewing the lock).
Exercise 5 — The event closing
- Implement
cerrar_eventos_pasados(): events withends_at < now→ state CLOSED +EventClosedto the outbox (25) + releasing orphan seats (ACTIVE reservations of closed events → EXPIRED). All in batches. - Integral test with FakeClock: yesterday's event with 2 reservations (1 ACTIVE, 1 CONFIRMED) → ACTIVE→EXPIRED, CONFIRMED untouched, outbox with 1
EventClosed+ 1ReservationExpired, and a second run changes NOTHING (idempotent).
Submit
Paste the beat_schedule, the batch test with checkpoint, the two locks compared and the closing's integral test. Next: Lesson 32 — Eventual consistency and sagas.