Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9b6f924
test(27.1): snapshot lock-hold repro + fork-snapshot contract tests
May 22, 2026
ff91475
feat(27.1): fork()+COW snapshot path — Valkey BGSAVE pattern
May 22, 2026
f6e5efd
test(27.1): add lock-contention + recovery-time tests for fork snapshot
May 22, 2026
eb59ea9
test(27.1): big-state tests at 1M-5M entries (production incident scale)
May 22, 2026
7509e9d
test(27.1): fork-syscall scaling sweep 1M-20M + answer 'is 10ms too l…
May 22, 2026
5a52ed6
test(27.1): extreme-scale fork sweep 30M-50M + Redis published-number…
May 22, 2026
d0eb1dd
test(27.1): default-running extreme-scale test asserting fork <10ms
May 22, 2026
32b8bc8
feat(27.1): Redis-style conditional snapshot + CI fixes
May 22, 2026
c80af07
feat(27.1): default fork-snapshot on (unix) + THP self-opt-out
May 23, 2026
d5607f3
fix(snapshot): harden fork path and conditional baseline
May 27, 2026
33bc047
fix(snapshot): use applied WAL watermarks for recovery
May 27, 2026
00d72f4
fix(snapshot): harden WAL replay and force register
May 27, 2026
9615125
test(snapshot): cover WAL ordering edge cases
May 27, 2026
f7b8fa3
fix(snapshot): reclaim covered WAL bytes
May 28, 2026
29bd32e
fix(snapshot): harden WAL compaction
May 28, 2026
a0fab6a
fix(snapshot): bound fork child reaping
May 28, 2026
82ca371
fix(snapshot): respect fork timeout on EINTR
May 28, 2026
dac554d
fix(persistence): harden WAL recovery and fork snapshots
May 28, 2026
805ab3f
test(27.1): measure snapshot encode vs write+fsync breakdown
May 28, 2026
d3f808d
docs(install): feature pip + brew + docker as the three primary insta…
May 29, 2026
01169da
docs: fix code snippets the shipped SDK rejects
May 29, 2026
af1db75
docs(install): correct brew command to beava-dev/beava/beava
May 29, 2026
b976bd9
docs: fix fictional test fixture + HTTP route snippets
May 29, 2026
6f34e3d
fix(home): stop mobile horizontal overflow
May 29, 2026
edd2380
docs: fix guide query widget to real /batch_get route
May 29, 2026
6fd49c1
docs: address review nits — flat batch_get result + footer tap targets
May 29, 2026
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
2 changes: 1 addition & 1 deletion beava-website/project/docs/concepts/streams/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ <h2 id="derived">Derived events</h2>
return (
checkout
.filter(bv.col("amount") > 100)
.with_columns(bucket=bv.col("amount").cast(int) // 50)
.with_columns(bucket=bv.col("amount").cast("int") // 50)
)`}
</Code>
<p>Push events to the parent (<code>Click</code>); beava routes matching records through the derivation chain and updates downstream tables for free. You never push to a derived event directly.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,24 @@ <h2 id="good-table">What makes a good beava table?</h2>
<p>Start with the feature your application actually needs. Add complexity only when the use case requires it.</p>

<h2 id="testing">Test your pipeline</h2>
<p>Before you push real traffic at it, drive the pipeline with a fixture and assert what comes out. <code>bv.test.fixture</code> spins up an in-process server, lets you push synthetic events, and exposes the same <code>get</code> / <code>batch_get</code> API you use in production:</p>
<p>Before you push real traffic at it, drive the pipeline with a fixture and assert what comes out. <code>bv.test.fixture</code> is a pytest-shaped generator: <code>yield from</code> it inside your own <code>@pytest.fixture</code> and it spins up an in-process server, exposing the same <code>get</code> / <code>batch_get</code> API you use in production:</p>
<Code lang="python">
{`import beava as bv

def test_user_clicks_counts_within_window():
with bv.test.fixture(Click, UserClicks) as f:
f.push("Click", {"user_id": "u_42"})
f.push("Click", {"user_id": "u_42"})
assert f.get("UserClicks", "u_42") == {"clicks_60s": 2}

f.advance_time(seconds=120)
f.push("Click", {"user_id": "u_42"})
assert f.get("UserClicks", "u_42") == {"clicks_60s": 1}`}
{`import pytest
import beava as bv
from beava.test import fixture

@pytest.fixture
def app():
# in-process server; reset_each=True gives every test a clean slate
yield from fixture(reset_each=True)

def test_user_clicks_counts_within_window(app):
app.register(Click, UserClicks)
app.push("Click", {"user_id": "u_42"})
app.push("Click", {"user_id": "u_42"})
assert app.get("UserClicks", "u_42") == {"clicks_60s": 2}`}
</Code>
<p>The fixture controls the clock with <code>f.advance_time(...)</code>, so you can write deterministic tests for windowed aggregations without sleeping. Run with <code>pytest</code>; no extra config required.</p>
<p>The fixture yields a regular <code>bv.App</code>, so the test reads exactly like production code — <code>register</code>, <code>push</code>, <code>get</code>. Run with <code>pytest</code>; no extra config required.</p>

<h2 id="go-deeper">Go deeper</h2>
<p>Pipelines are written in Python. The engineer who understands the feature can also ship it; the runtime is a single binary that you talk to over HTTP.</p>
Expand Down
8 changes: 4 additions & 4 deletions beava-website/project/field-guide-ch1.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ <h2>Try it — 3 curl commands</h2>

<pre className="code-well">
<span className="cmt"># 1. Register the pipeline</span>{'\n'}
<span className="kw">curl</span> -X POST localhost:8080/pipelines -d @pipeline.py{'\n'}{'\n'}
<span className="kw">curl</span> -X POST localhost:8080/register -d @pipeline.json{'\n'}{'\n'}
<span className="cmt"># 2. Send a watch</span>{'\n'}
<span className="kw">curl</span> -X POST localhost:8080/events/Watch \<br/>
<span className="kw">curl</span> -X POST localhost:8080/push \<br/>
{' '}-H <span className="str">'content-type: application/json'</span> \<br/>
{' '}-d <span className="str">{`'{"user_id":"u1","video_id":"v_abc","watch_seconds":42}'`}</span>{'\n'}{'\n'}
{' '}-d <span className="str">{`'{"event":"Watch","data":{"user_id":"u1","video_id":"v_abc","watch_seconds":42}}'`}</span>{'\n'}{'\n'}
<span className="cmt"># 3. Ask what this user cares about in the last hour</span>{'\n'}
<span className="kw">curl</span> localhost:8080/features/UserTagAffinity?user_id=u1{'\n'}
<span className="kw">curl</span> localhost:8080/get/UserTagAffinity/u1{'\n'}
<span className="cmt">{`# => [{"tag":"woodworking","weight_1h":42.0,...}, ...]`}</span>
</pre>

Expand Down
8 changes: 4 additions & 4 deletions beava-website/project/field-guide-ch2.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,13 @@ <h2>Try it — 3 curl commands</h2>

<pre className="code-well">
<span className="cmt"># 1. Register the pipeline</span>{'\n'}
<span className="kw">curl</span> -X POST localhost:8080/pipelines -d @pipeline.py{'\n'}{'\n'}
<span className="kw">curl</span> -X POST localhost:8080/register -d @pipeline.json{'\n'}{'\n'}
<span className="cmt"># 2. Send a watch</span>{'\n'}
<span className="kw">curl</span> -X POST localhost:8080/events/Watch \<br/>
<span className="kw">curl</span> -X POST localhost:8080/push \<br/>
{' '}-H <span className="str">'content-type: application/json'</span> \<br/>
{' '}-d <span className="str">{`'{"user_id":"u1","video_id":"v_abc","watch_seconds":42}'`}</span>{'\n'}{'\n'}
{' '}-d <span className="str">{`'{"event":"Watch","data":{"user_id":"u1","video_id":"v_abc","watch_seconds":42}}'`}</span>{'\n'}{'\n'}
<span className="cmt"># 3. Ask what this user cares about in the last hour</span>{'\n'}
<span className="kw">curl</span> localhost:8080/features/UserTagAffinity?user_id=u1{'\n'}
<span className="kw">curl</span> localhost:8080/get/UserTagAffinity/u1{'\n'}
<span className="cmt">{`# => [{"tag":"woodworking","weight_1h":42.0,...}, ...]`}</span>
</pre>

Expand Down
41 changes: 24 additions & 17 deletions beava-website/project/guide/chapter-1/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,12 @@ <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 600, fontSize: 'clamp(
borderRadius: 8, padding: '12px 14px', margin: 0,
overflowX: 'auto',
}}>
{'# curl (recommended) — fetches the platform wheel from latest GH Release\n'}
{'# pip (recommended) — fetches the platform wheel + bundled server binary\n'}
{'$ pip install beava\n'}
{'\n'}
{'# brew (macOS / Linux) — server binary on PATH the Homebrew way\n'}
{'$ brew install beava-dev/beava/beava\n'}
{'\n'}
{'# docker (zero deps on host — :edge tag rebuilt from main on every push)\n'}
{'$ docker run -p 8080:8080 -p 8081:8081 beavadev/beava:edge\n'}
{'\n'}
Expand Down Expand Up @@ -690,14 +693,15 @@ <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 600, fontSize: 'clamp(
// ---------------- Query panel (live curl + JSON response) ---------------- //

// Renders a curl POST request body and its JSON response based on the
// actual rows in state. Teaches: your app calls POST /batch/<Table>
// with the user_ids visible on screen, gets back the metrics, renders.
// actual rows in state. Teaches: your app calls POST /batch_get with
// {requests:[{table,key},...]}, gets back {results:[...]}, renders.
const QueryPanel = ({ table, userIds, rowsList, metricKeys, empty, onFirstRun, autoRefreshMs }) => {
// Show all visible users (up to 6 to keep the payload readable).
const ids = userIds.slice(0, 6);
const curl = `curl -X POST https://your-beava/batch/${table} \\
const reqBody = JSON.stringify({ requests: ids.map(k => ({ table, key: k })) });
const curl = `curl -X POST https://your-beava/batch_get \\
-H 'Content-Type: application/json' \\
-d '${JSON.stringify(ids)}'`;
-d '${reqBody}'`;

// User-triggered query: the response only appears after "Run query"
// is clicked. Once clicked, auto-refresh kicks in so the snapshot
Expand All @@ -720,20 +724,23 @@ <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 600, fontSize: 'clamp(

const buildResponse = React.useCallback(() => {
const rowByUid = new Map(rowsRef.current);
const obj = {};
const results = [];
for (const uid of idsRef.current) {
const r = rowByUid.get(uid);
if (!r) { obj[uid] = null; continue; }
// Server returns a flat feature map per result, in request order
// (no table/entity_id/key wrapping).
const o = {};
for (const k of metricKeys) {
if (k === 'views_total') o.views_total = r.views_total;
else if (k === 'views_24h') o.views_24h = r.views_24h;
else if (k === 'distinct_cats_7d') o.distinct_cats_7d = r.cats.size;
else if (k === 'last_seen') o.last_seen = new Date(r.lastTsMs).toISOString().replace(/\.\d{3}Z$/, 'Z');
if (r) {
for (const k of metricKeys) {
if (k === 'views_total') o.views_total = r.views_total;
else if (k === 'views_24h') o.views_24h = r.views_24h;
else if (k === 'distinct_cats_7d') o.distinct_cats_7d = r.cats.size;
else if (k === 'last_seen') o.last_seen = new Date(r.lastTsMs).toISOString().replace(/\.\d{3}Z$/, 'Z');
}
}
obj[uid] = o;
results.push(o);
}
return JSON.stringify(obj, null, 2);
return JSON.stringify({ results }, null, 2);
}, [metricKeys]);

const doFetch = React.useCallback((opts = {}) => {
Expand Down Expand Up @@ -777,7 +784,7 @@ <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 600, fontSize: 'clamp(
color: 'var(--fg3)', letterSpacing: '0.04em',
textTransform: 'lowercase',
}}>
↓ how your app reads it: <span style={{ color: 'var(--accent)', fontWeight: 700 }}>POST /batch/{table}</span>
↓ how your app reads it: <span style={{ color: 'var(--accent)', fontWeight: 700 }}>POST /batch_get</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
{state === 'done' && latency != null && (
Expand Down Expand Up @@ -806,9 +813,9 @@ <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 600, fontSize: 'clamp(
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--fg3)', letterSpacing: '0.04em', textTransform: 'lowercase', marginBottom: 8 }}>1. request — copy-paste this into your terminal</div>
<div style={{ position: 'relative' }}>
<pre className="code-well" style={{ fontSize: 12.5, padding: '18px 20px', lineHeight: 1.65, margin: 0, whiteSpace: 'pre', minHeight: 140 }}>
<span className="cmt">$ </span>curl -X POST https://your-beava/batch/<span className="fn">{table}</span> \{'\n'}
<span className="cmt">$ </span>curl -X POST https://your-beava/batch_get \{'\n'}
{' '}-H <span className="str">'Content-Type: application/json'</span> \{'\n'}
{' '}-d <span className="str">'{JSON.stringify(ids)}'</span>
{' '}-d <span className="str">'{reqBody}'</span>
</pre>
<CopyBtn text={curl}/>
</div>
Expand Down
12 changes: 7 additions & 5 deletions beava-website/project/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@
};

const FeedRow = ({ row, now, columnWidths }) => (
<div style={{
<div className="beava-feed-row" style={{
display: 'grid', gridTemplateColumns: columnWidths,
alignItems: 'center', columnGap: 24,
padding: '11px 22px', borderTop: '1px solid var(--border)',
Expand Down Expand Up @@ -569,6 +569,7 @@
@media (max-width: 760px) {
section { padding-left: 18px !important; padding-right: 18px !important; }
.beava-feed-headers { display: none !important; }
.beava-feed-row { grid-template-columns: 1fr !important; align-items: start !important; row-gap: 5px !important; }
.beava-pillars-grid { grid-template-columns: 1fr !important; }
.beava-finalcta-grid { grid-template-columns: 1fr !important; }
footer .bv-foot-grid { grid-template-columns: 1fr !important; gap: 32px !important; }
Expand Down Expand Up @@ -1000,7 +1001,7 @@
{' '}<span style={{ background: PIPE_HL.blue, borderRadius: 3, padding: '0 2px' }}>top_issue_30m</span> = bv.<span style={PIPE_CS.fn}>top_k</span>(<span style={PIPE_CS.str}>"topic"</span>, k=<span style={PIPE_CS.num}>1</span>, window=<span style={PIPE_CS.str}>"30m"</span>,{'\n'}
{' '}where=(bv.<span style={PIPE_CS.fn}>col</span>(<span style={PIPE_CS.str}>"event"</span>) == <span style={PIPE_CS.str}>"api_error"</span>) | (bv.<span style={PIPE_CS.fn}>col</span>(<span style={PIPE_CS.str}>"event"</span>) == <span style={PIPE_CS.str}>"docs_view"</span>)),{'\n'}
{' '}){'\n\n'}
bv.<span style={PIPE_CS.fn}>App</span>(<span style={PIPE_CS.str}>"0.0.0.0:6400"</span>).<span style={PIPE_CS.fn}>register</span>(<span style={PIPE_CS.ty}>CustomerEvent</span>, <span style={PIPE_CS.fn}>CustomerState</span>).<span style={PIPE_CS.fn}>serve</span>()
bv.<span style={PIPE_CS.fn}>App</span>(<span style={PIPE_CS.str}>"http://localhost:8080"</span>).<span style={PIPE_CS.fn}>register</span>(<span style={PIPE_CS.ty}>CustomerEvent</span>, <span style={PIPE_CS.fn}>CustomerState</span>)
</>
);

Expand All @@ -1024,7 +1025,7 @@
{' '}<span style={PIPE_CS.kw}>return</span> e.<span style={PIPE_CS.fn}>group_by</span>(<span style={PIPE_CS.str}>"shopper_id"</span>).<span style={PIPE_CS.fn}>agg</span>({'\n'}
{' '}<span style={{ background: PIPE_HL.blue, borderRadius: 3, padding: '0 2px' }}>view_price_30m</span> = bv.<span style={PIPE_CS.fn}>mean</span>(<span style={PIPE_CS.str}>"price"</span>, window=<span style={PIPE_CS.str}>"30m"</span>, where=bv.<span style={PIPE_CS.fn}>col</span>(<span style={PIPE_CS.str}>"event"</span>) == <span style={PIPE_CS.str}>"view"</span>),{'\n'}
{' '}){'\n\n'}
bv.<span style={PIPE_CS.fn}>App</span>(<span style={PIPE_CS.str}>"0.0.0.0:6400"</span>).<span style={PIPE_CS.fn}>register</span>(<span style={PIPE_CS.ty}>CommerceEvent</span>, <span style={PIPE_CS.fn}>SkuMomentum</span>, <span style={PIPE_CS.fn}>ShopperIntent</span>).<span style={PIPE_CS.fn}>serve</span>()
bv.<span style={PIPE_CS.fn}>App</span>(<span style={PIPE_CS.str}>"http://localhost:8080"</span>).<span style={PIPE_CS.fn}>register</span>(<span style={PIPE_CS.ty}>CommerceEvent</span>, <span style={PIPE_CS.fn}>SkuMomentum</span>, <span style={PIPE_CS.fn}>ShopperIntent</span>)
</>
);

Expand All @@ -1050,7 +1051,7 @@
{' '}<span style={PIPE_CS.kw}>return</span> e.<span style={PIPE_CS.fn}>group_by</span>(<span style={PIPE_CS.str}>"zone_id"</span>).<span style={PIPE_CS.fn}>agg</span>({'\n'}
{' '}<span style={{ background: PIPE_HL.blue, borderRadius: 3, padding: '0 2px' }}>jobs_5m</span> = bv.<span style={PIPE_CS.fn}>count</span>(window=<span style={PIPE_CS.str}>"5m"</span>, where=bv.<span style={PIPE_CS.fn}>col</span>(<span style={PIPE_CS.str}>"event"</span>) == <span style={PIPE_CS.str}>"job_created"</span>),{'\n'}
{' '}){'\n\n'}
bv.<span style={PIPE_CS.fn}>App</span>(<span style={PIPE_CS.str}>"0.0.0.0:6400"</span>).<span style={PIPE_CS.fn}>register</span>(<span style={PIPE_CS.ty}>OpsEvent</span>, <span style={PIPE_CS.fn}>VendorHealth</span>, <span style={PIPE_CS.fn}>ZonePressure</span>).<span style={PIPE_CS.fn}>serve</span>()
bv.<span style={PIPE_CS.fn}>App</span>(<span style={PIPE_CS.str}>"http://localhost:8080"</span>).<span style={PIPE_CS.fn}>register</span>(<span style={PIPE_CS.ty}>OpsEvent</span>, <span style={PIPE_CS.fn}>VendorHealth</span>, <span style={PIPE_CS.fn}>ZonePressure</span>)
</>
);

Expand Down Expand Up @@ -1339,7 +1340,8 @@
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 920px) {
.beava-pipelines-grid { grid-template-columns: 1fr !important; }
.beava-pipelines-grid { grid-template-columns: minmax(0, 1fr) !important; }
.beava-pipelines-grid > * { min-width: 0 !important; }
}
`}</style>
</section>
Expand Down
4 changes: 2 additions & 2 deletions beava-website/project/js/_shared/SiteFooter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ const SiteFooter = ({ maxWidth = 1200 }) => (
{SITE_FOOTER_COLS.map(col => (
<div key={col.title}>
<div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--fg3)', marginBottom: 12 }}>{col.title}</div>
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 8 }}>
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
{col.links.map(l => (
<li key={l.label}>
<a href={l.href}
{...(l.external ? { target: '_blank', rel: 'noopener' } : {})}
style={{ fontFamily: 'var(--font-sans)', fontSize: 14, color: 'var(--fg2)', textDecoration: 'none' }}>
style={{ fontFamily: 'var(--font-sans)', fontSize: 14, color: 'var(--fg2)', textDecoration: 'none', display: 'inline-block', padding: '5px 0' }}>
{l.label}
</a>
</li>
Expand Down
19 changes: 18 additions & 1 deletion beava-website/project/sdk/python/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,12 @@ <h2 id="pick-a-path">Pick a path</h2>

<!-- =============== INSTALL =============== -->
<h2 id="install"><span class="step-num">01</span> Install beava</h2>
<p><code>pip install beava</code> drops a platform wheel from PyPI. The wheel ships the SDK <em>and</em> the Rust server binary together (~4 MB, polars / ruff / uv pattern). If you'd rather run the server in a container, Docker is the alternative.</p>
<p>Three ways in, pick whichever fits your stack. <code>pip install beava</code> drops a platform wheel from PyPI that ships the SDK <em>and</em> the Rust server binary together (~4 MB, polars / ruff / uv pattern). <code>brew install</code> puts the server binary on your PATH the Homebrew way. <code>docker run</code> hands you a container on <code>:8080</code> with a persistent volume.</p>

<div class="tabs" data-tabs="server">
<div class="tabs-strip">
<button class="active" data-target="t-pip">pip</button>
<button data-target="t-brew">brew</button>
<button data-target="t-docker">docker</button>
</div>

Expand All @@ -164,6 +165,22 @@ <h2 id="install"><span class="step-num">01</span> Install beava</h2>
<p>The wheel comes from PyPI. The bundled <code>beava</code> binary lands in your Python user-scripts dir (<code>~/.local/bin/beava</code> on Linux, <code>~/Library/Python/&lt;ver&gt;/bin/beava</code> on macOS), so embed mode (<code>bv.App()</code> with no URL) finds it without extra setup. Pin a specific version with <code>pip install beava==0.0.0</code>. Add <code>--memory-only</code> to skip the WAL entirely.</p>
</div>

<div class="tabs-panel" id="t-brew">
<div class="codeblock">
<div class="codeblock-head">
<span class="filename">terminal</span>
<button class="copy-btn">Copy</button>
</div>
<pre><span class="tk-k">brew</span> install beava-dev/beava/beava

<span class="tk-c"># server binary on PATH; `beava` is ready to run</span>
<span class="tk-k">beava</span> --data-dir ./.beava/
<span class="tk-c"># → HTTP listen : 127.0.0.1:8080</span>
<span class="tk-c"># → TCP listen : 127.0.0.1:8081 (enabled=true)</span></pre>
</div>
<p>The formula installs the <code>beava</code> server binary into your Homebrew prefix (<code>/opt/homebrew/bin/beava</code> on Apple Silicon, <code>/usr/local/bin/beava</code> on Intel). Roll forward with <code>brew upgrade beava</code>. The Python SDK still comes from <code>pip install beava</code>; install both if you want embed mode and the client in the same env.</p>
</div>

<div class="tabs-panel" id="t-docker">
<div class="codeblock">
<div class="codeblock-head">
Expand Down
Loading
Loading