Checkpoints¶
Tidying the Diary Into the Photo Album¶
If you wrote every card swap in your diary for a whole year, rebuilding your collection after a sleepy morning would take ages -- you'd have to redo every swap since January! So every now and then you:
- Take a photo of the whole collection as it is right now.
- Stick it in your photo album, with a note: "this photo includes everything up to diary entry 5".
- Start a fresh diary page.
Next time you wake up confused, you start from the latest photo and only redo the few swaps written since. That's a checkpoint.
Try It¶
import json
import tempfile
from pathlib import Path
from pydb.database import Database
from pydb.schema import Column, Schema
from pydb.types import DataType
with tempfile.TemporaryDirectory() as tmp:
folder = Path(tmp) / "cards"
db = Database(folder, journal=True)
cards = db.create_table("cards", Schema(columns=[Column("name", DataType.TEXT)]))
for name in ["Pikachu", "Eevee", "Snorlax", "Mew"]:
cards.insert({"name": name})
journal = folder / "journal.log"
print("diary before:", journal.stat().st_size, "bytes")
db.checkpoint() # save() does the same on a journalled database
print("diary after: ", journal.read_text().strip())
print("album: ", sorted(p.name for p in (folder / "snapshots").iterdir()))
card = (folder / "checkpoint.json").read_text()
print("card: ", card.replace("\n", "").replace(" ", ""))
cards.insert({"name": "Ditto"}) # lands in the fresh diary as LSN 6
db.close() # close() takes a final checkpoint too
with tempfile.TemporaryDirectory() as tmp: # an automatic checkpoint
folder = Path(tmp) / "cards"
db = Database(folder, journal=True, checkpoint_bytes=300)
cards = db.create_table("cards", Schema(columns=[Column("name", DataType.TEXT)]))
for number in range(10):
cards.insert({"name": f"card{number}"})
card = json.loads((folder / "checkpoint.json").read_text())
size = (folder / "journal.log").stat().st_size
print("auto: diary is", size, "bytes; last checkpoint at LSN", card["lsn"])
db.close()
Output:
diary before: 496 bytes
diary after: PYDBJ1 5
album: ['cards.5.json']
card: {"lsn":5,"tables":[{"name":"cards","file":"cards.5.json"}]}
auto: diary is 10 bytes; last checkpoint at LSN 11
After the checkpoint the diary is just a header -- PYDBJ1 5 means "this
diary carries on after LSN 5" -- and the photo cards.5.json holds all
four cards.
When Does It Happen?¶
- When you call
db.checkpoint()-- orsave()/save_table(), which do the same thing on a journalled database. - When you
close()the database (or leave awith Database(...)block). - Automatically, after any commit that makes
journal.logbigger thancheckpoint_bytes(4 MiB unless you choose otherwise).
A checkpoint is never taken in the middle of a transaction -- that would photograph half-done work. Only tables that changed since the last checkpoint get a new photo.
The Careful Order¶
A checkpoint is three steps, always in this order:
- Write the new photos -- each with a brand-new file name (
cards.5.json), so the photos the old card points at are never touched. - Write
checkpoint.json-- atomically: it's either the old card or the new one, never half of each. - Start a fresh diary (rotate
journal.log), then tidy away photos nobody points at any more.
What if the power goes out in between? Every moment is safe:
| Crash after... | On reopening |
|---|---|
| some new photos | The old card still rules: old photos + the full diary. The new photos are ignored. |
| the new card | New photos; diary lines up to the new LSN are skipped. Nothing happens twice. |
| the fresh diary | New photos, empty diary. Leftover old photos are tidied next time. |
What We Test¶
- A checkpoint shrinks the journal to its header and writes
checkpoint.json. - Reopening after a checkpoint plus more commits gives the right rows and LSN.
- Only changed tables are rewritten; dropped tables' files are removed.
save(),save_table()andclose()checkpoint; a transaction blocks it.- Automatic checkpoints happen once the journal passes
checkpoint_bytes, and a failed one never undoes a commit. - A real crash after each step recovers exactly, with nothing doubled.
- A missing snapshot, a bad card or a gap in the journal is a
RecoveryError.
Next Up¶
Head to Torn Writes to see how PyDB spots a half-written diary line.