Course contents
On this page
Lesson 08 of 59

Save a row, close the program, read it back

Separate an ordinary file round trip from the stronger promise of crash recovery.

We encoded product 27 and the short name “Tote” as six bytes: 27, 4, 84, 111, 116, 101. The first two bytes describe the record; the remaining four encode the name.

The memory example lost its rows when the program ended. A file gives a later run somewhere to retrieve them. The following Python example demonstrates the round trip using a temporary directory, so it cannot replace an existing product file.

from pathlib import Path
from tempfile import TemporaryDirectory

record = bytes([27, 4, 84, 111, 116, 101])

with TemporaryDirectory() as folder:
    path = Path(folder) / "products.bin"
    with path.open("wb") as output:
        written = output.write(record)
        if written != len(record):
            raise OSError("Incomplete record write")

    with path.open("rb") as source:
        loaded = source.read()

    assert loaded == record

bytes(...) creates a byte sequence. wb opens a file for binary writing; rb opens it for binary reading. A with block closes the file when the block finishes. Errors are allowed to propagate: we do not print “saved” after a failed write.

The assertion checks byte equality. It does not decode the row or prove that the file remains valid under every failure. The temporary directory is removed at the end; it is just a safe place to run this demonstration.

What did the successful test establish?

We wrote bytes, closed the writing handle, opened a reading handle, and obtained the same bytes. We have demonstrated an ordinary save and read under the conditions of this run.

Now suppose the machine loses power during a write. Bytes may still be waiting in memory rather than on persistent storage. A write can also fail after some work has occurred. Closing a file in a successful run does not tell us what a failed run leaves behind.

  1. Program supplies bytes

    The runtime may collect them in a buffer.

  2. Operating system receives the write

    The system may retain modified data in memory before sending it to storage.

  3. Storage must preserve the promised result

    Failure assumptions and synchronization rules determine when the engine can safely acknowledge success.

Data passes through layers. A successful read during the same run does not independently verify power-loss durability.

Persistence means data is retained beyond the running program’s temporary state. To promise durability, we must say which failures an acknowledged change survives, then implement the necessary protocol. File synchronization is one ingredient. It is not by itself a transaction protocol for several related changes.

For example, a purchase might need to update one record and append another. Making each write persistent separately does not prevent a failure between them. Later, logging and recovery will give us a way to reason about that gap.

Read a damaged record

The file contains 27, 4, 84, 111. Can our reader return a valid product name?

Compare the promised length with the available data

No. The header promises four name bytes, but only two follow. A reader for this format must report truncation instead of treating “To” as the intended name. A recovery system may later supply a way to restore a valid state, but this decoder has no evidence for the missing bytes.

We now have a representation and an ordinary storage path. As the file grows, loading everything at once stops being practical. The next chapter provides a deeper look at physical representation. The page introduction then shows how to bring only part of the file into memory at a time.

Further reading

Python file objects explain the example’s operations. SQLite’s atomic-commit explanation develops the failure and storage assumptions needed for a real commit protocol.