Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 183 additions & 1 deletion .github/scripts/readme_bench.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""Rewrite the README bench section from the newest main run.
"""Rewrite the published bench numbers from the newest main run.

Rewrites the marked section of README.md and the marked blocks of
web/index.html from the same result artifacts, so the site and the readme
can never disagree.

Usage: readme_bench.py <results_dir> <reference_dir> <reference_stamp> <sha>
"""
Expand Down Expand Up @@ -90,6 +94,182 @@ def field_table(profiles, stamp):
return lines


SITE = pathlib.Path("web/index.html")
FIELD_BEGIN = "<!-- bench:field:start -->"
FIELD_END = "<!-- bench:field:end -->"
MATRIX_BEGIN = "<!-- bench:matrix:start -->"
MATRIX_END = "<!-- bench:matrix:end -->"

ENGINE_NAMES = {
"quantadb": "QuantaDB",
"postgres18": "PostgreSQL 18",
"mariadb114": "MariaDB 11.4",
"mysql84": "MySQL 8.4",
}
WORKLOAD_LABELS = {"reads": "Reads", "mixed": "Mixed", "writes": "Writes"}

# A profile is called out when its writes fall under a quarter of the best
# profile's, or its write tail passes 20 ms. Both are visible on the page
# rather than dropped.
BAD_WRITE_SHARE = 0.25
BAD_P99_MS = 20.0


def value(records, workload, engine=None):
record = records.get((engine, workload))
return None if record is None else record["ops_per_s"]


def field_html(profiles, stamp, sha):
records = profiles.get(FIELD_PROFILE)
if not records:
return None

engines = [e for e in FIELD_ENGINES if any(key[0] == e for key in records)]
if not engines:
return None

peak = {}
for workload in WORKLOADS:
found = [value(records, workload, e) for e in engines]
found = [v for v in found if v is not None]
peak[workload] = max(found) if found else None

rows = [
' <div class="field">',
' <div class="field-row field-head" role="presentation">',
' <span class="field-name">Engine</span>',
' <span class="field-metric">Reads</span>',
' <span class="field-metric">Mixed 80/20</span>',
' <span class="field-metric">Writes</span>',
" </div>",
]

for engine in engines:
self_row = engine == "quantadb"
rows.append("")
rows.append(
f' <div class="field-row{" is-self" if self_row else ""}">'
)
rows.append(
f' <span class="field-name">{ENGINE_NAMES.get(engine, engine)}</span>'
)
for workload in WORKLOADS:
found = value(records, workload, engine)
best = peak[workload]
label = WORKLOAD_LABELS[workload]
if found is None or not best:
rows.append(
f' <span class="field-metric" data-label="{label}">'
f'<b>-</b></span>'
)
continue
width = f"{found / best * 100:.1f}".rstrip("0").rstrip(".")
leads = ""
if found == best:
leads = ' data-leads="yes"' if self_row else ' data-leads="rival"'
rows.append(
f' <span class="field-metric" data-label="{label}"{leads}>'
)
rows.append(f' <span class="bar" style="--w:{width}%"></span>')
rows.append(f" <b>{found:,}</b>")
rows.append(" </span>")
rows.append(" </div>")

rows.append(" </div>")
rows.extend(
[
"",
' <dl class="provenance">',
f" <div><dt>Measured</dt><dd>{stamp}</dd></div>",
f" <div><dt>Commit</dt><dd><code>{sha[:9]}</code></dd></div>",
" <div><dt>Limits</dt><dd>2 CPU, 2 GiB</dd></div>",
" <div><dt>Load</dt><dd>4 conn, 10 s</dd></div>",
" </dl>",
]
)
return "\n".join(rows)


def matrix_html(profiles, sha):
if not profiles:
return None

ordered = sorted(
profiles.items(),
key=lambda item: value(item[1], "writes") or 0,
reverse=True,
)
writes = [value(records, "writes") or 0 for _, records in ordered]
best_writes = max(writes) if writes else 0

rows = [
' <div class="matrix-scroll">',
' <table class="matrix">',
" <caption>QuantaDB on GitHub runners, measured on main at "
f"<code>{sha[:9]}</code></caption>",
" <thead>",
" <tr>",
' <th scope="col">Profile</th>',
' <th scope="col">Reads</th>',
' <th scope="col">Mixed</th>',
' <th scope="col">Writes</th>',
' <th scope="col">Write p99</th>',
" </tr>",
" </thead>",
" <tbody>",
]

for name, records in ordered:
write_ops = value(records, "writes")
tail = wp99(records, "writes")
bad = False
if write_ops is not None and best_writes:
bad = write_ops < best_writes * BAD_WRITE_SHARE
if tail != "-" and float(tail) > BAD_P99_MS:
bad = True
cells = []
for workload in WORKLOADS:
found = value(records, workload)
cells.append("-" if found is None else f"{found:,}")
marker = ' class="is-bad"' if bad else ""
unit = "" if tail == "-" else " ms"
rows.append(f" <tr{marker}>")
rows.append(f' <th scope="row">{name}</th>')
rows.append(
f" <td>{cells[0]}</td><td>{cells[1]}</td>"
f"<td>{cells[2]}</td><td>{tail}{unit}</td>"
)
rows.append(" </tr>")

rows.extend([" </tbody>", " </table>", " </div>"])
return "\n".join(rows)


def splice(text, begin, end, body):
head, rest = text.split(begin, 1)
middle, tail = rest.split(end, 1)
trailing = middle.rsplit("\n", 1)[-1]
indent = trailing if trailing.strip() == "" else ""
return f"{head}{begin}\n{body}\n{indent}{end}{tail}"


def publish_site(results, reference, stamp, sha):
if not SITE.exists():
return
text = SITE.read_text(encoding="utf-8")

field = field_html(reference, stamp, sha)
if field is not None:
text = splice(text, FIELD_BEGIN, FIELD_END, field)

matrix = matrix_html(results, sha)
if matrix is not None:
text = splice(text, MATRIX_BEGIN, MATRIX_END, matrix)

SITE.write_text(text, encoding="utf-8")


def main():
results_dir, reference_dir, stamp, sha = (
sys.argv[1],
Expand All @@ -113,5 +293,7 @@ def main():
_, tail = rest.split(END, 1)
readme.write_text(head + "\n".join(section) + tail, encoding="utf-8")

publish_site(results, reference, stamp, sha)


main()
8 changes: 4 additions & 4 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,12 @@ jobs:
python .github/scripts/readme_bench.py results reference "$stamp" "$GITHUB_SHA"
- name: Commit if changed
run: |
if git diff --quiet README.md; then
echo "README already current"
if git diff --quiet README.md web/index.html; then
echo "published numbers already current"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md
git commit -m "Refresh README bench numbers [skip ci]"
git add README.md web/index.html
git commit -m "Refresh published bench numbers [skip ci]"
git push
61 changes: 61 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# QuantaDB design system

Scope: the static marketing site in `web/`. Written 2026-07-25 with the
rebuild of `web/index.html`.

## Direction contract

**Thesis.** The comparison table is the argument. It sits in the first
viewport at full width, carries its own provenance, and shows the workloads
QuantaDB loses as plainly as the ones it wins. It refuses the arrangement
this category ships by default, where a claim headline sits above three
feature cards and the measurements are a link.

**Own world.** Near-black ground, one violet accent, one neutral ramp, and
figures set in mono on a shared baseline. Data bars are structural, not
decorative: every bar is a real ratio against the leader of its own column.
No gradients, no glass, no rounded card stacks.

**Story.** A skeptical engineer sees four engines measured under identical
limits, finds the commit and date attached, sees QuantaDB lose on writes,
and concludes the numbers were not selected for flattery.

**First viewport.** Wordmark and nav, one headline line, one supporting
line, two actions, then the field table edge to edge with its provenance
strip. The primary action sits above the table on desktop and directly
under the headline on mobile.

**Form.** Developer infrastructure convention, played straight, at the craft
level of Neon, Turso and ClickHouse. Chosen by the user as the standing exit
from the direction roll on 2026-07-25.

## Color

Ground `#0B0C0E`. Raised surface `#131519`. Rule `#23262C`. Body text
`#E6E8EC`. Secondary text `#9AA1AC`. Accent `#9B7BFF` for interactive and
data, brand `#7132F5` reserved for the mark. Amber `#E0A458` marks the
engine leading a column when it is not QuantaDB.

Strategy is restrained: neutrals plus one accent, because the visitor came
to read numbers. Dark is chosen from the scene, not the category: this is
read on a laptop beside a terminal.

## Type

System grotesque for prose, system mono for every figure and identifier.
No webfonts, because the site loads no external resources. Hierarchy is
carried by scale and weight, not by family. Display caps at 4rem. Body
measure stays inside 68ch.

## Rules

- Figures always in mono, tabular numerals, right aligned on their column.
- A number never appears without its unit and its provenance reachable in
one glance or one link.
- Bars share one scale per column and are labelled; a bar is never a
decoration in a cell that has no ratio to express.
- Sections vary: table, matrix, prose pair, code, list. No repeating rig of
eyebrow over heading over paragraph.
- One authored motion moment, on the bars, from an already-visible default.
- The benchmark blocks between the `bench:` markers belong to CI. Do not
hand-edit them.
67 changes: 67 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# QuantaDB product truth

## What it is

An experimental relational OLTP database written in Rust, rebuilt from a
retired v0.1 prototype. Version 0.2.0. Apache-2.0. Not a database anyone
should store real data in yet, and the site says so.

## The mechanism nobody else claims

Every performance number QuantaDB publishes carries the machine it ran on,
the commit that produced it, the container limits, and the runs it lost.
CI rewrites the published tables from the newest run on main, so the numbers
are current rather than curated. A result is discarded if a correctness test
fails.

## Audience and scene

Database and systems engineers evaluating an unfamiliar engine, usually
arriving from a repository link with a few minutes of skeptical attention.
They have read benchmark marketing before and discount it by habit. They
want the method before the number.

## What the first surface must prove

That the numbers are real, reproducible, and include the losses.

## Shape of the system

Six crates with an enforced acyclic graph: `syntax` (span-aware SQL parser),
`storage` (checksummed pages, two-segment WAL, group commit, recovery),
`index` (immutable B+ tree generations published online), `mvcc` (snapshot
isolation, first-committer-wins), `engine` (catalog, constraints,
transactional CRUD), `server` (bounded concurrent TCP server).

## Protocol

One native protocol. JSON frames, one per line, over TCP on port 6626.
Documented in `docs/protocol-v1.md`. The PostgreSQL wire listener was
removed; nothing in the product speaks Postgres any more.

## Constraints that bind the site

- No authentication, authorisation, or TLS on any listener.
- No replication.
- No published container image, so the quickstart builds from source.
- The site is static, deployed from `web/` to GitHub Pages, and loads no
external resources.

## Brand commitments

- Purple mark, `#7132F5`, already carried by `web/logo.svg` and the favicons.
- Benchmark tables on the site and in the README are written by CI from the
same run artifacts. They are never edited by hand.
- Standing preference recorded 2026-07-25: the site follows the developer
infrastructure convention, executed at full fidelity, with Neon, Turso and
ClickHouse as the craft bar. This was chosen deliberately over two more
expressive directions and holds until the user says otherwise.

## Known gap the site must not paper over

The published field comparison drove QuantaDB through its PostgreSQL port
with psycopg2, the same driver the real Postgres got. That port no longer
exists. The numbers remain real measurements of the engine, but the run is
not reproducible against the current commit until `.github/workflows/
reference.yml` and `.github/scripts/field_bench.py` are re-pointed at the
native client. Until then the table states the commit it was measured at.
Loading