The Power Can Fail Between Your Two Statements
Taking money out of one account and putting it into another is two writes, and a crash between them is a real event. Here is the mechanism that makes it one.
This course is about what happens when two transactions run at the same time,
which is the hard and interesting problem. This first lesson is about the part
everybody already half knows, done properly, because the mechanism underneath
it is the one the rest of the course keeps using.
What the boundary is
A transaction is a boundary you draw around a group of changes. Inside the
boundary you make as many changes as you like. At the end you either commit,
and every change becomes visible at the same instant, or you roll back, and
none of them ever existed as far as anyone else is concerned.
That is the whole promise when nothing else is running. Not speed, not locking,
not consistency of your business rules. Simply that there is no observable
middle.
The canonical example earns its place. Moving fifty pounds between two accounts
is two writes: subtract from one, add to the other. There is a moment between
them when the money is nowhere. If nothing can observe that moment and nothing
can make it permanent, the pair of writes is a transfer. If either of those
fails, it is a way of losing money.
| step | moment | account A | account B | what exists on disk | what happened |
|---|---|---|---|---|---|
| 1 | before | 500 | 200 | both rows as shown | The starting state, and the only invariant that matters is that the two balances sum to 700. |
| 2 | after the first write | 450 | 200 | a log record saying A goes to 450 | The sum is now 650. This state is wrong, and it is wrong for a few microseconds under normal operation, which is fine as long as it cannot be seen or kept. |
| 3 | power fails here | 450 | 200 | the same log record, nothing else | The machine is off. Nothing in the two numbers indicates a problem: 450 and 200 are perfectly valid balances and no later inspection of the data can tell that fifty pounds is missing. |
| 4 | after restart | 500 | 200 | log replayed, no commit record found | Recovery reads the log, finds a started transaction with no commit record, and undoes it. The state is the one from before the transfer began. |
| 5 | after a successful rerun | 450 | 250 | both writes and a commit record | The sum is 700 again. Either outcome is acceptable; the one in the middle row is not. |
The crash in the middle
It is worth being specific about what can interrupt the pair of writes, because
people often picture only one of these.
The process can be killed. The machine can lose power. The disk can accept a
write into its own cache and lose it. The network can disappear between the
application and the database after the first statement. The application can
throw an exception on the line between them. The container can be evicted.
All of these produce the same situation: some of the changes reached storage
and some did not, and nothing in the stored data distinguishes that from a
legitimate state.
Writing it down first
The mechanism is a log and a single rule.
Before any change is written to the page where the data lives, a record
describing that change is appended to a sequential log, and that log record is
made durable. This is the write-ahead rule, and the ordering is the whole of
it: log first, data afterwards.
Recovery then has a simple job. Read the log from the last checkpoint. Any
transaction with a commit record gets its changes reapplied, because they may
not have reached the data pages. Any transaction without one gets its changes
undone, because the caller was never told it succeeded. The presence or absence
of one small record decides which.
| changes exist in memory | log records on disk | commit record on disk | data pages written | |
|---|---|---|---|---|
| before the log was flush | 1 | 0 | 0 | 0 |
| after the log, before th | 1 | 1 | 0 | 0 |
| after the commit record, | 1 | 1 | 1 | 0 |
| after everything | 1 | 1 | 1 | 1 |
What the promise costs
There is one physical cost in the whole mechanism, and it is at the flush.
A commit cannot be reported to the caller until the log is actually on the
device. Not in the operating system's cache, not in the drive's own cache, but
on durable media. That operation is a disk sync, and it is slow in a way that
is unrelated to the amount of data: a few hundred microseconds on good flash,
several milliseconds on a spinning disk.
- the time for one physical sync, which depends on the hardware and not on how much is being written
- the number of transactions whose commit records ride along in a single sync
- commits per second, which with n equal to one is simply the inverse of the sync time
Two practical consequences follow from that figure, and both show up in real
systems.
Many small transactions are far more expensive than one larger one covering the
same work, because each pays its own sync. A loop that commits once per row is
the most common cause of a load that takes hours instead of minutes, and the
fix is to commit once per few thousand rows.
And every database offers a setting that relaxes the sync, reporting a commit
once the log record is in the operating system's cache rather than on the
device. It is genuinely faster and it genuinely gives up durability: a power
failure loses the last fraction of a second of committed transactions. That is
an acceptable trade for some workloads and a catastrophe for others, and the
thing to avoid is having made the trade without knowing it.
The next lesson drops the assumption that nothing else is running, which is
where the subject actually begins.
Recap
- A transaction is a boundary you draw around several changes, and the only promise it makes alone is that the outside world sees all of them or none of them.
- The mechanism is writing the intention to a log before touching the data, so that a crash leaves a record from which the work can be finished or undone.
- Durability costs one physical disk sync per commit, which is why commit rate rather than write rate is often what limits a busy system.
This is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents