Typorithm · medium

Partition generations and atomic manifest publication

pythonetldata-engineeringindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Partition generations and atomic manifest publication

0%

Concept and contract

Keep partial writes invisible by publishing a complete immutable generation through one pointer.

Writing several partition files is not one atomic operation. A reader that lists whatever files happen to exist may see a mixture of old and new data after a failure. This fixture writes a new generation separately and exposes it only by replacing one manifest pointer after every declared partition is ready.

The owned example supports a finite country allowlist and one writer. Readers follow the active manifest rather than scanning staging directories. The guarantee is scoped to pointer visibility on the same filesystem; os.replace alone does not establish power-loss durability or a transaction spanning SQLite, a filesystem and remote object storage.

Mechanism and worked example

The program starts with an old published generation, derives a deterministic generation name from candidate content and writes country-partitioned JSON files beneath it.

The program starts with an old published generation, derives a deterministic generation name from candidate content and writes country-partitioned JSON files beneath it. A controlled exception can interrupt the first partition. The active manifest is replaced only after the loop finishes successfully and a complete candidate manifest has been written.

The report opens the active pointer and reads only its listed parts. With the injected failure, the old generation remains visible even though one unpublished file exists. With success, both new countries become visible. An empty replacement has an explicit empty manifest rather than depending on a reader's directory-listing accident.

Complete worked example

python

from hashlib import sha256
import json
import os
from pathlib import Path

def inspect_example(rows, fail):
    root = Path('lake')
    (root / 'old').mkdir(parents=True)
    (root / 'old' / 'part.json').write_text(json.dumps([['IL','previous']]))
    pointer = root / 'manifest.json'
    pointer.write_text(json.dumps({'generation':'old','parts':['part.json']}))
    groups = {}
    for country, identity in rows:
        if country not in ['IL','UA','US']:
            raise ValueError('unsupported partition')
        groups.setdefault(country, []).append([country,identity])
    generation = 'g-' + sha256(json.dumps(sorted(rows)).encode()).hexdigest()[:12]
    folder = root / generation
    folder.mkdir()
    manifest = {'generation':generation,'parts':[c+'.json' for c in sorted(groups)]}
    temporary = root / 'manifest.tmp'
    try:
        for country in sorted(groups):
            (folder / (country+'.json')).write_text(json.dumps(groups[country]))
            if fail:
                raise RuntimeError('controlled interruption during staging')
        temporary.write_text(json.dumps(manifest))
        os.replace(temporary, pointer)
    except RuntimeError:
        pass
    active = json.loads(pointer.read_text())
    visible = [row for part in active['parts'] for row in json.loads((root/active['generation']/part).read_text())]
    return {'published': active['generation'] == generation, 'ids': sorted(r[1] for r in visible)}
Reasoning
The program starts with an old published generation, derives a deterministic generation name from candidate content and writes country-partitioned JSON files beneath it. A controlled exception can interrupt the first partition. The active manifest is replaced only after the loop finishes successfully and a complete candidate manifest has been written.
Download the ETL and Data Engineering computer lab ↗

Focused decision: use the complete example for context

python

temporary.write_text(json.dumps(manifest))
os.replace(temporary, pointer)
Decision
Count only partitions in the active manifest, including after an interrupted unpublished generation.
Download the ETL and Data Engineering computer lab ↗

Counterexamples and common mistakes

The controlled bug moves manifest replacement inside the partition-writing loop.

The controlled bug moves manifest replacement inside the partition-writing loop. At the fault point the pointer already advertises files that have not been created, and the reader raises FileNotFoundError. The successful single-partition path could conceal this bug, so the suite uses a multi-partition failure case.

Atomic pointer replacement is not a complete storage platform. Concurrent publishers require coordination or compare-and-swap semantics, and crash durability may require file and directory synchronization. Object stores have different APIs and consistency contracts. This lesson proves local staged visibility under its declared writer model, not universal atomic publication across arbitrary storage services.

Controlled bug: observe the rejected data contract

python

from hashlib import sha256
import json
import os
from pathlib import Path

def inspect_example(rows, fail):
    root = Path('lake')
    (root / 'old').mkdir(parents=True)
    (root / 'old' / 'part.json').write_text(json.dumps([['IL','previous']]))
    pointer = root / 'manifest.json'
    pointer.write_text(json.dumps({'generation':'old','parts':['part.json']}))
    groups = {}
    for country, identity in rows:
        if country not in ['IL','UA','US']:
            raise ValueError('unsupported partition')
        groups.setdefault(country, []).append([country,identity])
    generation = 'g-' + sha256(json.dumps(sorted(rows)).encode()).hexdigest()[:12]
    folder = root / generation
    folder.mkdir()
    manifest = {'generation':generation,'parts':[c+'.json' for c in sorted(groups)]}
    temporary = root / 'manifest.tmp'
    try:
        for country in sorted(groups):
            (folder / (country+'.json')).write_text(json.dumps(groups[country]))
            temporary.write_text(json.dumps(manifest))
            os.replace(temporary, pointer)
            if fail:
                raise RuntimeError('controlled interruption during staging')
        temporary.write_text(json.dumps(manifest))
        os.replace(temporary, pointer)
    except RuntimeError:
        pass
    active = json.loads(pointer.read_text())
    visible = [row for part in active['parts'] for row in json.loads((root/active['generation']/part).read_text())]
    return {'published': active['generation'] == generation, 'ids': sorted(r[1] for r in visible)}
Reasoning
The controlled bug moves manifest replacement inside the partition-writing loop. At the fault point the pointer already advertises files that have not been created, and the reader raises FileNotFoundError. The successful single-partition path could conceal this bug, so the suite uses a multi-partition failure case.
Download the ETL and Data Engineering computer lab ↗

Independent practice

Add visiblePartitions to the report, calculated from the manifest actually read by the reader.

Add visiblePartitions to the report, calculated from the manifest actually read by the reader. Preserve the failure point and old-generation visibility. A failed candidate with two intended countries should still report the old manifest's partition count, not the number of files attempted or the candidate's planned partition count.

Use the active manifest after the try block and before returning the report. Compare the partial-write failure, successful two-country replacement and empty replacement. The task is an observation of published state; it must not make staged files visible or move pointer replacement earlier merely to simplify counting the new candidate.

Step 1: Count only partitions in the active manifest, including after an interrupted unpublished generation. Step 2: Edit the owned exercise file and run verify.py --practice etl-publish. Preserve caller inputs and the independently declared failure cases.

Practice starter: solve before reading the solution

python

from hashlib import sha256
import json
import os
from pathlib import Path

def inspect_example(rows, fail):
    root = Path('lake')
    (root / 'old').mkdir(parents=True)
    (root / 'old' / 'part.json').write_text(json.dumps([['IL','previous']]))
    pointer = root / 'manifest.json'
    pointer.write_text(json.dumps({'generation':'old','parts':['part.json']}))
    groups = {}
    for country, identity in rows:
        if country not in ['IL','UA','US']:
            raise ValueError('unsupported partition')
        groups.setdefault(country, []).append([country,identity])
    generation = 'g-' + sha256(json.dumps(sorted(rows)).encode()).hexdigest()[:12]
    folder = root / generation
    folder.mkdir()
    manifest = {'generation':generation,'parts':[c+'.json' for c in sorted(groups)]}
    temporary = root / 'manifest.tmp'
    try:
        for country in sorted(groups):
            (folder / (country+'.json')).write_text(json.dumps(groups[country]))
            if fail:
                raise RuntimeError('controlled interruption during staging')
        temporary.write_text(json.dumps(manifest))
        os.replace(temporary, pointer)
    except RuntimeError:
        pass
    active = json.loads(pointer.read_text())
    visible = [row for part in active['parts'] for row in json.loads((root/active['generation']/part).read_text())]
    return {'published': active['generation'] == generation, 'ids': sorted(r[1] for r in visible)}

# Independent task: Count only partitions in the active manifest, including after an interrupted unpublished generation.
Reasoning
Count only partitions in the active manifest, including after an interrupted unpublished generation.
Download the ETL and Data Engineering computer lab ↗
  • Hint 1: Add visiblePartitions to the report, calculated from the manifest actually read by the reader. Preserve the failure point and old-generation visibility. A failed candidate with two intended countries should still report the old manifest's partition count, not the number of files attempted or the candidate's planned partition count.
  • Hint 2: Use the active manifest after the try block and before returning the report. Compare the partial-write failure, successful two-country replacement and empty replacement. The task is an observation of published state; it must not make staged files visible or move pointer replacement earlier merely to simplify counting the new candidate.

Solution, tests and trade-offs

The solution reports how many partitions the current reader can legitimately observe.

The solution reports how many partitions the current reader can legitimately observe. The prepared result distinguishes candidate work from published state, and exact returned rows confirm the manifest points to readable files. The controlled bug remains rejected at the first incomplete advertised generation.

A production manifest should usually contain hashes, schema version, row counts and lineage for every immutable part. Validate those before promotion, then retain the previous generation for rollback according to an explicit policy. Garbage collection must avoid deleting parts referenced by active readers, retained manifests or in-progress recovery operations.

Practice solution and acceptance checks

python

from hashlib import sha256
import json
import os
from pathlib import Path

def inspect_example(rows, fail):
    root = Path('lake')
    (root / 'old').mkdir(parents=True)
    (root / 'old' / 'part.json').write_text(json.dumps([['IL','previous']]))
    pointer = root / 'manifest.json'
    pointer.write_text(json.dumps({'generation':'old','parts':['part.json']}))
    groups = {}
    for country, identity in rows:
        if country not in ['IL','UA','US']:
            raise ValueError('unsupported partition')
        groups.setdefault(country, []).append([country,identity])
    generation = 'g-' + sha256(json.dumps(sorted(rows)).encode()).hexdigest()[:12]
    folder = root / generation
    folder.mkdir()
    manifest = {'generation':generation,'parts':[c+'.json' for c in sorted(groups)]}
    temporary = root / 'manifest.tmp'
    try:
        for country in sorted(groups):
            (folder / (country+'.json')).write_text(json.dumps(groups[country]))
            if fail:
                raise RuntimeError('controlled interruption during staging')
        temporary.write_text(json.dumps(manifest))
        os.replace(temporary, pointer)
    except RuntimeError:
        pass
    active = json.loads(pointer.read_text())
    visible = [row for part in active['parts'] for row in json.loads((root/active['generation']/part).read_text())]
    return {'published': active['generation'] == generation, 'ids': sorted(r[1] for r in visible), 'visiblePartitions': len(active['parts'])}
Reasoning
The solution reports how many partitions the current reader can legitimately observe. The prepared result distinguishes candidate work from published state, and exact returned rows confirm the manifest points to readable files. The controlled bug remains rejected at the first incomplete advertised generation.
Download the ETL and Data Engineering computer lab ↗

Interview reasoning and next step

Explain what the atomic step is and what it does not cover.

Explain what the atomic step is and what it does not cover. Identify the staged files, active pointer and reader algorithm, then trace the failure before promotion. A correct answer should account for why orphan staging data can exist safely while a pointer to an incomplete generation cannot.

For a stretch, add per-file checksums to the manifest and deliberately corrupt one staged part before promotion. Reject the candidate while preserving the old pointer. Keep cleanup as a separate reviewed operation; deleting all directories except the newest name is not a safe substitute for reference-aware retention.

Optional stretch — after the core task works: Validate staged part checksums before pointer promotion and preserve the prior generation on rejection.

Computer practice: https://applydjinn.com/typorithm/etl-data-engineering-lab-20261107.zip. Use an isolated Python 3.13 environment, install requirements.txt and run verify.py. Edit exercises/etl-publish.py and run --practice etl-publish. Each case owns its files and database; Parquet uses the pinned DuckDB engine. The recovery probe terminates a separate interpreter and reopens the SQLite file. The app retains static examples, prepared results and questions without an editor or code execution. Use the course Download button once to save every lesson and illustration; opening an unsaved course does not automatically download it.

Keep partial writes invisible by publishing a complete immutable generation through one pointer.