This document is the versioned contract between the benchmark emitters (which run in
the vortex-data/vortex monorepo) and the storage + read service in this repository. It is
anchored to SCHEMA_VERSION (currently 1). The emitters are owned by the monorepo
and are unchanged by this repository; this repo owns the ingest contract, the schema, and the
read service.
Scope. There is one ingest path: the monorepo's
scripts/post-ingest.py --postgreswrites emitter records directly into the hosted RDS Postgres and then pings this repo's read service to flush its cache. The retired v3 HTTP ingest path (POST /api/ingestinto a Rust server) is described indocs/legacy.md; the record shapes on the wire are unchanged from that era, which is why the producer flag is still spelled--gh-json-v3.
SCHEMA_VERSION is a single integer that keeps the producer wire shape and the reader in
lockstep. Bumping it is a coordinated, multi-site change.
| Anchor | File | Form |
|---|---|---|
| Source of truth | web/lib/schema-version.ts |
export const SCHEMA_VERSION = 1; |
The consistency check in web/lib/schema-version.test.ts asserts this file and the anchor
quoted in this document agree automatically.
These live in the vortex-data/vortex monorepo and cannot be verified by this repo's CI.
A SCHEMA_VERSION bump must be coordinated with them in the same logical change:
| Site | Role |
|---|---|
vortex-bench/src/v3.rs (the --gh-json-v3 emitter) |
Producer-side wire-shape source of truth |
scripts/post-ingest.py |
CI ingest writer; hardcodes the version as a Python literal that must equal the value above |
The monorepo emitter vortex-bench --gh-json-v3 <path> writes JSONL of bare records only —
one JSON object per line, no envelope. records are discriminated by a kind field
(snake_case). The five kinds and their destination fact tables:
kind |
Destination table |
|---|---|
query_measurement |
query_measurements |
compression_time |
compression_times |
compression_size |
compression_sizes |
random_access_time |
random_access_times |
vector_search_run |
vector_search_runs |
Each record's fields are defined by the producer structs in the monorepo's
vortex-bench/src/v3.rs and match the column names of its fact table (see
migrations/ for the DDL). scripts/post-ingest.py validates and maps them
column-by-column; an unknown kind or a missing dimension field fails the ingest run.
measurement_idis never on the wire. It is a deterministic hash overcommit_sha+ the record's dimension tuple, computed by the ingest writer just before INSERT and used as the primary key for theON CONFLICT … DO UPDATEupsert. Emitters do not (and must not) send it.
The monorepo's scripts/post-ingest.py --postgres runs in benchmark CI (required, not
best-effort — a failure fails the bench job and alerts):
- Commit upsert: the writer builds the commit row from
git showand upserts it (ON CONFLICT (commit_sha) DO UPDATE) before applying any record. - Direct write:
INSERT … ON CONFLICT (measurement_id) DO UPDATEinto RDS as the least-privilegebench_ingestIAM role (IAM auth,sslmode=verify-full), against the schema in this repo'smigrations/. Each JSONL file applies in one transaction. - Revalidate ping:
POST {BENCH_SITE_BASE_URL}/api/revalidatewithAuthorization: Bearer $BENCH_REVALIDATE_TOKEN, to flush the Next.js Data Cache so the next read recomputes against freshly written data.
measurement_id is an xxhash64 (seed 0) over a canonical byte encoding of the dimension tuple —
per-table tag separators, length-prefixed string framing, optional / i32 / f64-as-bits
encodings, and a signed-i64 finish. It is implemented once, in the monorepo's
scripts/_measurement_id.py. Its output is frozen forever: every fact row already in
production is keyed by it, so drift does not error — it yields a different key for the same
measurement, which silently duplicates or collides rows instead of upserting.
The freeze is pinned by golden vectors, not by trust. The monorepo's
scripts/measurement_id_golden.json holds (input → hash) cases covering empty/Unicode
strings, i32 bounds, and f64 edges, generated by the original Rust implementation (the
retired v3 server, which wrote the production rows) and never regenerated; the monorepo's
scripts/tests/test_measurement_id.py asserts every vector byte-for-byte in CI. The golden
vectors ARE the contract.
- Auth: bearer token compared in constant time against
BENCH_REVALIDATE_TOKEN. - Responses:
503 { "error": "not_configured" }ifBENCH_REVALIDATE_TOKENis unset/empty (fails closed — an unconfigured deployment never silently accepts unauthenticated revalidation).401 { "error": "unauthorized" }on a missing/incorrect token.200 { "revalidated": true }on success — flushes theBENCH_DATA_TAGData Cache entries. The response is never CDN-cached.
A version bump or wire-shape change is a coordinated change across BOTH repos in one logical change:
- This repo:
web/lib/schema-version.ts(and the schema inmigrations/if columns change). - Monorepo:
vortex-bench/src/v3.rs(the producer wire shape) andscripts/post-ingest.py(the hardcoded literal plus its column mapping).
For wire/record shape changes, also update the producer snapshot fixtures in the monorepo in the same commit.