Skip to content

Rework benchmark matrix: sharded tdigest, inline-vs-sidecar, K=4, 4 GB#196

Merged
espg merged 8 commits into
mainfrom
claude/193-sharded-inline-sidecar-matrix
Jul 8, 2026
Merged

Rework benchmark matrix: sharded tdigest, inline-vs-sidecar, K=4, 4 GB#196
espg merged 8 commits into
mainfrom
claude/193-sharded-inline-sidecar-matrix

Conversation

@espg

@espg espg commented Jul 8, 2026

Copy link
Copy Markdown
Member

Reworks the live benchmark matrix to what actually matters after this week's read-path work, espg-directed (session thread); builds on #192 (wall columns).

The new matrix

tdigest · sharded · inline-vs-sidecar · granule_workers=4 · 4 GB workers · o9 + o10. The read backend is the A/B axis; the old ShardingCodec (sharded/inner) + read (cached) axes are retired. 4 live targets: tdigest_healpix_{o9,o10}_{inline,sidecar}.

Phases

  • Phase 1 — matrix + cost/memory foundation. New targets.json (4 inline/sidecar targets; old codec/inner/cached retired with a frozen-note); o9/o10 configs pinned granule_workers: 4 + sharded: true with index set per-target; run_benchmark applies data_source.index from each target's index_backend and records it; LAMBDA_MEMORY_GB 2.0→4.0 and template.yaml MemorySize default 2048→4096 (issue Granule-level read concurrency: the worker's serial granule x group loop is the dominant read-wall term #180 sizing — 4 GB is faster and cheaper); bench_metrics gains the index_backend column. Tests updated (74 pass).
  • Phase 2 — plot_series. Freeze the codec figures (stop regenerating codec_table.png / *_codec.png — the old PNGs are retained on the benchmarks branch, just no longer updated); add the inline/sidecar matrix figures (2×2, o9/o10 × inline/sidecar) at the top; replace the codec-layout tests with matrix-layout tests.
  • Phase 3 — docs. New matrix at the top of tests/data/benchmark/README.md; frozen codec matrix documented below.
  • Phase 4 — final gates. 331 tests green (test_benchmark/config/dispatch/lambda_build, excl. the pre-existing environmental test_function_build_succeeds); ruff clean. End-to-end plot render verified: matrix table + *_matrix.png on top, retained codec/frozen PNGs embedded as an archived section, machine-readable companions track the live matrix.

Notes / for review

  • Retained-not-removed: make_codec_figure/_codec_layout/make_figure etc. stay in plot_series.py — they regenerate the archived tiers on demand and are the shared rendering engine's test surface; main() simply no longer invokes the codec/frozen tiers. Flagging in case you'd rather rip the codec-specific renderers out entirely.

  • Deploy (yours): the 4096 default lands via a template.yaml MemorySize change — production process-shard picks it up on the next stack update/deploy (§1, espg). Until then the CI cost model (LAMBDA_MEMORY_GB=4.0) assumes the benchmark function is at 4096; the benchmark should run against a 4096-deployed function for the numbers to be truthful.

  • Old benchmark PNGs: archived (sent to espg) from benchmarks commit e0eafda; this PR stops updating them but retains them on the branch.

  • metrics.json at repo root is a stray run artifact — left untracked (removed from the phase-1 commit).


Branch/commit messages say "#193" from an earlier number assumption; #193 is actually the merged consolidation-skip issue. This matrix rework is session-directed with no separate issue — happy to file a tracking issue if wanted. Commit messages can't be corrected without a force-push.

@espg espg changed the title Rework benchmark matrix: sharded tdigest, inline-vs-sidecar, K=4, 4 GB (issue #193) Rework benchmark matrix: sharded tdigest, inline-vs-sidecar, K=4, 4 GB Jul 8, 2026
# issue #193: the sharded inline-vs-sidecar matrix. The read backend is set
# per target (index_backend: inline|sidecar) by run_benchmark; granule-level
# read concurrency is pinned to K=4 (issue #180 sizing sweet spot).
granule_workers: 4

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Removing index: backend: hierarchical from this shared config (and the o10 twin) silently reroutes the provisional _88s baseline probes. tdigest_healpix_o9_88s / tdigest_healpix_o10_88s reuse these exact configs (atl03_tdigest_healpix_o9.yaml / _o10.yaml) and set no index_backend, so run_target leaves data_source.index absent → index_from_config resolves to the default inline path (these configs have read_plan.spatial_index: segments, no chunk_boundaries, so the hierarchical exception does not apply).

Before this PR those probes ran hierarchical. That contradicts the standing intent recorded in provisional_targets._comment: "the base _88s targets stay pinned to the hierarchical baseline (issue #148 arm 1, the honest uncached reference)" — the _88s vs _88s_cached pair is supposed to measure hierarchical-vs-sidecar, and it now measures inline-vs-sidecar. (These probes also now inherit sharded: true + granule_workers: 4.)

run_benchmark already grew a hierarchical branch in this PR, so the clean fix is to add "index_backend": "hierarchical" to the two _88s provisional targets in targets.json, restoring the honest uncached reference without re-pinning the shared config.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Good catch — folded in 454ac1b. Pinned index_backend: hierarchical on tdigest_healpix_{o9,o10}_88s (restores the honest uncached baseline the config lost when I dropped its pin) and sidecar on the _88s_cached companions (explicit, matches their config). Verified via dry-run: the base 88s target now records hierarchical, the cached one sidecar. Also refreshed the two stale '2 GB' prose spots (plot_series memory-cap fallback + bench_metrics header) to 4 GB.

@espg

espg commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude (review)

Fresh-context review of the benchmark-matrix rework. One substantive finding is inline (the _88s provisional probes silently rerouting hierarchical→inline). Two minor / advisory items below that have no diff line to anchor on, plus the things I verified clean.

Minor — stale "2 GB" prose after the 4 GB flip. plot_series._memory_cap_mb docstring still says "the benchmark pins 2 GB" and its fallback returns 2.0 * 1024.0; bench_metrics.py header comment still says "arm64, 2 GB — issue #110". Not a live bug (build_record records memory_gb=LAMBDA_MEMORY_GB=4.0, and the 2048 fallback only fires when the column is absent on pre-#193 rows), but the prose/fallback are now inconsistent with the sizing this PR ships.

Advisory — dead render path (question of judgment). main() no longer calls make_codec_figure / make_figure / make_codec_latest_table / make_latest_table / _codec_layout / _panel_layout / _codec_history / _frozen_history. Retaining them is defensible since they remain under test and document the archived-tier layout — but note the archived PNGs are now embedded purely by existence-check and are never regenerated, so this code is genuinely dead in production, not just dormant. Recommendation: keep them for this PR (test surface + possible archived regen), but add a one-line module note that they are retained only as the tested archived-render surface, or prune in a follow-up. Low priority.

Cost-model coupling (verified acceptable). memory_gb=4 is truthful only if the deployed stack is actually 4096 MB. LAMBDA_MEMORY_GB is a constant, not read from the live function, and the template Default change does not touch an already-deployed stack — so the benchmark bills 4 GB regardless of what is deployed. This is by-design and documented (dispatch comment + template default), but flagging that the deploy must be re-applied at 4096 for the reported cost to stay honest.

Verified clean:

  • run_target index application — inline / sidecar (with on_miss: build + the same store the _cached config uses) / hierarchical branches are correct; a target with no index_backend leaves data_source.index untouched (provisional 88s/scalar dispatch unchanged); sharded and index_backend are independent config sections, no conflict when both are set.
  • Schema: index_backend appended last in RECORD_COLUMNS; build_record threads it by name; old rows reindex to null. No positional assumption broken (the [-5:] tail test was updated).
  • plot_series: main() truly stops regenerating codec/frozen PNGs; the archived section embeds them by outdir existence-check; the workflow checks out the benchmarks site/ then git add site (no deletion), so retained PNGs persist. _matrix_mask = index_backend.notna() correctly isolates the new matrix and excludes codec/frozen rows; matrix rows do leak into _frozen_history (codec.isna) but that path is no longer rendered, so it is harmless.
  • Matrix layout tests are correct (fixed 2×2 o9/o10 × inline/sidecar, missing order → None). Unicode (em-dash / middle-dot /·) renders as real characters in index.html, no \u leak.
  • template.yaml block-scalar Description parses under the CFN-aware loader; MemorySize default 2048→4096 breaks no test (the mirror test compares !Ref MemorySize symbolically).
  • Full tests/test_benchmark.py + tests/test_dispatch.py: 94 passed.

@espg espg added the implement label Jul 8, 2026

@espg espg left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Reviewed commit 325ec8d only. Verdict: this is a legitimate fix that tracks the intentional 2→4 GB behavior change, not a weakening of tests to mask a regression.

Verification:

  • Source of truth confirmed: src/zagg/dispatch.py:320 LAMBDA_MEMORY_GB = 4.0 (# issue #193), and the runner cost path multiplies total_lambda_time * memory_gb.
  • Arithmetic on all three updated fixtures is correct:
    • test_lambda_cost_byte_identical: 4 cells × 2 s = 8.0 lambda_time_s (unchanged), × 4 GB = 32.0 GB-s; cost 32.0 * 0.0000133334. ✓
    • test_..._with_mixed_durations: gb_seconds == total * 4.0, cost (total * 4.0) * price — preserves the single-multiply-over-summed-time order the test guards. ✓
    • TestTemporalLambdaStrategy: 2.0 s × 4 GB == 8.0. ✓
  • The updated comments were kept in sync with the new factor.

No other cost-factor test still hardcodes 2 GB. tests/test_dispatch.py already references LAMBDA_MEMORY_GB symbolically (unaffected). The memory_gb: 2.0 / cap_mb / 2048 occurrences in tests/test_benchmark.py are historical benchmark rows and the memory-fraction twin axis (issue #120) — inputs to plotting, not the dispatch cost factor — so they correctly stay at 2 GB and are out of scope for this change. tests/test_lambda_handler.py's memory_limit_in_mb = 2048 is a Lambda context mock, also unrelated.

One non-blocking nit left inline about symbolic vs. literal constant. Otherwise clean.


Generated by Claude Code

Comment thread tests/test_runner.py
# 4 cells x 2 s x 4 GB = 32 GB-s; cost = 32 * arm64 price.
assert summary["lambda_time_s"] == 8.0
assert summary["gb_seconds"] == 16.0
assert summary["gb_seconds"] == 32.0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Non-blocking nit (follow-up, not for this commit): these three cost fixtures hardcode the 4.0 factor as a literal, whereas the sibling tests/test_dispatch.py imports LAMBDA_MEMORY_GB and asserts against it symbolically (3.0 * LAMBDA_MEMORY_GB), which won't drift if the default changes again. The literal style matches the rest of test_runner.py, so it's acceptable here — just worth a future pass to reference the constant symbolically so a memory bump only needs a source edit, not a fixture sweep.


Generated by Claude Code

@espg

espg commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

Benchmark render preview (issue #193)

Rendered locally from plot_series.py on this branch. Layout/rendering is exactly what CI produces; the numbers are representative 0.21/4 GB values across 3 synthetic merges (SSO expired, so this isn't a fresh fleet dispatch — the live values populate on the first merge to main).

Latest-merge table — the live inline-vs-sidecar matrix:

matrix table

Cost per shard — 2×2 grid, rows o9/o10, columns inline/sidecar, cost + runtime vs merge history:

cost per shard

Cost per 100 km²:

cost per 100km2

The retired codec/frozen figures are frozen (retained on the benchmarks branch, embedded in an "Archived" section of index.html below the live matrix — not shown here since a fresh render has no archived PNGs present).

(Preview images live on the throwaway claude/193-benchmark-preview branch — not for merge; delete whenever.)

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview Ready!

Live Preview

URL: https://zagg-pr-196.surge.sh/

Commit: 325ec8d


Preview deployed via /preview command by @espg

@espg

espg commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

Real benchmark render (0.21.0 fleet · 4 GB · K=4)

Live dispatch of the 4 matrix targets against process-shard (0.21.0, 4096 MB), rendered through this PR's plot_series.py. finalize = 0.0 s on all four (the 0.21 consolidation-skip working).

target granules obs runtime cost/shard cost/100 km²
o9 inline 59 3,112,138 92.0 s $0.00245 $0.00151
o9 sidecar 59 3,112,138 68.0 s $0.00181 $0.00112
o10 inline 50 939,217 60.5 s $0.00161 $0.00398
o10 sidecar 50 939,217 48.7 s $0.00130 $0.00321

Sidecar beats inline on both orders (o9 −26%, o10 −19%) — the selection-read + compiled-decode win. Memory 320–540 MB of 4096.

Latest-merge table:

matrix table

Cost per shard (rows o9/o10, cols inline/sidecar):

cost per shard

Cost per 100 km²:

cost per 100km2

(Single-merge render, so each series panel shows one point — the trend fills in over merges. Preview images on the throwaway claude/193-benchmark-preview branch, not for merge.)

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview Ready!

Live Preview

URL: https://zagg-pr-196.surge.sh/

Commit: 325ec8d


Preview deployed via /preview command by @espg

@espg espg marked this pull request as ready for review July 8, 2026 19:43
@espg espg merged commit 305d03b into main Jul 8, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants