Benchmarks Website V3: Admin and Auto-Deploy#7849
Conversation
| @@ -0,0 +1,22 @@ | |||
| # SPDX-License-Identifier: Apache-2.0 | |||
1477314 to
07656cc
Compare
Merging this PR will improve performance by 19.79%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | chunked_varbinview_opt_canonical_into[(1000, 10)] |
225.1 µs | 187.9 µs | +19.79% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing ct/bench-v3-autopilot (bbc364f) with develop (f852d72)
3bc4736 to
6ffb3dc
Compare
c26d766 to
03f8731
Compare
6ffb3dc to
a246b73
Compare
|
You'll need to delete https://github.com/vortex-data/vortex/blob/develop/.github/workflows/publish-benchmarks-website.yml, otherwise it will publish a new image and break the live website |
|
I will figure out the performance problems before I merge this |
c35640d to
9625ab4
Compare
a246b73 to
1270615
Compare
9625ab4 to
ee5f352
Compare
02a5b97 to
499fe8f
Compare
ee5f352 to
1c1dab6
Compare
84e51d8 to
bc08b17
Compare
560e059 to
b133658
Compare
Applied must-fix items from gauntlet review of PR-7849 "Benchmarks Website V3: Admin and Auto-Deploy" plus the external-agent findings folded into the synthesis. Applied (must-fix): - scope-creep: delete .github/workflows/publish-benchmarks-website.yml (built the v2 Dockerfile this PR deletes; would push a broken image) - bug: ensure_admin_is_loopback() refuses VORTEX_BENCH_ADMIN_BIND that doesn't resolve to 127.0.0.0/8 or ::1; tests added - weak-exit-criteria: README quick-start now includes the explicit systemctl start step after the env edit (install.sh leaves units stopped when tokens were empty at install time) - scope-drift: deploy.sh symlink swap is now staging-symlink + mv -Tf (rename(2) is atomic on POSIX); same pattern in both rollback paths - scope-drift: deploy.sh force mode bypasses the hash-equal restart-skip fast path; force ALWAYS restarts + /health-verifies - concurrency: snapshot duplicate-ts race closed with a process-local Mutex<HashSet<String>> reservation (SnapshotTicket RAII guard) plus the existing tmp+rename for crash safety - security: backup.sh and inspect.sh now write Authorization header to a 0600 tempfile + pass via `curl -H @path` so bearer token never appears in argv (visible via `ps aux`) - concurrency: migrate.sh masks deploy.timer + backup.timer for the migration window and restores them on trap EXIT - coverage: ci.yml runs the formerly-#[ignore]'d snapshot tests explicitly via `--run-ignored only` on Linux/macOS runners; the entire backup contract is now exercised in CI - bug: post-ingest.py default --max-envelope-bytes raised 8MB -> 60MB to fit under the server's 64 MB limit in one envelope (restores the per-file all-or-nothing contract for typical runs); chunking now emits an explicit warning about partial-data risk - cross-cutting: install.sh override env vars removed (run user, state-dir, env-file, systemd-dir paths are hard-coded to match the shipped systemd units rather than advertising a non-working override surface) - bug: migrate CLI fails by default if any file-sizes-*.json.gz source failed; --allow-missing-file-sizes opts into the prior warn-and-continue behavior - cross-cutting: README + backup.service comment now describe `aws s3 cp <ts>.tar.gz` (not `aws s3 sync`) - cross-cutting: README says "DuckDB core extension repo" (matching admin.rs comments and the PR's intent) - cross-cutting: build.rs emits the full 40-hex SHA in VORTEX_BENCH_BUILD_SHA so /health's build_sha matches last-deployed-sha byte-for-byte - error-path: admin.rs cleanup discards replaced with cleanup_partial() helper that tracing::warn!s on non-ENOENT failures Applied (cheap should-fix): - doc: admin.rs module docstring corrected (each admin SQL call runs on a cloned connection under READ ONLY tx, not "mutex held for duration") - doc: main.rs module docstring points to public_router + admin_router (production), not the test-only `router` helper - doc: README quick-start renumbered after the new "start units" step - doc: README "last 1-2" migration backup retention claim removed (migrate.sh never prunes bench.prev-*) - doc: README migrate cheatsheet sources /etc/vortex-bench.env before expanding $VORTEX_BENCH_DB (falls back to canonical path) - doc: README manual-snapshot example sources the env file (no whitespace-fragile grep) and references the response's snapshot_dir - doc: backup.sh comment now accurately names the v3 bucket (vortex-benchmark-results-database; v2 used vortex-ci-...) - concurrency: backup.sh uses mktemp + trap-cleanup for response capture and the auth-header file (no /tmp/snapshot.out race) - backup.service: PrivateTmp=true (extra isolation for the response capture file) - architecture: deploy.sh rollback path re-probes /health on the rolled-back binary; distinguishes "rolled back cleanly" (exit 6) from "rollback ALSO failed" (exit 7, manual intervention) Deferred (should-fix; revisit next cycle): - architecture: app.rs three-router constructor duplication - architecture: ADMIN_SQL_ROW_LIMIT silent cap surface - architecture: startup probe for vortex DuckDB extension - architecture: reject EXPLAIN ANALYZE writes at allow-list (instead of relying on READ ONLY transaction to return 5xx) - encapsulation: lib.rs admin module's public DTO surface - scope-creep flags from spec lens (bench.yml continue-on-error, window clamp, read-model retention, Accept-Encoding q-value, post-ingest chunking) — these are real but expanding the PR description is the right action, not reverting the code Dismissed: - correctness lens flagged deploy.sh:87 `git fetch origin <branch>` + `git rev-parse origin/<branch>` as stale-ref; verified modern git (>=1.8.4) updates refs/remotes/origin/<branch> from this form - nits about validate_ts naming, require_admin_bearer 503 doc, OnCalendar catch-up behavior, SIGTERM-on-Unix-only doc Checks run: - `cargo nextest run -p vortex-bench-server -p vortex-bench-migrate`: 188 tests pass, 4 skipped (snapshot tests, exercised separately) - `cargo nextest run -p vortex-bench-server --test admin --run-ignored only`: 4 snapshot tests pass (including concurrent same-ts race) - `cargo clippy -p vortex-bench-server -p vortex-bench-migrate --all-targets --all-features`: clean - `cargo +nightly fmt --check -p vortex-bench-server -p vortex-bench-migrate`: clean - `yamllint --strict -c .yamllint.yaml .github/workflows/ci.yml`: clean - `bash -n` on every ops script: clean - `python3 -m py_compile scripts/post-ingest.py`: clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Connor Tsui <connor@spiraldb.com>
Cycle 2 re-review found 14+ must-fix items concentrated at the fix-boundary of cycle 1. Most importantly: cycle 1 incorrectly deleted the v2 publish workflow, which builds the still-present top-level benchmarks-website/Dockerfile (NOT the deleted server/Dockerfile). The PR description explicitly states "v2 keeps shipping unchanged until DNS flips" — cycle 1 broke that contract. Restored here. Applied (must-fix): - scope-drift revert: restore .github/workflows/publish-benchmarks-website.yml (cycle 1 deleted it under a misreading; the workflow builds the v2 top-level Dockerfile, not the deleted server/ one. v2 production deploy depends on it until DNS cutover.) - cross-cutting H1: dto.rs build_sha docstring says "full 40-hex git SHA" (was "12-char short form"; cycle 1 changed build.rs to emit the full SHA, this doc-comment was stale) - weak-exit-criteria: README quick-start step 4 starts only the deploy + backup timers, not vortex-bench-server (which would fail because the binary doesn't exist until deploy.timer's first fire) - error-path: admin.rs ROLLBACK failures now tracing::warn! instead of `let _ = ROLLBACK`; same in both snapshot tx and admin SQL tx - concurrency: migrate.sh now stops the deploy/backup SERVICES too (not just timers — an in-flight oneshot service keeps running when only its timer is stopped). Stops services + timers before pausing the server. - concurrency / error-path: migrate.sh trap restores the autopilot timers ONLY on success (tracked via migration_succeeded flag). On failure, leaves timers stopped so the operator can roll back without the deploy timer racing against the half-rolled-back DB. - error-path / doc-quality: migrate.sh failure messages include the WAL-file rollback step (cp also copies .wal; rollback needs to mv it back too) - bug: deploy.sh hash-equal restart-skip fast path now requires non-empty last_sha (only fires when the prior stamp was already health-verified). Without this, a deploy crashed between atomic_symlink and the restart would let the next deploy stamp success and never restart the server. - boundary: deploy.sh versioned binary names include $$ so two deploys within the same UTC second cannot collide on filename (the loser's binary would overwrite the prior versioned binary, potentially the rollback target) - doc-quality: ops/README.md manual-snapshot note clarifies that backup.sh tars the client-computed path from VORTEX_BENCH_SNAPSHOT_DIR, not the server-returned snapshot_dir (the cycle-1 wording oversold the response field's role) - weak-exit-criteria: install.sh's token-detection heuristic now sources the env file in a subshell and tests the RUNTIME variable values, not a grep against '^TOKEN=.+' (which false-positives on explicitly-empty `TOKEN=""` lines: the two quote characters satisfy .+ but the runtime value is empty) - doc-quality: admin.rs let _ = writeln!(out, ...) for fmt::Write on String now has an inline comment explaining why the Result is discarded (fmt::Write for String is infallible) Deferred (should-fix; revisit next cycle): - shell harness tests for deploy.sh / backup.sh / migrate.sh - EXPLAIN ANALYZE write → 403 instead of 5xx (defense-in-depth via READ ONLY tx is correct, but allow-list classification is cleaner) - post-ingest.py single-record-over-limit early failure - docker-compose port :3001 vs new admin listener default :3001 conflict on co-residency (v2 cleanup PR territory) - README.md / AGENTS.md references to publish workflow / docker-compose (intentionally preserved per "v2 stays shipping until DNS flip") Checks run: - `cargo nextest run -p vortex-bench-server -p vortex-bench-migrate`: 188 tests pass, 4 skipped (snapshot tests, run via CI's --run-ignored only step) - `cargo clippy -p vortex-bench-server -p vortex-bench-migrate --all-targets --all-features`: clean - `cargo +nightly fmt --check -p vortex-bench-server -p vortex-bench-migrate`: clean - `yamllint --strict` on ci.yml + restored publish-benchmarks-website.yml: clean - `bash -n` on every ops script: clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Connor Tsui <connor@spiraldb.com>
A Claude-routed fix-pass on cycle 2's commit (Codex hit its usage limit
mid-loop, so the fix-pass fell back to Claude per the auxiliary-arg
degrade policy) found 3 fix-introduced must-fix items plus a sudoers
gap the migrate.sh expansion would have hit on real hosts.
Applied (must-fix):
- doc/code: deploy.sh:130 vanished-commit branch now clears last_sha to
"" so the cycle-2 hash-equal restart-skip guard at line 190 sees an
empty last_sha and forces a full restart + /health verify in that
case too. Without this, the cycle-2 comment overclaimed what the
guard covered.
- cross-cutting: README.md manual rollback recipe (under
"I pushed a website change...") was stale relative to cycle-2's
migrate.sh failure messaging — now includes the .wal mv-back step
AND the systemctl start of deploy/backup timers (cycle 2 left them
stopped on failure; without the README mentioning that, an operator
following only the README leaves the autopilot permanently silent).
Also added an explicit paragraph explaining migrate.sh's new
stop-then-restore-on-success-only autopilot pause.
- cross-cutting: install.sh's empty-tokens follow-up instruction at
line 164 had told operators "sudo systemctl start vortex-bench-server
vortex-bench-deploy.timer vortex-bench-backup.timer", contradicting
cycle-2's README quick-start step 4 which dropped vortex-bench-server
(no binary exists until deploy.timer's first fire builds one). Now
matches: timers only. The "tokens present at install" branch
similarly stops trying to start vortex-bench-server.service.
Applied (cheap should-fix):
- security/scope: install.sh sudoers fragment expanded to cover
systemctl start/stop on vortex-bench-deploy.{service,timer} and
vortex-bench-backup.{service,timer}. Without this, cycle-2's
migrate.sh autopilot-pause would prompt for sudo password (or, in
the timer-fired non-interactive context, fail and silently no-op
via `|| true`). Both /bin/ and /usr/bin/ paths listed for AL2023 +
Debian-style hosts. Rewrote the fragment as multi-line with `\\`
continuations so the v3 unit list is grep-able.
- doc: migrate.sh failure trap inspect-with hint now includes
vortex-bench-deploy.service + vortex-bench-backup.service (cycle 2
stopped the .service units too; operators need to inspect those
too to catch a partial cargo build sitting in target/).
Deferred (should-fix; reviewer flagged but cost/value not worth this
cycle):
- install.sh whitespace-only token edge case: `[ -n " " ]` is true but
server validate_bearer_token rejects whitespace via trim(). Brief
noisy first-boot if an operator quotes whitespace in the env file;
not a security issue. Defer.
- deploy.sh PID-suffix prune ordering nit: within a same-second batch
the prune sorts by PID, not install order. Symlink-target check
prevents deleting the live binary; the worst case is non-deterministic
prev_target choice for a sub-second-spaced batch.
Checks run:
- `cargo nextest run -p vortex-bench-server`: 115 pass, 4 skipped
- `cargo clippy -p vortex-bench-server --all-targets --all-features`: clean
- `bash -n` on install.sh / deploy.sh / migrate.sh: clean
- `visudo -cf` on the sudoers fragment skeleton: parses
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor@spiraldb.com>
Cycle 4 ran with all 4 lenses routed to Claude (Codex still rate-limited).
3 of 4 lenses (correctness, maint, arch) independently converged on the
SAME fix-introduced must-fix from cycle 3:
cycle 3 expanded install.sh's sudoers fragment with per-unit
`systemctl <verb> <unit>` Cmnds, but migrate.sh:60 invokes
`sudo systemctl stop A B C D` with FOUR units on one argv. sudoers
matches argv exactly — the multi-unit form is NOT covered by the
per-unit allow rules. With `|| true` masking the denial, the
autopilot pause silently no-ops on any host where ec2-user isn't
also a passwordless wheel-group sudoer. The deploy.service+timer
and backup.service+timer race against the migrator's exclusive DB
access — the exact failure mode the cycle-3 commit message claimed
to close. The success-path restore (line 66) had the same shape;
a SUCCESSFUL migration would leave the autopilot permanently
stopped because the multi-unit start can't authorize either.
Applied (must-fix):
- bug: migrate.sh now stops + starts the v3 units one per sudo
invocation (4 stops + 2 starts), matching install.sh's per-unit
sudoers entries exactly. Dropped the `|| true` on the stops so a
real sudo failure surfaces in the journal instead of pretending the
autopilot is paused.
Applied (cheap should-fix):
- doc-quality: README quick-start step 4 + manual rollback recipe +
install.sh empty-tokens follow-up message all use one-line-per-unit
`systemctl start` invocations. Multi-unit forms in operator-facing
docs would silently fail under the (correct) tight sudoers model.
- doc-quality: README architecture diagram + restart.sh sample output
+ "Which build is actually running?" `readlink` example now show
the real `vortex-bench-server.<ts>.<pid>` versioned-binary form
(cycle 1's PID suffix that protects against same-second deploys).
Doc had been stale relative to the code since cycle 1.
- doc-quality: README "Backup hasn't run" diagnostic rewritten —
`http_status=000` (connection refused), not 503, is the symptom
of ADMIN_BEARER_TOKEN being unset (no admin listener bound).
503 from require_admin_bearer is a defensive belt-and-braces
branch that prod can't reach (admin router is unmounted in that
case); doc now says so.
Deferred from cycle 4 (should-fix; revisit only if a future cycle
re-flags):
- SnapshotTicket doc comment overclaims cross-process safety (the
in-process Mutex<HashSet> is correct for the single-process
supported deployment; cross-process race is documented as out of
scope but the wording could be tighter)
- install.sh tokens_set subshell silently swallows env-file source
errors (a malformed env yields empty capture and falls into the
"tokens not set" branch with no surfaced error)
- build_sha "byte-for-byte" claim ignores trailing newline in the
stamp file (`echo` appends \n; build_sha is .trim()'d)
- lib.rs module map's `db` entry dropped the read-backpressure
mention when the admin entry was inserted
- migrate.sh has no sync against an in-flight backup.service mid-curl
(systemctl stop interrupts cleanly but a partial S3 upload window
is observable in journalctl)
Cycle 4 spec-lens nits dismissed:
- cycle-3 commit body misattributes which README section it touched
("I pushed a website change..." vs "How do I re-run the migration?")
— commit message will live in git history as-is; README content
is correct
- router() vs public_router/admin_router carry-forward duplication
Checks run:
- `cargo nextest run -p vortex-bench-server -p vortex-bench-migrate`:
188 pass, 4 skipped
- `bash -n` on every ops script: clean
- `yamllint --strict` on changed workflows: clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor@spiraldb.com>
A Claude fix-pass on cycle 5's commit (8b2728806) found the cycle-5
sweep missed one occurrence of the multi-unit systemctl pattern plus
two doc drifts that should have ridden along.
Applied (must-fix):
- bug: migrate.sh:140's "server didn't respond on /health" stderr
rollback recipe still printed `systemctl start
vortex-bench-deploy.timer vortex-bench-backup.timer` on one line.
Cycle 5's `replace_all` matched the indented form (inside the
cargo-failed function body) but missed the unindented form here at
the top-level stderr block. That recovery path is the one operators
copy-paste verbatim under stress, and it's the exact sudoers-deny
pattern cycles 3-5 chased. Now split into per-unit lines, matching
the parallel cargo-failed block at lines 113-115 and the EXIT trap's
per-unit advice at 82-83.
Applied (cheap should-fix):
- doc-quality: deploy.sh header docstring step 7 said
`bin/vortex-bench-server.<ts>` but the code at line 214 writes
`${ts}.$$` (PID suffix; cycle 1 added it). Header now describes the
real `<ts>.<pid>` form AND mentions the staging-symlink + `mv -Tf`
rename(2) atomic-swap pattern (cycle 1) AND the re-probe of /health
after rollback (cycle 1). The header was the only remaining stale
doc-comment after cycle 5's README sweep.
- doc-quality: README "Backup hasn't run" diagnostic now uses the
literal error string `ERROR: /api/admin/snapshot returned 000` that
backup.sh:67 actually emits. The cycle-5 wording said
`http_status=000` which an operator grepping the journal would not
find. Semantic claim (000 from connection-refused, not 503 from a
defensive admin-not-configured branch) is unchanged.
Deferred (nit; not worth a re-flag):
- README ASCII architecture diagram's right-border `│` columns step
inward by a couple of characters on the new versioned-binary
annotation; pre-existing ragged borders in the same block
Checks run:
- `cargo nextest run -p vortex-bench-server -p vortex-bench-migrate`:
188 pass, 4 skipped
- `bash -n` on every ops script: clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor@spiraldb.com>
Applied 8 must-fix findings from the cycle-1 gauntlet review (synthesis
of fresh/correctness/maint/arch lenses).
* `scripts/post-ingest.py`: replace dead `benchmarks-website/planning/*`
pointers with live ones (records.rs, v3.rs, schema.rs) and add a
comment naming the SCHEMA_VERSION literal as the 4th coupled site.
* `benchmarks-website/server/src/schema.rs`: add a coupled-sites
callout on `SCHEMA_VERSION` naming `vortex-bench/src/v3.rs` and
`scripts/post-ingest.py` so a bump cannot drop the script silently.
* `benchmarks-website/AGENTS.md`: extend the "wire shapes are
coordinated" bullet to call out `scripts/post-ingest.py` as the 4th
coupled site for `SCHEMA_VERSION`; rewrite the LANDING_INLINE_N
zoom-out bullet to describe the shard-hydration shape (charts
initially load from `/api/artifacts/{generation}/groups/{slug}/shards/{i}`,
not from inlined landing payloads).
* `benchmarks-website/server/src/api/dto.rs`: rewrite the `GROUP_ORDER`
doc comment — the "only first group's chart payloads are inlined"
claim is gone post-pivot; the order now governs the disclosure list
only and every group hydrates from versioned shard artifacts.
* `benchmarks-website/server/src/html/landing.rs`: rewrite the
`landing_body` doc + `idx_iter` comment. Landing emits no
`chart-data-N` inline-script tags any more; the integer is now a
per-page-unique wiring id between `<canvas data-chart-index>` and the
toolbar controls.
* `benchmarks-website/ops/config/vortex-bench.env.example`: add a
commented `#KEEP_BINARIES=3` entry under the "ops scripts only"
section so the disk-full incident runbook's "edit the env file" path
has a template line to uncomment.
* `benchmarks-website/ops/deploy.sh`: document exit code 7 (rollback
to the previous binary ALSO failed `/health` — server is down) in
the leading exit-code matrix.
* `vortex-bench/src/v3.rs`: clamp the three saturation sentinels at
the server's signed type max. `duration_as_ns` now caps at
`i64::MAX as u64`; the two `u32::try_from(...).unwrap_or(u32::MAX)`
sites cap at `i32::MAX as u32`. Without this, a single overflowed
measurement saturates to `u64::MAX` / `u32::MAX` which the server's
`i64` / `i32` deserializers reject — 400ing the whole ingest envelope.
Deferred to a follow-up (should-fix items from cycle 1, ~14 total):
the closed-enum extension cost (introduce a Family trait so adding a
sixth fact table compile-time-enforces the four-way Record/ChartKey/
GroupKey/measurement_id coupling); `chart_kind` wire seam for non-line
render variants; the migrate classifier's storage collapse for
non-fan-out suites; `verify::display_query_group` divergence vs v3's
`group_name_query` for statpopgen/polarsignals; description-keyed-by-
display-name string coupling. None block cutover but each is in the
review for the next pass.
Signed-off-by: Connor Tsui <connor@spiraldb.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tor)
Introduces a new `family` module with a const `Family` struct + a `FAMILIES`
const array that ties each fact table's table name, chart and group slug
prefixes, `measurement_id` hash, `apply_record` insert, chart / group
collectors, and row count into one declaration. Five families today
(`QUERY_MEASUREMENTS`, `COMPRESSION_TIMES`, `COMPRESSION_SIZES`,
`RANDOM_ACCESS_TIMES`, `VECTOR_SEARCH_RUNS`); adding a sixth becomes one
new const + one entry in `FAMILIES`.
This is phase 1 of a multi-phase refactor identified by the recent gauntlet
audit (closed-enum extension cost: ~12 file edits per new fact table with
no compile-time enforcement linking the four parallel surfaces — Record /
ChartKey / GroupKey / measurement_id_*). Phase 1 introduces the registry
alongside the existing free functions; phase 2 migrates call sites
(`apply_record`, `chart_payload`, `collect_groups`, `collect_health`,
`SCHEMA_DDL`) to dispatch via `FAMILIES`; phase 3 moves the per-family
bodies into the family adapters and deletes the originals.
Behavior-preserving:
* Existing free functions in `api::charts`, `api::groups`, `db`, `ingest`
are unchanged — the family adapters delegate to them.
* Per-family `collect_*_chart`, `collect_*_group(s)`, and the existing
`insert_query_measurement` / `insert_vector_search` are bumped from
private to `pub(crate)` so the family adapters can reference them.
* `ingest::apply_record`'s three inline `Compression{Time,Size},
RandomAccess` insert branches are extracted into matching
`pub(crate) fn insert_compression_{time,size}` and
`pub(crate) fn insert_random_access` parallel to the existing two
named functions; `apply_record` itself becomes a clean five-arm
dispatcher.
* `RecordError` is bumped from private to `pub(crate)` so the family
adapters' `apply_record` field can carry it.
Tests:
* `cargo check -p vortex-bench-server`: clean.
* `cargo +nightly fmt --all --check`: clean.
* `cargo nextest run -p vortex-bench-server`: 117 passed, 0 failed.
* New tests in `family::tests`: `table_names_match_schema_tables` (the
registry agrees with `schema::TABLES`) and `slug_prefixes_are_distinct`
(chart and group prefixes are unique per family).
No on-disk changes (SCHEMA_DDL output unchanged, `measurement_id` hashes
unchanged, slug shapes unchanged). The pre/post DuckDB file is fully
compatible.
Signed-off-by: Connor Tsui <connor@spiraldb.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Family registry (phase 2)
Three load-bearing dispatch sites now route through the per-family
function-pointer fields on `family::Family`, instead of spelling out
five-arm matches inline:
* `ingest::apply_record`: was a five-arm `match record { Record::* => insert_* }`
dispatcher; now one line: `(family_for_record(record).apply_record)(tx, record)`.
* `api::charts::chart_payload`: was a five-arm `match key { ChartKey::* => collect_*_chart(...) }`
dispatcher with bespoke argument shuffling per variant; now one line:
`(family_for_chart_key(key).collect_chart_for_key)(conn, key, window)`.
* `api::groups::collect_groups`: was five hand-written calls + flatten;
now `FAMILIES.iter().for_each(|f| groups.extend((f.collect_groups)(conn)?))`.
Adding a sixth fact table no longer requires editing these three
dispatchers — only the new const in `family.rs` and one entry in
`FAMILIES`.
`Family::apply_record`'s `#[allow(dead_code)]` Phase-1 stub allowance
comes off (the field is now read by `ingest::apply_record`).
Behavior-preserving:
* No on-wire change, no on-disk change. The per-family adapter functions
in `family.rs` still delegate to the existing free functions in
`db`, `ingest`, `api::charts`, `api::groups`.
* `cargo check -p vortex-bench-server`: clean.
* `cargo nextest run -p vortex-bench-server`: 117 passed.
* `cargo +nightly fmt --all`: clean.
Phase 3 moves the bodies of `insert_*`, `collect_*_chart`,
`collect_*_group(s)`, and `measurement_id_*` INTO their `family.rs`
adapters and deletes the originals; phase 3 also splits `SCHEMA_DDL`
into one `commits` DDL plus a per-family DDL on each `Family` so the
schema-DDL coupling becomes registry-driven.
Signed-off-by: Connor Tsui <connor@spiraldb.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the single `schema::SCHEMA_DDL` const with one DDL per fact table — `COMMITS_DDL` for the dim, plus `QUERY_MEASUREMENTS_DDL` / `COMPRESSION_TIMES_DDL` / `COMPRESSION_SIZES_DDL` / `RANDOM_ACCESS_TIMES_DDL` / `VECTOR_SEARCH_RUNS_DDL` for the facts — and adds a `schema_ddl: &'static str` field on `family::Family` so every family declaration ties its slug prefixes, hash, ingest, read collectors, AND its CREATE TABLE block together in one const. `db::open()` now applies `COMMITS_DDL` first, then loops over `family::FAMILIES` to apply each family's `schema_ddl`. Adding a sixth fact table no longer needs an edit in `db::open()` or in `schema::SCHEMA_DDL` — just one new DDL const in `schema.rs` plus the matching `Family` entry. `admin::write_snapshot` (the `/api/admin/snapshot` writer) assembles `schema.sql` from `COMMITS_DDL` + every family's `schema_ddl` so the snapshot artifact stays byte-equivalent to the prior monolithic `SCHEMA_DDL` output. Adds `family::tests::measurement_id_per_family_seed_keeps_overlapping_dims_distinct`, a regression test that builds a `CompressionTime` and a `CompressionSize` with overlapping dim tuples and asserts the hashes are non-zero and distinct. Locks down the per-family `hasher_for(<table_name>)` seeding behaviour the registry depends on; any accidental hash-collision-via- seed-collapse fails loudly. Tests: * `cargo check -p vortex-bench-server`: clean. * `cargo nextest run -p vortex-bench-server`: 118 passed (was 117; the new hash-stability test brings it to 118). * `cargo +nightly fmt --all`: clean. Public API note: `schema::SCHEMA_DDL` is removed and replaced by the six per-family DDL constants. Internal callers (`db::open`, `admin::write_snapshot`) are updated. `public-api.lock` is regenerated in a follow-up commit. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`clippy::redundant_static_lifetimes` flagged `&[&'static Family]` in the const array signature; `&[&Family]` infers the same lifetime in const position. No behaviour change. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 split `vortex_bench_server::schema::SCHEMA_DDL` into `COMMITS_DDL` + per-family DDL constants, but `benchmarks-website/migrate/src/migrate/mod.rs::open_target_db` was still importing the removed `SCHEMA_DDL`. Migrate now applies `COMMITS_DDL` first and then iterates `family::FAMILIES`, identical to `vortex_bench_server::db::open()`. `cargo nextest run -p vortex-bench-migrate`: 73 passed. `cargo clippy -p vortex-bench-migrate --all-targets --all-features`: clean. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rnings` `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p vortex-bench-server` was failing on eight intra-doc-link errors. Five were introduced by the Family refactor (phase 3); two were pre-existing private-link issues. Refactor-introduced: * `benchmarks-website/server/src/schema.rs` — the five per-family DDL doc-comments referenced `crate::family::<CONST>::schema_ddl`. Rustdoc refuses associated-item paths through `const` (a const is not a module/type), so the links broke. Each rewrites to "Wired into the `schema_ddl` field of [`crate::family::<CONST>`]" — links to the const, mentions the field name as backtick-quoted text. * `benchmarks-website/server/src/family.rs` — module doc referenced `crate::api::groups` which is ambiguous (there is both a function and a module with that name). Switches `[`crate::api::groups`]` and the one `crate::api::charts` reference to the `[`mod@crate::api::<name>`]` module-disambiguator form. Pre-existing private-link cleanups (the public `//!` module-doc was linking to a private / `pub(crate)` constant, which rustdoc rejects under strict warnings): * `benchmarks-website/server/src/admin.rs:41` — `[`ADMIN_SQL_ROW_LIMIT`]` → backtick-only `ADMIN_SQL_ROW_LIMIT`. * `benchmarks-website/server/src/api/window.rs:42` — `[`MAX_NUMERIC_COMMIT_WINDOW`]` → backtick-only. `scripts/post-ingest.py` was already ruff-clean — the docstring rewrites in cycle 1 did not introduce any new violations. Verification: * `uv run ruff check scripts/post-ingest.py`: clean. * `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo check -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo clippy -p vortex-bench-server -p vortex-bench-migrate --all-targets --all-features`: clean. * `cargo +nightly fmt --all --check`: clean. * `cargo nextest run -p vortex-bench-server -p vortex-bench-migrate`: 191 passed. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntrast Two small UI fixes against the v3 benchmarks website. ## Favicons / tab metadata The v3 head was missing the five `<link>` tags v2 carries for browser tab + home-screen branding (favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png, site.webmanifest). Tabs rendered without an icon, and adding to a home screen showed the default placeholder. * `server/src/html/static_assets.rs`: five new `include_bytes!` constants that reach into the v2 `public/` directory (matching the existing pattern for the two logo PNGs) and five matching `serve_*` handlers. * `server/src/html/mod.rs`: five new `/favicon.ico`, `/favicon-16x16.png`, `/favicon-32x32.png`, `/apple-touch-icon.png`, `/site.webmanifest` routes mounted on the public router. * `server/src/html/render.rs`: a new `favicon_links()` helper emitting all five `<link>` tags. Wired into both `render_page` (the landing / chart / group pages) and `error_page` so even a 404 carries a tab icon. * `STATIC_ASSET_VERSION` bumped from `bench-v3-ui-22` to `bench-v3-ui-23` so cached browsers pull the new bytes. ## Dark-mode contrast (cards-vs-page inversion) The v3 dark mode used the same hex values v2 does (`#050507`, `#121219`, etc.) but assigned them to surfaces in the inverted role: `--page-bg: #121219` (the lighter grey) and `--card-bg: #050507` (the near-black) made cards read as dark voids floating on a dark-grey page. v2's pattern is the opposite — `--bg-color: #050507` for the body and `--bg-secondary: #121219` for elevated surfaces, so cards gently lift above the page rather than punching into it. Swapped the dark-mode assignments in both the `@media (prefers-color-scheme: dark)` and `:root[data-theme="dark"]` blocks: * `--bg` `#050507` → `#121219` (elevated controls, dropdowns, toolbars) * `--page-bg` `#121219` → `#050507` (body background — deepest) * `--card-bg` `#050507` → `#121219` (chart cards — elevated) * `--code-bg` `#121219` → `#050507` (code blocks / summary inputs at page depth, mirroring light-mode's `code-bg == page-bg`) Light mode untouched. `.sticky-header`'s `rgba(18, 18, 25, 0.95)` dark-mode background unchanged — that value now matches the new elevated colour, so the header continues to read as an elevated surface sitting above the deeper page. ## Verification * `cargo check -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo clippy ... --all-targets --all-features`: clean. * `cargo nextest run -p vortex-bench-server -p vortex-bench-migrate`: 118 + 73 = 191 passed. The three HTML snapshots (`landing_page.snap`, `chart_page_query.snap`, `group_page_query.snap`) regenerated to include the new `<link>` tags. * `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps`: clean. * `cargo +nightly fmt --all --check`: clean. * `uv run ruff check scripts/post-ingest.py`: clean (a passing linter collapsed one multi-line argparse help string into a single line while I was iterating; included in this commit). Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…l back bolding Three small follow-up tweaks against the cycle-1 UI sweep: ## Favicons Replaced the v2 `public/favicon-*.png` set (every file had a white background baked in, which rendered as a glaring white square on dark-mode tabs) with two transparent sigil-only PNGs supplied from the "Vortex Logo (V2)/Only Iso/Without Margin" set: * `server/static/icon-light.png` — 500x500 black sigil on transparent bg, for light-mode tabs. * `server/static/icon-dark.png` — 500x500 white sigil on transparent bg, for dark-mode tabs. `render::favicon_links` emits four `<link>` tags now: a light-mode `prefers-color-scheme` icon, a dark-mode `prefers-color-scheme` icon, an unmediated default (dark sigil, since the developer audience skews dark-mode), and an `apple-touch-icon` (dark sigil over whatever tile colour iOS picks). The five v2-public favicon include_bytes (.ico, favicon-16, favicon-32, apple-touch, site.webmanifest) and their matching `serve_*` handlers + routes are dropped. ## Title "bench.vortex.dev" replaced with "Vortex Benchmarks" in every browser tab title — landing, chart page, group page, and error page. The domain reads as a URL rather than a page name; "Vortex Benchmarks" matches v2's `<title>` and the in-page `.site-title` text. The doc-comment / aria-label uses of `bench.vortex.dev` (in `lib.rs` and `render::site_header`'s logo aria-label) are left as-is — those are correctly identifying the deployed site, not the page name. ## Bold dial-back v3's CSS reached for `font-weight: 600` and even `650` on a dozen elements; v2 mostly uses `500`. The cumulative effect on landing was the "too much bold text" feel. Per-element changes (no other CSS touched): * `.site-title` 600 -> 500 (header brand text) * `.chart-card-title` 600 -> 500 (per-chart card title) * `.repo-link` 600 -> 500 (GitHub link) * `.filter-chip--all` 600 -> 500 (filter pill) * `.scores-title` 650 -> 600 (summary card heading) * `.score-rank` 650 -> 600 (summary rank chip) * `.score-series` 550 -> 500 (summary series label) * `.score-value/.score-runtime` 650 -> 600 (summary numbers) Structural headings stay at 600: `.group-name` (disclosure header) and `.page-header h1` (chart/group page heading) — those want some weight. `STATIC_ASSET_VERSION` bumped from `bench-v3-ui-23` to `bench-v3-ui-24` so cached browsers see the new icons + CSS + title. ## Verification * `cargo check -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo clippy --all-targets --all-features`: clean. * `cargo nextest run`: 118 + 73 = 191 passed. Landing / chart / group snapshots regenerated for the new title text and `<link>` set. * `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps`: clean. * `cargo +nightly fmt --all --check`: clean. * `uv run ruff check scripts/post-ingest.py`: clean. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v3 was falling through to each OS's system font (San Francisco / Segoe UI / Roboto). v2 loads Geist (sans + mono) from unpkg and Funnel Display from Google Fonts via `<link>` tags in `index.html`, then uses Geist for body, Funnel Display for headings, and Geist Mono for code. v3 now mirrors that exactly. * `render::web_font_links`: a new head-block helper emitting the same five `<link>` tags v2 carries — two `preconnect` hints to fonts.googleapis.com / fonts.gstatic.com, plus three stylesheet links for Funnel Display (Google Fonts), Geist Sans (unpkg), and Geist Mono (unpkg). Wired into both `render_page` and `error_page` next to the existing `favicon_links()` call. * `style.css` body: font-family now leads with `"Geist"`, falling back to the system stack the line was using before. * `style.css` headings: a new `h1, h2, h3, h4, h5, h6` rule sets Funnel Display + the same `-0.02em` letter-spacing v2 has on its matching rule. The site title, group disclosure header, chart-card title, page-header h1, scores-title, etc. all inherit this. * `style.css` monospace: the two `ui-monospace, ...` stacks (chart series formatter + score-runtime metrics) now lead with `"Geist Mono"`. CDN-loaded (not bundled). v2 already loads from the same hosts so the network cost is unchanged; if a user previously hit v2 their browser will already have the bytes cached when they land on v3. `STATIC_ASSET_VERSION` bumped `bench-v3-ui-24` -> `bench-v3-ui-25` so cached browsers re-fetch style.css. ## Verification * `cargo check -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo clippy --all-targets --all-features`: clean. * `cargo nextest run`: 191 passed (landing / chart / group snapshots regenerated to include the five new font `<link>` tags). * `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps`: clean. * `cargo +nightly fmt --all --check`: clean. * `uv run ruff check scripts/post-ingest.py`: clean. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ode elevation Two focused style passes following an audit of v2's `src/styles/index.css` against v3's `server/static/style.css`. The previous font commit (cc4554f) loaded the Geist + Funnel Display CDNs and applied Funnel Display to `h1, h2, ..., h6` — but several load-bearing v3 elements aren't `<h*>` tags, so they kept inheriting Geist. And four dark-mode background surfaces were wired to the wrong elevation pair, inverting the cards-vs-page hierarchy v2 uses. ## Font reach (matches v2's font assignments) * `.group-name` (the disclosure header text — "Random Access", "Compression", "TPC-H NVMe SF=1", etc.) is a `<span>` in `landing.rs`, so the `h1-h6` Funnel Display rule didn't reach it. Now sets `font-family: "Funnel Display"` explicitly, mirroring v2's `.benchmark-title` rule. Letter-spacing also tightened to `-0.02em` to match v2's heading tracking. * New top-level `code, pre` rule applies Geist Mono with the same fallback stack the inline mono usages already declare — mirrors v2's `code, pre` rule. The `<code>` tags emitted by `chart.rs::chart-meta` and elsewhere now render in mono. * `.site-title` font-size bumped 1.25rem -> 1.5rem to match v2. ## Dark-mode elevation hierarchy v2's pattern: elevated surfaces (disclosure headers, sticky-header inner bar, summary cards) use `--bg-secondary` (`#121219` in dark); page-level surfaces (body, chart-grid behind cards) use `--bg-color` (`#050507`). v3's variables already map correctly — `--bg` / `--card-bg` = elevated, `--page-bg` / `--code-bg` = page — but four surfaces were pointed at the wrong pair: * `.group-summary` (disclosure header): `var(--code-bg)` -> `var(--card-bg)`. The disclosure bar now lifts off the page like v2's `.benchmark-header`. * `.group-details > .chart-grid` (chart grid inside the group): `var(--bg)` -> `var(--page-bg)`. The grid behind cards now matches the body, restoring v2's `.benchmark-graphs` pattern. (Previously the grid was *lighter* than the body — the "slightly different color than the black background" the user noticed.) * `.benchmark-scores-summary` (summary card inside a group): `var(--code-bg)` -> `var(--card-bg)`. Summary card now elevates with v2's `.benchmark-scores-summary`. * `.header-content` (sticky-header inner bar): `var(--code-bg)` -> `var(--bg)`. Header bar now elevates like v2's `.header-content` over `--bg-secondary`. (Also dropped a duplicate `background:` declaration the rule had carried.) ## Cache busting `STATIC_ASSET_VERSION` bumped `bench-v3-ui-25` -> `bench-v3-ui-26` so cached browsers re-fetch the new CSS. ## Verification * `cargo check -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo clippy --all-targets --all-features`: clean. * `cargo +nightly fmt --all --check`: clean. * `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps`: clean. * `cargo nextest run`: 191 passed. Three HTML snapshots regenerated for the version-bump URL change. * `uv run ruff check scripts/post-ingest.py`: clean. Chart.js axis / legend / tooltip text remains canvas-rendered at Chart.js defaults — opted out of touching `chart-init.js` in this pass per the planning conversation. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mmit `build.rs` was already capturing `VORTEX_BENCH_BUILD_SHA` for `/health`; the SHA was just invisible from the rendered pages. Adds a small muted footer at the bottom of every page (landing, chart, group, and the error page) showing `build <7-char>` linked to `https://github.com/vortex-data/vortex/commit/<full-sha>` so operators and curious visitors can trace what they're looking at to a source revision. * `render::site_footer` — new helper emitting a `<footer class="site-footer">` block. The link is open-in-new-tab + `rel= "noopener noreferrer"` (matching the existing GitHub header link). Falls back to the literal `"unknown"` label without a link when built outside a git checkout (shallow CI / source tarball). * Wired into both `render_page` and `error_page` after `main { ... }`. * `.site-footer` CSS: small, muted, centered, mono SHA. Accent-colour hover on the link. Test snapshots: insta's `add_filter` (gated on the `filters` feature, now enabled on the server's dev-dep) redacts the captured SHA so the locked HTML snapshots don't churn on every commit: * `[0-9a-f]{40}` inside the GitHub URL → `<build-sha>` * `>[0-9a-f]{7}</code></a>` (the short SHA inside the linked code tag) → `><build-sha></code></a>`. The `"unknown"` fallback isn't hex, so it doesn't collide. `STATIC_ASSET_VERSION` bumped `bench-v3-ui-26` -> `bench-v3-ui-27` so cached browsers re-fetch the new CSS. ## Verification * `cargo check -p vortex-bench-server -p vortex-bench-migrate`: clean. * `cargo clippy --all-targets --all-features`: clean. * `cargo +nightly fmt --all --check`: clean. * `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps`: clean. * `cargo nextest run`: 191 passed. The three HTML snapshots regenerated with `<build-sha>` redactions in place; sanity-checked that the redaction pattern fires for the new footer fragment. * `uv run ruff check scripts/post-ingest.py`: clean. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Cross-cutting must-fix items surfaced by the 11-reviewer parallel gauntlet across server / ops / frontend+CI slices. Restored v2 publish workflow: * `.github/workflows/publish-benchmarks-website.yml` is back. The two deletions on this branch (1ab3edd, a62c2d0) killed v2 image rebuilds while v2 is still production per AGENTS.md / README.md. Defer the deletion to the post-cutover v2 cleanup PR. Em-dash sweep: * 142 em-dashes replaced with ASCII hyphens / sentence breaks across 28 newly-modified files (server src/tests/static, ops scripts + systemd units + docs, AGENTS.md / README.md / BOOTSTRAP.md, scripts/post-ingest.py, rerun-approvals.yml, ci.yml). Pinned the HTML title format change in the chart/group page snapshots. Docs vs code drift: * ARCHITECTURE.md retention paragraph now reflects RETAINED_PREVIOUS_ GENERATIONS=8 (was "active + most recent previous"). * AGENTS.md amends the no-server-side-cap key decision to codify the MAX_NUMERIC_COMMIT_WINDOW=1000 numeric clamp as DoS mitigation; `?n=all` remains uncapped. Coupled-site list for SCHEMA_VERSION no longer mis-says "fourth site". ADMIN_BEARER_TOKEN added to the local-dev block. New v3-ingest-now-load-bearing-in-CI note. * BOOTSTRAP.md: Phase 5.4 expected milestones now match deploy.sh output verbatim. Phase 4.1 fixed (backup.service is `static`, not `enabled`). Phase 7.A migration recipe references the migrator's `--help` instead of asserting a flag it doesn't have. Phase 7.B / 8.3 drop redundant `sudo -u ec2-user`. Phase 7 numbering normalized to 7.A / 7.B / 7.C / 7.D. Phase 8.5 guards `$ts` with `: "${ts:?...}"`. Failure-mode table extended with deploy exit codes 4/5/6/7 (including a recipe for exit 7 = server down). Cross-link to README's "Failure modes (conceptual)" anchor fixed. Lifecycle math normalized to 168 (was 168/170 mixed). * README.md (ops) lifecycle math normalized to 168. * migrate.sh header comment names `bench.prev-<ts>.duckdb` correctly. Server code: * admin.rs `validate_read_only` + `ensure_single_statement` now skip leading and trailing SQL comments correctly (`-- x\nSELECT 1` and `SELECT 1; /* note */` no longer rejected with 403). * admin.rs `value_ref_to_json` renders Decimal as Display and Timestamp as a structured `<unit>:<raw>` string; Debug-formatted enum names no longer leak through for DuckDB temporal types. * read_model.rs `accepts_encoding` rejects malformed q-values (`q=foo`, `q=inf`, `q=2`) and non-finite / out-of-range values per RFC 9110; was previously accept-on-parse-failure. * main.rs resolves both bind specs BEFORE opening any TCP listener so the loopback / distinct-binds checks run against the parsed SocketAddr rather than briefly opening a public port and then refusing startup. `ensure_distinct_binds` now also catches the 0.0.0.0 + 127.0.0.1 same-port overlap (previously silently EADDRINUSE'd at bind time with no actionable diagnostic). * app.rs `router()` now reuses `public_router` + `admin_router` instead of copy-pasting both bodies; closes the prod/test parity drift that would silently miss a public-route addition. Ops: * install.sh validates the sudoers fragment via `visudo -cf` on a tempfile and only `mv`s into `/etc/sudoers.d/vortex-bench` on success (a syntax error no longer locks the host out of sudo). Removed the `openssl rand -hex 32` calls that printed live tokens to stdout / journal. Dropped the dead `/var/log/vortex-bench/` directory creation. Preflight checks for git/cargo/openssl/jq/ flock/aws/tar/gzip/curl up front. ops/ symlink swap is now stage+rename atomic. * deploy.sh: dead `benchmarks-website/Cargo.toml` path-filter entry removed. Stamp-file writes are temp+rename atomic. Prune glob is narrowed to the `[0-9]*` timestamp form so a crashed atomic_symlink staging symlink (`.new.<pid>`) cannot displace real binaries from the keep set. The systemctl-restart-failure rollback path now re-probes /health and escalates to exit 7 (server down) if the rolled-back binary is also broken. * migrate.sh: EXIT trap installed BEFORE the autopilot stop calls so a partial stop still triggers restore. /health-failure path stops the server before printing rollback instructions (the earlier order had `Restart=on-failure` looping a broken/half- migrated server against the new DB while the printed `mv` raced the live open file handle). Migrator argv no longer logged verbatim (future flags carrying secrets would leak to journal). * backup.sh: flock guard against itself + deploy. Completeness check enumerates expected files before tar+upload (incomplete snapshots no longer ship to S3 as "valid" restore artifacts). Retry `aws s3 cp` transient failures with 2s/8s/30s backoff. Bearer-token write to the auth-header file is wrapped in `set +x` guards so `bash -x backup.sh` does not leak it. * inspect.sh: same `set +x` guard. Stale "here-doc" comment now describes the actual jq-based body construction. * restart.sh: SERVER_URL precedence sequence corrected (env file wins over the local default, matching sibling scripts). systemd hardening: * server.service: full Restrict* / Protect* / SystemCallFilter / CapabilityBoundingSet=empty block. ConditionPathExists guard on the binary symlink so `start` before the first deploy fails cleanly instead of emitting confusing execve errors. * backup.service: matching hardening block (deploy.service still needs cargo build, so it stays at the ec2-user default). * deploy.timer: `Persistent=true` for reboot-resilience symmetry with backup.timer. * deploy.service: `SuccessExitStatus=0 1` so lock-contention is not a noisy failed unit. Tests: * common/mod.rs: insta SHA-redaction regex anchored on the `class="site-footer-sha"` attribute. Was previously over-broad, also redacting every ingested-fixture commit URL (1111..., etc.) in the chart/landing snapshots and losing coverage of correct URL rendering. The hex match is `{7,40}` so disambiguating short-SHAs are still caught. * chart_page_query.snap / group_page_query.snap regenerated to surface the real fixture commit URLs. * chart_api.rs: `chart_page_window_caps_commits` no longer a tautology; asserts huge_count <= MAX_NUMERIC_COMMIT_WINDOW AND equals min(MAX_NUMERIC_COMMIT_WINDOW, fixture). * admin.rs: validate_ts bad-cases extended with ISO-8601-looking inputs (`:`, `+`, `.`). ASCII-table header check now symmetric with the data-row check on the trailing `│`. CI-invocation comment now matches the cargo nextest form ci.yml uses. * static_assets.rs: new favicon assets `/static/icon-light.png` / `/static/icon-dark.png` added to the served-routes loop. chart-init.js / AGENTS.md: * LANDING_INLINE_N constant removed; the apologetic four-line AGENTS.md justification is gone. Comment reference at the ensureFullHistory boundary rephrased. post-ingest.py: * `chunk_envelopes` rewritten to be O(N) instead of O(N^2) per record (was re-encoding the entire growing batch each loop). * First-record-exceeds-cap is fail-fast with a clear message instead of shipping an over-cap chunk that 413s at the server. Checks: * cargo check -p vortex-bench-server / vortex-bench-migrate: pass * cargo nextest run -p vortex-bench-server: 118 passed, 4 skipped Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Family registry Phase 3 - complete the "spine" contract. The cycle-1 review surfaced that the Family registry's headline "adding a sixth fact table is one new const" claim was silently false at three sites: `collect_health` hardcoded five COUNT calls, `api::dto::RowCounts` was a six-field struct, and `slug.rs` kept a parallel `PREFIX_QUERY` / `PREFIX_QUERY_GROUP` / ... const table that duplicated each Family's `chart_slug_prefix` / `group_slug_prefix`. `schema::TABLES` was a hand-listed parallel source of truth held in agreement by a test. `family_for_group_key` was `pub fn` with zero callers. What changed: * `api::dto::HealthResponse.row_counts` is now a `BTreeMap<&'static str, i64>` keyed by table name; the JSON wire shape (`row_counts. commits`, `row_counts.query_measurements`, ...) is unchanged. `RowCounts` struct deleted. * `api::collect_health` iterates `family::FAMILIES` plus the `commits` dim table. Adding a sixth fact table now picks up in `/health` automatically. * `schema::TABLES` is now a `LazyLock<Vec<&'static str>>` derived from `commits` + `FAMILIES.iter().map(|f| f.table_name)`. * `admin::copy_each_table` iterates `TABLES.iter()` (the deref needed because LazyLock<Vec<_>> is not a slice literal). * `slug.rs` drops the ten `PREFIX_*` const declarations; `ChartKey::prefix` and `GroupKey::prefix` now consult `family::family_for_chart_key(self).chart_slug_prefix` and `family::family_for_group_key(self).group_slug_prefix`. That also gives `family_for_group_key` its first caller, closing the dead `pub fn` finding. * `family.rs` module doc is rewritten: the prior "Phase 1 status" framing is replaced with a "The 'spine' contract" section that enumerates the call sites dispatching through the registry today. The header comment over the per-family declarations no longer claims "Phase 3 will move bodies into the adapters"; that scaffolding-doc-comment about pending future work is gone. * `table_names_match_schema_tables` is reoriented as `schema_tables_derived_from_families`, pinning the derivation rule explicitly in case someone replaces TABLES with a hand- written list again. Other clean-ups carried in this commit: * `html::render::site_footer` doc-comment continuation reformatted (clippy::doc_lazy_continuation) - a side effect of the cycle-1 em-dash sweep that did not surface until this build. Checks: * cargo check -p vortex-bench-server: pass * cargo clippy -p vortex-bench-server --all-targets: pass, no warnings * cargo nextest run -p vortex-bench-server: 118 passed, 4 skipped Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cycle 2 re-review flagged 3 must-fix doc-vs-code mismatches that my
cycle 1 commit INTRODUCED (H1 cross-reference drift), plus 4 should-
fix coverage gaps for the new behaviours.
Must-fix doc-vs-code mismatches (cycle 1 induced):
* BOOTSTRAP Phase 5.4 used ASCII '->' in the literal-grep substrings,
but deploy.sh emits Unicode '→' at lines 241 / 315. An operator
running `journalctl -u vortex-bench-deploy | grep 'swapped symlink
->'` would get zero matches and assume the deploy hung. Switched
the substrings to '→' with a sentence calling out the literal.
* Phase 5.4 footnote listed exit codes wrong ('2 git fetch, 3 git
rev-parse' against deploy.sh's actual '2 config error, 3 git
fetch/checkout'). Pointed the footnote at the symptom table in
this file (which has the correct rows already) and corrected the
inline summary.
* Phase 4 still said install.sh creates '/var/log/vortex-bench/'
even though the cycle-1 install.sh edit removed it.
Should-fix carry-overs (cycle 2 surfaced):
* migrate.sh's /health-failure stop used `|| true` which silently
swallows a sudo stop failure right before instructing the operator
to `mv` the prev DB over a possibly-still-running server. Now
errors out with an explicit "do NOT run the mv lines below until
the server is verifiably stopped" message and exits 4.
* admin.rs `value_ref_to_json` doc-comment claimed RFC 3339 for
Timestamp but the implementation emits '<unit>:<raw>' (the
inline comment already documented the real format). Doc-comment
rewritten to match.
* AGENTS.md's two `?n=` bullets said "HTML routes default to
?n=all", but the actual default is materialized latest-100;
`?n=all` is the explicit opt-in chart-init.js uses once on the
zoom-out hop. Reworded both bullets.
* chart_api.rs comment had `<=` HTML entity instead of `<=` in a
Rust comment block.
Missing regression tests (cycle 2 should-fix):
* ensure_distinct_binds: 0.0.0.0:3000 + 127.0.0.1:3000 must reject;
0.0.0.0:3000 + 127.0.0.1:3001 must pass; [::]:3000 + [::1]:3000
must reject. Pins the cycle-1 same-port-across-unspec-and-loopback
logic.
* accepts_encoding: malformed q-values (q=foo, q=, q=2, q=1.5,
q=-0.1, q=NaN, q=inf) all reject; canonical q=1 / q=1.0 / q=0.001
all accept. Pins the cycle-1 RFC 9110 tightening.
* admin SQL leading / trailing comment handling (5 new integration
tests): -- line comments and /* block */ comments both
before SELECT and after the trailing semicolon must produce 200;
a second statement after a trailing comment must produce 403.
Pins the cycle-1 skip_leading_noise / ensure_single_statement
fixes.
Checks:
* cargo nextest run -p vortex-bench-server: 127 passed (up from 118)
+ 4 skipped
Signed-off-by: Connor Tsui <connor@spiraldb.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two prose-only follow-ups the cycle-4 fix-pass review flagged as nits before issuing `overall: accept`. Both are wording corrections that do not change any logical behaviour. * BOOTSTRAP.md Phase 5.4: prior wording claimed deploy exit codes 1-7 are "surfaced row by row in [the symptom table] below", but the symptom table only has rows for exit codes 4-7. An operator looking up exit 3 would scroll the table and find nothing. Reworded to point exit codes 1-3 at the journal (self-explanatory there) and only claim row-coverage for 4-7. * migrate.sh /health-failure CRITICAL message: prior wording said "Do NOT run the rollback mv lines below" but on the exit-4 path the script bails before printing the mv lines. Changed "mv lines below" to "documented rollback 'mv' commands" so the instruction is anchor-free. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
790eaad to
bbc364f
Compare
Summary
http://ec2-18-219-54-101.us-east-2.compute.amazonaws.com:3000/
Hopefully the Benchmarks V3 finale. The website is one Rust binary + one DuckDB file managed by
systemd:
vortex-bench-deploy.timerpollsorigin/developevery 60s, rebuilds + atomic-swaps thebinary on website-touching commits,
/health-verifies, rolls back on failure.vortex-bench-backup.timerfires hourly, calls a new loopback-onlyPOST /api/admin/snapshot(per-table Vortex export under one
READ ONLYtransaction),tar czfs the result, and uploads tos3://vortex-benchmark-results-database/v3-backups/<ts>.tar.gz. Admin endpoints (snapshot +read-only
POST /api/admin/sql) sit on a separateADMIN_BEARER_TOKEN-gated listener that refusesto start on a non-loopback bind. v2 keeps shipping unchanged via the existing publish workflow until
DNS flips.
Operator runbook (one-time install on a fresh EC2 host, day-to-day, recovery):
benchmarks-website/ops/README.md. On a clean box,git clonethe repo, edit
INGEST_BEARER_TOKEN+ADMIN_BEARER_TOKENin/etc/vortex-bench.env,./benchmarks-website/ops/install.sh.Admin cheat sheet (generated by claude)
Status — "what's the state right now?"
Manual triggers — "do something now"
Logs — "what happened?"
Pause / resume the autopilot — "stop fiddling for a minute"
Config changes — "edit settings"
Recover from S3 — "restore a backup"
The 5 commands you'll actually use day-to-day:
restart.sh,force-rebuild.sh,inspect.sh,journalctl -fu …,curl /health | jq. Everything else is rare.