Repository navigation
perf: time the zarr export's four phases — 5h07m with no breakdown could not be attributed (#501) - #502
Merged
Conversation
…uld not be attributed (#501) The 2026-10-16 run measured the export at 18,427s, 88% of the pipeline and ~5x ADR-041's June figure of ~57 min. Nothing recorded WHERE that time went, so the regression could be stated but not located. Four phases are now timed, printed in the export's summary and written to export_ledger.jsonl as `phase_seconds`: scan the source_sums loop — one traversal of grid.npy per feature write ds.to_zarr + consolidate_metadata — the second traversal verify the round-trip integrity check — full decompression swap the C-144 .tmp -> rename dance Plus `other`, printed explicitly so the columns account for the whole of the existing `Time:` line rather than silently omitting a remainder. The ledger is the point: a print lives in logs/refresh.log, which rotates. `phase_seconds` in the ledger makes the breakdown queryable across runs, which is what turns a single measurement into a trend. Verified empirically, not inferred: - grid.npy is C_CONTIGUOUS with features as the contiguous last axis, so grid[:, :, :, i] strides F x 4 bytes between adjacent (t, h, w) — 316 bytes at F=79. A 4 KB page carries ~13 useful float32s out of 1024. - `source_sums` has exactly one consumer: the verify comparison. The scan phase's full-grid read exists solely to feed that check. NOT optimised. No change to chunking, compression, the slicing pattern, or the integrity check. Each is a plausible remedy for a cause that is not yet established, and the integrity check is an ADR-011/C-144 guarantee that must not be weakened to save time. #501 carries a pre-registered falsification criterion so the first production measurement is a test rather than a confirmation. Three guards in TestZarrExport, both substantive ones drilled by sabotage rather than assumed: delete phase_seconds["swap"] -> test_phase_timings_recorded FAILED inflate the scan timer past total -> test_phases_do_not_exceed_the_total FAILED The phase labels are asserted as an exact set; a subset assertion would let a dropped phase restore the original blindness silently. Verification: full suite exit 0 (0 FAILED, 0 ERROR), ruff clean, mypy src/ clean (89 files), validate_docs.sh PASSED, export-touching test files re-run green against the final tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EfeuULbPTqrx4qtNki5dy9
Polichinel
enabled auto-merge (squash)
October 18, 2026 17:37
Polichinel
added a commit
that referenced
this pull request
Oct 20, 2026
…he whole point (#501) (#503) Not one file under src/ changed since v1.14.0, and the wheel packages only the nine src/datafactory_* trees, so a consumer installing 1.15.0 from PyPI gets exactly what 1.14.0 gave them with different version metadata. That is the point rather than a defect. The server deploys by git tag (ADR-022): refresh_pipeline.sh reads ~/.views-deploy-tag and checks out that exact tag. The two things this release carries — scripts/export_zarr.py and a set of corrected guides — reach production no other way, because the wheel does not contain them. v1.11.0 and v1.12.0 were both minor bumps with zero src/ change, so the shape is house precedent, not a judgement made here. Why now: #502 merged the only instrument that can attribute the zarr export's 5h07m to a phase, and it sits on development where the server cannot see it. The monthly cron fires 00:00 on the 21st. A tag before then gets the measurement on a run that was happening anyway; missing it means a deliberate out-of-band six-hour run or waiting until 21 November. The plan edit is not housekeeping tacked on. TestF7ProductPlanCurrency asserts the product plan's title carries the current minor version, so the version bump alone turns it red — the two edits are one atomic change. Confirmed by breaking it rather than by reading it: with the title reverted to "through v1.14" and the version at 1.15.0 the test FAILED, and went green when the title was restored. uv.lock moves too: it pins the root project version (line 1701), and uv re-synced it on the first uv run after the bump. refresh_pipeline.sh:178 does git checkout -- uv.lock before the tag checkout precisely because the server mutates it locally. Noticed, deliberately not fixed: "## Version History" in the same plan file stops at v1.6 and jumps to v2.0 — eight releases happened without it. The per-release addenda at the top have been the live record since v1.7. Repairing a section stale for eight releases inside a version bump would hide a documentation change inside a release. No tag, no GitHub Release, no deploy. Each is separately gated. Verification: full suite EXIT=0, 0 FAILED, 0 ERROR (XFAILs are documented deferrals); ruff check . clean; mypy src/ clean (89 files); validate_docs.sh PASSED; deploy_v160 + _v160_r2 + merge_readiness 10 passed 1 skipped (HEAD not on a tag, expected pre-release); main is an ancestor of development. Refs #501. Claude-Session: https://claude.ai/code/session_01EfeuULbPTqrx4qtNki5dy9 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Polichinel
added a commit
that referenced
this pull request
Oct 20, 2026
…ve times in eight days (C-343) (#509) Covers the export phase instrumentation (#501/#502), four rounds of C-343 correction (#497-#500), the release/publish/deploy of v1.15.0, a fifth C-343 round found while writing this up (#507), and a licensing contradiction found while onboarding an external consumer (#508). The v1.11.0 post-mortem named the spine running through everything it reported: "a written record that was true when written and stopped being true without anything failing." Both it and its companion observation — that the post-mortem convention keeps failing to be applied — recurred. Three of the four releases since are unwritten and now never will be. The spine produced five fresh instances in eight days, all in C-343, the fifth found inside the register entry created to track the first, including in its Trigger: field. A sixth variant was committed by the author of the fourth correction three days later: merging the instrumentation is not deploying the instrumentation. The conclusion recorded is not "try harder". Every instance in this window was found by a human asking an unprompted question or by an author re-reading their own work. Five rounds have produced a procedure that is right and nothing that notices when a copy of it goes wrong. Also records what went well rather than only the failures: block 4 of the deploy procedure executed successfully for the first time since it was written, the #501 falsification criterion was committed before any production figure existed, three guards were sabotaged and confirmed red, and the clean- venv publish check caught a defect in itself rather than a false alarm on the release. Verification: validate_docs.sh PASSED; all 14 register-reading test files 41 passed. Every figure quoted is cited to a file rather than recalled; the release count was corrected against `git tag` before committing. Refs #501, #508. Claude-Session: https://claude.ai/code/session_01EfeuULbPTqrx4qtNki5dy9 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the first ask of #501. Measurement only — nothing is optimised.
The problem
The 2026-10-16 run measured the zarr export at 18,427s (5h07m): 88% of the pipeline, and ~5× ADR-041's June figure of ~57 min. The regression could be stated but not located, because the export printed one
Time:line and nothing else.What this adds
Four phases, printed in the export's summary and written to
export_ledger.jsonlasphase_seconds:scansource_sumsloop — one traversal ofgrid.npyper featurewriteds.to_zarr+consolidate_metadata— the second traversal, plus compressionverifyswap.tmp→ rename danceotherTime:I added
swapbeyond the three #501 asked for because measuring it is free and it lets us rule it out instead of assuming it's cheap.On the synthetic grid (7 features, 0.1 GB, fits in RAM):
The ledger is the point, not the print.
logs/refresh.logrotates.phase_secondsin the ledger makes the breakdown queryable across runs, which is what turns one measurement into a trend — and the absence of exactly that is why a 5× regression sat unnoticed for four months.Two claims verified rather than inferred
The comments in the diff assert things about the data layout. I measured them:
grid.npyis C-order with features as the contiguous last axisC_CONTIGUOUS=True,strides=(..., 28, 4)at F=7 →grid[:,:,:,i]strides F × 4 bytes between adjacent(t,h,w), i.e. 316 bytes at F=79. A 4 KB page carries ~13 useful float32s out of 1024.source_sumsexists only to feed the verify comparisonexport_zarr.py:408The second matters: the
scanphase reads the entire grid purely to produce checksums for a later comparison.Explicitly not done
No change to chunking, compression, the slicing pattern, or the integrity check. Each is a plausible remedy for a cause that is not established, and the integrity check is an ADR-011/C-144 guarantee that must not be weakened to buy time.
#501 carries a pre-registered falsification criterion written before any production measurement exists:
The hypothesis is mine, so pre-committing the criterion is the only thing stopping me from reading the result as confirmation. The synthetic baseline above — where
scanis the smallest named phase — is the contrast.Guards, drilled
Three tests in
TestZarrExport. The phase labels are asserted as an exact set: a subset assertion would let a dropped phase silently restore the original blindness. Both substantive guards were sabotaged to confirm they bite:phase_seconds["swap"] = ...test_phase_timings_recordedFAILEDscantimer past the totaltest_phases_do_not_exceed_the_totalFAILED —phases sum to 9999.8s but total is 1.3sRegister: no new entry, and why
Nothing in the register covers runtime regression, and
check_health.pyhas freshness SLOs (how old the data is) but no duration SLO. That gap is real.It is not a new entry, for two reasons. The instrument that would have caught this is what this PR builds — registering a concern this PR largely closes would be noise. And the root cause is already recorded: the 5× went unnoticed because ADR-041's figures were the only record and nobody re-measured, which is C-343's pattern exactly, registered as its 2026-10-18 addendum in #500.
What remains genuinely open: durations are now recorded but nothing compares them across runs. One run of data does not yet justify a trend-watching mechanism, so I am flagging it rather than building it.
Verification
uv run ruff check .— cleanuv run mypy src/— clean, 89 source filesbash docs/validate_docs.sh— PASSEDPre-existing and out of scope:
export_zarr.py:100has a mypyunion-attrerror onsys.stdout.reconfigure, identical on a clean tree. CI typecheckssrc/only, so it does not gate — but it is real and unowned.🤖 Generated with Claude Code
https://claude.ai/code/session_01EfeuULbPTqrx4qtNki5dy9