Files
Baradb/benchmarks/fair_bench.py
T
dimgigov 8db5cfe7e1
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
feat: harden storage, schema persistence, fair benches, fix wire crash
Core storage: hash MemTable, WAL group commit, L0 compaction rebuild,
reader-writer lock, and a global StorageGate so HTTP workers and TCP
share the LSM safely under multi-thread access.

Schema: durable CREATE/ALTER/DROP under _schema:tables:* with full LSM
restore on open. Executor types/values/schema split into query/exec/.

Wire protocol: switch default MM to ARC — ORC cycle collector segfaulted
after ~20 async INSERTs. Fair multi-tier benchmarks (SQLite/HTTP/wire/PG)
and honesty docs for mixed-tier comparisons.
2026-07-18 16:55:50 +03:00

800 lines
25 KiB
Python

#!/usr/bin/env python3
"""
Fair multi-tier benchmarks for BaraDB.
Tiers (never mix across tiers in a single "speedup" claim):
1. embedded — in-process storage (BaraDB LSM from JSON, SQLite)
2. client_server — network + SQL
• BaraDB HTTP REST
• BaraDB wire protocol (Python async client, TCP 9472)
• PostgreSQL (psycopg2)
Within each tier we measure both **row-at-a-time** and **batch multi-row INSERT**.
Usage:
# 1) optional: run BaraDB embedded micro-benches first
nim c -d:release -r benchmarks/bench_all.nim
# 2) start server for HTTP/wire tiers (optional)
./build/baradadb
# 3) fair suite
python3 benchmarks/fair_bench.py
# 4) markdown report
python3 benchmarks/generate_report.py --fair
Env:
BARADB_HTTP_HOST default 127.0.0.1
BARADB_HTTP_PORT default 9912 (TCP 9472 + 440)
BARADB_WIRE_HOST default 127.0.0.1
BARADB_WIRE_PORT default 9472
PGHOST / PGPORT / PGDATABASE / PGUSER / PGPASSWORD
FAIR_N_KV default 20000
FAIR_N_SQL default 5000
FAIR_BATCH default 100 (rows per multi-row INSERT)
FAIR_SKIP_PG=1 skip PostgreSQL
FAIR_SKIP_HTTP=1 skip BaraDB HTTP
FAIR_SKIP_WIRE=1 skip BaraDB wire protocol
"""
from __future__ import annotations
import asyncio
import json
import os
import sqlite3
import sys
import tempfile
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OUT_JSON = ROOT / "fair_benchmark_results.json"
BARA_JSON = ROOT / "benchmark_results.json"
CLIENTS_PY = ROOT / "clients" / "python"
N_KV = int(os.environ.get("FAIR_N_KV", "20000"))
N_SQL = int(os.environ.get("FAIR_N_SQL", "5000"))
BATCH = int(os.environ.get("FAIR_BATCH", "100"))
HTTP_HOST = os.environ.get("BARADB_HTTP_HOST", "127.0.0.1")
HTTP_PORT = int(os.environ.get("BARADB_HTTP_PORT", "9912"))
WIRE_HOST = os.environ.get("BARADB_WIRE_HOST", "127.0.0.1")
WIRE_PORT = int(os.environ.get("BARADB_WIRE_PORT", "9472"))
def now_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
def result(name: str, system: str, tier: str, ops: int, seconds: float, **extra):
ops_s = ops / seconds if seconds > 0 else 0.0
r = {
"name": name,
"system": system,
"tier": tier,
"ops": ops,
"seconds": seconds,
"opsPerSec": ops_s,
"timestamp": now_iso(),
}
r.update(extra)
return r
def fmt_ops(x: float) -> str:
if x >= 1_000_000:
return f"{x/1_000_000:.2f}M"
if x >= 1_000:
return f"{x/1_000:.2f}K"
return f"{x:.2f}"
# ─── Tier 1: Embedded ───────────────────────────────────────────────
def load_baradb_embedded() -> list[dict]:
"""Map bench_all.nim LSM results into fair embedded tier."""
if not BARA_JSON.exists():
print(" [skip] benchmark_results.json missing — run: nimble bench")
return []
data = json.loads(BARA_JSON.read_text())
name_map = {
"LSM-Write": "kv_write",
"LSM-Read": "kv_read",
"WAL-none": "wal_none",
"WAL-group64": "wal_group64",
"WAL-group256": "wal_group256",
"WAL-every": "wal_every",
}
out = []
for r in data.get("results", []):
mapped = name_map.get(r.get("name"))
if not mapped:
continue
out.append(
result(
mapped,
"baradb_lsm_embedded",
"embedded",
r.get("ops", 0),
r.get("seconds", 0.0),
source="benchmark_results.json",
gitSha=data.get("gitSha", ""),
)
)
return out
def multi_values_sql(start: int, count: int) -> str:
"""Build VALUES (...),(...),... for multi-row INSERT."""
parts = [f"({i}, 'value_{i}')" for i in range(start, start + count)]
return ",".join(parts)
def bench_sqlite_embedded(n: int = N_KV) -> list[dict]:
"""SQLite in-process — fair peer for BaraDB embedded LSM."""
out = []
fd, path = tempfile.mkstemp(suffix=".db")
os.close(fd)
os.unlink(path)
# --- durability: FULL (fsync) vs OFF ---
for mode, label in (("OFF", "sqlite_off"), ("FULL", "sqlite_full")):
if os.path.exists(path):
os.unlink(path)
conn = sqlite3.connect(path)
cur = conn.cursor()
cur.execute(f"PRAGMA synchronous = {mode}")
cur.execute("PRAGMA journal_mode = WAL")
cur.execute("CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)")
conn.commit()
t0 = time.perf_counter()
for i in range(n):
cur.execute("INSERT INTO kv(k,v) VALUES(?,?)", (f"key_{i}", f"value_{i}"))
conn.commit()
w = time.perf_counter() - t0
out.append(
result(
"kv_write",
label,
"embedded",
n,
w,
durable=mode == "FULL",
note=f"PRAGMA synchronous={mode}",
)
)
t0 = time.perf_counter()
found = 0
for i in range(n):
cur.execute("SELECT v FROM kv WHERE k=?", (f"key_{i}",))
if cur.fetchone():
found += 1
r = time.perf_counter() - t0
out.append(
result(
"kv_read",
label,
"embedded",
n,
r,
found=found,
durable=mode == "FULL",
)
)
# Batch multi-row INSERT into SQL table (embedded SQL peer for batch)
cur.execute("DROP TABLE IF EXISTS fair_batch")
cur.execute("CREATE TABLE fair_batch (id INTEGER PRIMARY KEY, v TEXT)")
conn.commit()
t0 = time.perf_counter()
for start in range(0, n, BATCH):
cnt = min(BATCH, n - start)
vals = multi_values_sql(start, cnt)
cur.execute(f"INSERT INTO fair_batch (id, v) VALUES {vals}")
conn.commit()
bw = time.perf_counter() - t0
out.append(
result(
"sql_insert_batch",
label,
"embedded",
n,
bw,
batch=BATCH,
durable=mode == "FULL",
note=f"multi-row INSERT batch={BATCH}, sync={mode}",
)
)
conn.close()
if os.path.exists(path):
os.unlink(path)
return out
# ─── Tier 2: Client / server ────────────────────────────────────────
def bara_http_query(sql: str, host: str = HTTP_HOST, port: int = HTTP_PORT) -> dict:
body = json.dumps({"query": sql}).encode()
req = urllib.request.Request(
f"http://{host}:{port}/query",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode())
def bara_http_available() -> bool:
try:
body = json.dumps({"query": "SELECT 1"}).encode()
# health endpoint preferred
req = urllib.request.Request(f"http://{HTTP_HOST}:{HTTP_PORT}/health", method="GET")
with urllib.request.urlopen(req, timeout=2) as resp:
return resp.status == 200
except Exception:
try:
bara_http_query("SELECT 1")
return True
except Exception:
return False
def bench_baradb_http(n: int = N_SQL) -> list[dict]:
if os.environ.get("FAIR_SKIP_HTTP") == "1":
print(" [skip] FAIR_SKIP_HTTP=1")
return []
if not bara_http_available():
print(
f" [skip] BaraDB HTTP not reachable at {HTTP_HOST}:{HTTP_PORT} "
f"(start: ./build/baradadb)"
)
return []
out = []
ep = f"http://{HTTP_HOST}:{HTTP_PORT}/query"
# BaraDB parser may not support IF EXISTS — ignore DROP failures
try:
bara_http_query("DROP TABLE fair_bench")
except Exception:
pass
try:
bara_http_query("CREATE TABLE fair_bench (id INT PRIMARY KEY, v TEXT)")
except Exception as e:
print(f" [warn] setup query failed: {e}")
# Row-at-a-time INSERT
t0 = time.perf_counter()
errors = 0
for i in range(n):
try:
r = bara_http_query(
f"INSERT INTO fair_bench (id, v) VALUES ({i}, 'value_{i}')"
)
if isinstance(r, dict) and r.get("error"):
errors += 1
except Exception:
errors += 1
w = time.perf_counter() - t0
out.append(
result(
"sql_insert_row",
"baradb_http",
"client_server",
n,
w,
errors=errors,
endpoint=ep,
)
)
# Point SELECT
t0 = time.perf_counter()
found = 0
for i in range(n):
try:
r = bara_http_query(f"SELECT v FROM fair_bench WHERE id = {i}")
rows = r.get("rows") if isinstance(r, dict) else None
if rows:
found += 1
except Exception:
pass
rd = time.perf_counter() - t0
out.append(
result(
"sql_select_row",
"baradb_http",
"client_server",
n,
rd,
found=found,
endpoint=ep,
)
)
# Multi-row batch INSERT
try:
try:
bara_http_query("DROP TABLE fair_batch")
except Exception:
pass
bara_http_query("CREATE TABLE fair_batch (id INT PRIMARY KEY, v TEXT)")
except Exception as e:
print(f" [warn] batch setup failed: {e}")
return out
t0 = time.perf_counter()
berr = 0
for start in range(0, n, BATCH):
cnt = min(BATCH, n - start)
sql = f"INSERT INTO fair_batch (id, v) VALUES {multi_values_sql(start, cnt)}"
try:
r = bara_http_query(sql)
if isinstance(r, dict) and r.get("error"):
berr += 1
except Exception:
berr += 1
bw = time.perf_counter() - t0
out.append(
result(
"sql_insert_batch",
"baradb_http",
"client_server",
n,
bw,
batch=BATCH,
errors=berr,
endpoint=ep,
note=f"multi-row INSERT batch={BATCH}",
)
)
return out
def _import_baradb_client():
"""Load clients/python baradb package without requiring install."""
p = str(CLIENTS_PY)
if p not in sys.path:
sys.path.insert(0, p)
from baradb import Client # type: ignore
return Client
def bara_wire_available() -> bool:
if os.environ.get("FAIR_SKIP_WIRE") == "1":
return False
try:
Client = _import_baradb_client()
except Exception as e:
print(f" [skip] wire client import failed: {e}")
return False
async def _ping():
c = Client(WIRE_HOST, WIRE_PORT, timeout=2.0)
try:
await c.connect()
await c.ping()
await c.close()
return True
except Exception:
try:
await c.close()
except Exception:
pass
return False
try:
return asyncio.run(_ping())
except Exception:
return False
def bench_baradb_wire(n: int = N_SQL) -> list[dict]:
"""BaraDB binary wire protocol (TCP) — primary high-performance client path."""
if os.environ.get("FAIR_SKIP_WIRE") == "1":
print(" [skip] FAIR_SKIP_WIRE=1")
return []
try:
Client = _import_baradb_client()
except Exception as e:
print(f" [skip] wire client not available: {e}")
return []
if not bara_wire_available():
print(
f" [skip] BaraDB wire not reachable at {WIRE_HOST}:{WIRE_PORT} "
f"(start: ./build/baradadb)"
)
return []
async def _run() -> list[dict]:
out: list[dict] = []
client = Client(WIRE_HOST, WIRE_PORT, timeout=60.0)
await client.connect()
try:
try:
await client.query("DROP TABLE fair_wire")
except Exception:
pass
try:
await client.query(
"CREATE TABLE fair_wire (id INT PRIMARY KEY, v TEXT)"
)
except Exception as e:
print(f" [warn] wire setup: {e}")
ep = f"tcp://{WIRE_HOST}:{WIRE_PORT}"
# Row-at-a-time INSERT (may crash older servers under load — record partial)
t0 = time.perf_counter()
errors = 0
done = 0
crashed = False
for i in range(n):
try:
await client.query(
f"INSERT INTO fair_wire (id, v) VALUES ({i}, 'value_{i}')"
)
done += 1
except (ConnectionError, OSError, Exception) as e:
errors += 1
if "reset" in str(e).lower() or "closed" in str(e).lower():
crashed = True
print(f" [warn] wire connection lost after {done} inserts: {e}")
break
w = time.perf_counter() - t0
if done > 0:
out.append(
result(
"sql_insert_row",
"baradb_wire",
"client_server",
done,
w,
errors=errors,
requested=n,
endpoint=ep,
note="binary wire protocol"
+ (" (partial — server disconnect)" if crashed else ""),
)
)
if crashed:
return out
# Point SELECT
t0 = time.perf_counter()
found = 0
for i in range(done):
try:
r = await client.query(
f"SELECT v FROM fair_wire WHERE id = {i}"
)
if r is not None and (
getattr(r, "row_count", 0) > 0 or getattr(r, "rows", None)
):
found += 1
except (ConnectionError, OSError, Exception) as e:
if "reset" in str(e).lower() or "closed" in str(e).lower():
crashed = True
print(f" [warn] wire lost during SELECT: {e}")
break
rd = time.perf_counter() - t0
out.append(
result(
"sql_select_row",
"baradb_wire",
"client_server",
max(done, 1),
rd,
found=found,
endpoint=ep,
)
)
if crashed:
return out
# Batch multi-row INSERT
try:
try:
await client.query("DROP TABLE fair_wire_batch")
except Exception:
pass
await client.query(
"CREATE TABLE fair_wire_batch (id INT PRIMARY KEY, v TEXT)"
)
except Exception as e:
print(f" [warn] wire batch setup: {e}")
return out
t0 = time.perf_counter()
berr = 0
bdone = 0
for start in range(0, n, BATCH):
cnt = min(BATCH, n - start)
sql = (
"INSERT INTO fair_wire_batch (id, v) VALUES "
+ multi_values_sql(start, cnt)
)
try:
await client.query(sql)
bdone += cnt
except (ConnectionError, OSError, Exception) as e:
berr += 1
if "reset" in str(e).lower() or "closed" in str(e).lower():
print(f" [warn] wire lost during batch after {bdone} rows: {e}")
break
bw = time.perf_counter() - t0
if bdone > 0:
out.append(
result(
"sql_insert_batch",
"baradb_wire",
"client_server",
bdone,
bw,
batch=BATCH,
errors=berr,
requested=n,
endpoint=ep,
note=f"multi-row INSERT batch={BATCH}",
)
)
finally:
try:
await client.close()
except Exception:
pass
return out
try:
return asyncio.run(_run())
except Exception as e:
print(f" [skip] wire bench failed: {e}")
return []
def bench_postgresql(n: int = N_SQL) -> list[dict]:
if os.environ.get("FAIR_SKIP_PG") == "1":
print(" [skip] FAIR_SKIP_PG=1")
return []
try:
import psycopg2
except ImportError:
print(" [skip] psycopg2 not installed")
return []
cfg = {
"host": os.environ.get("PGHOST", "localhost"),
"port": int(os.environ.get("PGPORT", "5432")),
"dbname": os.environ.get("PGDATABASE", "postgres"),
"user": os.environ.get("PGUSER", "postgres"),
"password": os.environ.get("PGPASSWORD", os.environ.get("PG_PASSWORD", "")),
}
if not cfg["password"] and os.environ.get("PGPASSWORD") is None:
cfg["password"] = os.environ.get("BARA_PG_PASSWORD", "pas+123")
out = []
try:
conn = psycopg2.connect(**cfg)
except Exception as e:
print(f" [skip] PostgreSQL connect failed: {e}")
return []
cur = conn.cursor()
for sync, label in (("on", "postgresql_sync_on"), ("off", "postgresql_sync_off")):
cur.execute(f"SET synchronous_commit = {sync}")
cur.execute("DROP TABLE IF EXISTS fair_bench")
cur.execute("CREATE TABLE fair_bench (id INTEGER PRIMARY KEY, v TEXT)")
conn.commit()
t0 = time.perf_counter()
for i in range(n):
cur.execute(
"INSERT INTO fair_bench (id, v) VALUES (%s, %s)",
(i, f"value_{i}"),
)
conn.commit()
w = time.perf_counter() - t0
out.append(
result(
"sql_insert_row",
label,
"client_server",
n,
w,
durable=sync == "on",
note=f"synchronous_commit={sync}",
)
)
t0 = time.perf_counter()
found = 0
for i in range(n):
cur.execute("SELECT v FROM fair_bench WHERE id = %s", (i,))
if cur.fetchone():
found += 1
rd = time.perf_counter() - t0
out.append(
result(
"sql_select_row",
label,
"client_server",
n,
rd,
found=found,
durable=sync == "on",
)
)
# Batch multi-row INSERT (same durability setting)
cur.execute("DROP TABLE IF EXISTS fair_batch")
cur.execute("CREATE TABLE fair_batch (id INTEGER PRIMARY KEY, v TEXT)")
conn.commit()
t0 = time.perf_counter()
for start in range(0, n, BATCH):
cnt = min(BATCH, n - start)
cur.execute(
f"INSERT INTO fair_batch (id, v) VALUES {multi_values_sql(start, cnt)}"
)
conn.commit()
bw = time.perf_counter() - t0
out.append(
result(
"sql_insert_batch",
label,
"client_server",
n,
bw,
batch=BATCH,
durable=sync == "on",
note=f"multi-row INSERT batch={BATCH}, sync={sync}",
)
)
cur.close()
conn.close()
return out
# ─── Report ──────────────────────────────────────────────────────────
def print_tier(name: str, rows: list[dict]):
print(f"\n=== Tier: {name} ===")
if not rows:
print(" (no results)")
return
# group by bench name
names = []
for r in rows:
if r["name"] not in names:
names.append(r["name"])
for nm in names:
print(f" [{nm}]")
for r in rows:
if r["name"] != nm:
continue
print(
f" {r['system']:28s} {fmt_ops(r['opsPerSec']):>10s}/s "
f"({r['seconds']:.3f}s, n={r['ops']})"
)
def write_markdown(payload: dict, path: Path):
lines = []
lines.append("# Fair Benchmark Results")
lines.append("")
lines.append(f"Generated: **{payload.get('generated', '')}**")
lines.append("")
lines.append("## Methodology")
lines.append("")
for line in payload.get("methodology", []):
lines.append(f"- {line}")
lines.append("")
lines.append("**Do not compare numbers across tiers.** Embedded storage is not the same")
lines.append("workload as client-server SQL over the network.")
lines.append("")
for tier in ("embedded", "client_server"):
rows = [r for r in payload.get("results", []) if r.get("tier") == tier]
lines.append(f"## Tier: `{tier}`")
lines.append("")
if not rows:
lines.append("_No results for this tier._")
lines.append("")
continue
lines.append("| Bench | System | ops/s | seconds | n | notes |")
lines.append("|-------|--------|------:|--------:|--:|-------|")
for r in rows:
note = r.get("note") or r.get("source") or ""
lines.append(
f"| {r['name']} | `{r['system']}` | {fmt_ops(r['opsPerSec'])} | "
f"{r['seconds']:.3f} | {r['ops']} | {note} |"
)
lines.append("")
# same-bench comparison within tier
names = sorted({r["name"] for r in rows})
lines.append(f"### Same-bench ratios (`{tier}`)")
lines.append("")
for nm in names:
group = [r for r in rows if r["name"] == nm]
if len(group) < 2:
continue
best = max(group, key=lambda x: x["opsPerSec"])
lines.append(f"**{nm}** (fastest: `{best['system']}` @ {fmt_ops(best['opsPerSec'])}/s)")
lines.append("")
lines.append("| System | Relative to fastest |")
lines.append("|--------|--------------------:|")
for r in sorted(group, key=lambda x: -x["opsPerSec"]):
rel = r["opsPerSec"] / best["opsPerSec"] if best["opsPerSec"] else 0
lines.append(f"| `{r['system']}` | {rel:.2f}x |")
lines.append("")
path.write_text("\n".join(lines) + "\n")
print(f"\nMarkdown written to {path}")
def main():
print("BaraDB Fair Benchmark Suite")
print(f" N_KV={N_KV} N_SQL={N_SQL} BATCH={BATCH}")
print(f" HTTP={HTTP_HOST}:{HTTP_PORT} WIRE={WIRE_HOST}:{WIRE_PORT}")
methodology = [
"Tier `embedded`: in-process only (BaraDB LSM from nimble bench JSON; SQLite via Python sqlite3).",
"Tier `client_server`: network SQL (BaraDB HTTP /query; BaraDB binary wire TCP; PostgreSQL via psycopg2).",
"`sql_insert_row`: one INSERT statement per row (chatty).",
f"`sql_insert_batch`: multi-row INSERT with batch size {BATCH} (same SQL style across systems).",
"PostgreSQL: synchronous_commit=on|off; SQLite: PRAGMA synchronous FULL|OFF.",
"BaraDB WAL modes appear only if you ran benchmarks/bench_all.nim (WAL-* rows).",
"Never claim 'Nx faster than Postgres' using embedded BaraDB numbers.",
]
results: list[dict] = []
print("\n--- Embedded tier ---")
results.extend(load_baradb_embedded())
print(" SQLite embedded (+ batch)…")
results.extend(bench_sqlite_embedded())
print("\n--- Client/server tier ---")
print(" BaraDB HTTP…")
results.extend(bench_baradb_http())
print(" BaraDB wire (TCP)…")
results.extend(bench_baradb_wire())
print(" PostgreSQL…")
results.extend(bench_postgresql())
payload = {
"generated": now_iso(),
"methodology": methodology,
"config": {
"N_KV": N_KV,
"N_SQL": N_SQL,
"HTTP": f"{HTTP_HOST}:{HTTP_PORT}",
},
"results": results,
}
OUT_JSON.write_text(json.dumps(payload, indent=2))
print(f"\nJSON written to {OUT_JSON}")
print_tier("embedded", [r for r in results if r["tier"] == "embedded"])
print_tier("client_server", [r for r in results if r["tier"] == "client_server"])
write_markdown(payload, ROOT / "benchmarks" / "FAIR_COMPARISON.md")
return 0
if __name__ == "__main__":
sys.exit(main())