Recap from chapter 1: idempotent means two runs leave the same result as one run. Chapter 1 ran a delete-insert twice. Now the first run crashes in the middle.
Think of a light switch. A toggle switch is not safe to repeat: press it twice and the light is off again. A switch you set to ON is safe: press it twice and the light is still on.
The daily job must act like "set to ON", not like "toggle".
For the daily job, "set to ON" means: replace everything for that date. This is a delete-insert of one partition. A partition is one slice of a table, here the rows of one day.
Put all four steps in one transaction (BEGIN ... COMMIT, from chapter 1). Here is the safe job:
Delete-insert of one day, in one transactionworked example
BEGIN;
DELETE FROM orders WHERE order_date = DATE '2026-01-10';
INSERT INTO orders (order_id, customer_id, status, amount, order_date)
SELECT order_id, customer_id, status, amount, order_date
FROM source_orders
WHERE order_date = DATE '2026-01-10';
DELETE FROM daily_revenue WHERE order_date = DATE '2026-01-10';
INSERT INTO daily_revenue (order_date, paid_revenue)
SELECT order_date, SUM(amount)
FROM orders
WHERE order_date = DATE '2026-01-10' AND status = 'paid'
GROUP BY order_date;
COMMIT;
Run 1 crashes before COMMIT. The scheduler retries. Someone then reruns it by hand.
| event | orders rows for 2026-01-10 | daily_revenue for 2026-01-10 |
|---|
| Before any run | no rows | no row |
| Run 1 crashes before COMMIT (rolled back) | no rows | no row |
| Retry reaches COMMIT | 1 row (102) | 25.00 |
| Manual rerun reaches COMMIT | 1 row (102) | 25.00 |
The crash left nothing behind, because the database rolled the transaction back. The retry deleted the day, loaded order 102 once, and wrote 25.00. The manual rerun changed nothing.
Without the transaction, a crash after the first DELETE leaves the day empty. Readers see no revenue until the retry finishes.
The same input every time
A retry must also read the same input. The job must not ask the clock which day to load. A retry runs later, so the clock gives a different day.
Use the logical date instead. This is the day the run is for, not the day it runs.
The scheduler passes it to the job. Compare the two filters:
Ask the clock, or use the logical dateworked example
-- Not safe: the day depends on when the job runs
WHERE order_date = CURRENT_DATE - 1
-- Safe: the scheduler passes the run's logical date
WHERE order_date = DATE '2026-01-10'
Run 1 starts on 2026-01-11 and crashes. Someone reruns it by hand on 2026-01-12.
| run | clock date | CURRENT_DATE - 1 loads | logical date loads |
|---|
| Run 1 | 2026-01-11 | 2026-01-10 (order 102) | 2026-01-10 (order 102) |
| Rerun | 2026-01-12 | 2026-01-11 (order 103) | 2026-01-10 (order 102) |
With the clock, the rerun loads the wrong day. 2026-01-10 stays unfixed.
Do not use NOW() for loaded values either. A retry gets a different time, so the rows change.