Skip to content

Add Rayforce - #1375

Open
alexey-milovidov wants to merge 1 commit into
mainfrom
add-rayforce
Open

Add Rayforce#1375
alexey-milovidov wants to merge 1 commit into
mainfrom
add-rayforce

Conversation

@alexey-milovidov

@alexey-milovidov alexey-milovidov commented Aug 14, 2026

Copy link
Copy Markdown
Member

Adds Rayforce — a zero-dependency
columnar analytics and graph engine in pure C (MIT), queried through Rayfall,
its Lisp-like language. queries.sql therefore holds 43 Rayfall expressions,
one per line, the way jd and bqn hold their own dialects.

(Not the same as the closed #940 — that was RayforceDB's internal 10M-row
harness, opened against this repo by mistake. This is a standard-harness entry
on the full dataset.)

Two measured engine properties shaped the entry

Text columns load as SYM (dictionary-encoded), not STR. STR pool
offsets are uint32_t, so a column's pool is capped at 4 GiB, while at 100M
rows URL/Title/Referer/OriginalURL hold 5–11 GB of bytes each. Opening a
splayed table also validates every STR element, and that cost is superlinear:
0.4 s at 2M rows, 199 s at 9.6M rows (99 s for the parted layout). All-SYM
opens in 17 s at 9.6M rows and stores the same subset in 4.4 GB instead of
7.5 GB.

It runs as an IPC server, with BENCH_RESTARTABLE=yes. The table open is
eager (validate every column file, load the dictionary), so a CLI-per-query
would pay it 129 times per run; the server pays it once per start and times
queries with timeit server-side. The restart is not optional either: with the
server left up, drop_caches cannot evict pages a live process still maps, and
a "cold" query measured 0.029 s versus 0.44 s after a real restart — keeping the
process alive would report every run as warm. BENCH_CHECK_TIMEOUT is raised to
1800 s because the readiness probe now waits for that open.

Query notes

HAVING becomes an outer select over the grouped one; LIMIT n OFFSET m is
take: [m n]; DATE_TRUNC('minute', EventTime) is
(xbar EventTime 60000000000); CASE WHEN is the row-wise (if ...). Q29's
REGEXP_REPLACE is rebuilt from str-find/substr/if and reproduces the
regex exactly on this dataset, fallback rows included. Q25/Q27 project
EventTime alongside SearchPhrase because sorting runs after projection, and
Q35 projects its constant group key in an outer select.

Validation

All 43 queries were diffed against clickhouse-local on a 2M-row subset of the
same CSV: 33 match exactly or modulo row order, and the other 10 differ only in
which of several equally-ranked rows a LIMIT over ties returns (the sort-key
multisets are identical). install, load, the stop/drop_caches/start/check
cold cycle, all 43 queries through ./query, 10 concurrent clients, and the
error paths were exercised end to end.

Upstream bugs found on the way

Neither affects this entry, both filed with minimal repros:

🤖 Generated with Claude Code

Rayforce (https://github.com/RayforceDB/rayforce) is a zero-dependency
columnar analytics and graph engine in pure C, queried through Rayfall,
its Lisp-like language. queries.sql therefore holds 43 Rayfall
expressions, one per line, the way jd/bqn hold their own dialects.
(Not to be confused with the closed #940, which was RayforceDB's
internal 10M-row harness opened against this repo by mistake.)

Two measured engine properties shaped the entry:

  - Text columns are loaded as SYM (dictionary-encoded), not STR. STR
    pool offsets are uint32_t, so a column's pool is capped at 4 GiB,
    and at 100M rows URL/Title/Referer/OriginalURL need 5-11 GB each.
    Opening a splayed table also validates every STR element, and that
    cost is superlinear: 0.4 s at 2M rows, 199 s at 9.6M rows (99 s for
    the parted layout). All-SYM opens in 17 s at 9.6M rows and stores
    the same subset in 4.4 GB instead of 7.5 GB.

  - It runs as an IPC server, with BENCH_RESTARTABLE=yes. Opening the
    table is eager (validate every column file, load the dictionary),
    so a CLI-per-query would pay that 129 times per run; the server
    pays it once per start and times queries with `timeit` server-side.
    The restart is not optional either: with the server left up,
    drop_caches cannot evict pages a live process still maps, and a
    "cold" query measured 0.029 s against 0.44 s after a real restart,
    so keeping the process alive would report every run as warm.
    BENCH_CHECK_TIMEOUT is raised to 1800 s because the readiness probe
    now waits for that table open.

Query notes: HAVING becomes an outer select over the grouped one;
LIMIT/OFFSET is take: [m n]; DATE_TRUNC('minute', EventTime) is
(xbar EventTime 60000000000); CASE WHEN is the row-wise (if ...);
Q29's REGEXP_REPLACE is rebuilt from str-find/substr/if and reproduces
the regex exactly on this dataset, fallback rows included. Q25/Q27
project EventTime alongside SearchPhrase because sorting runs after
projection, and Q35 projects the constant group key in an outer select.

All 43 queries were checked against clickhouse-local on a 2M-row subset
of the same CSV: 33 match exactly or modulo row order, and the other 10
differ only in which of several equally-ranked rows a LIMIT over ties
returns (the sort-key multisets are identical). install, load, the
stop/drop_caches/start/check cold cycle, 43 queries through ./query, 10
concurrent clients and the error paths were all exercised end to end.

Two upstream bugs found on the way, neither affecting this entry:
RayforceDB/rayforce#404 (take: drops pool-backed STR values) and
RayforceDB/rayforce#405 (ungrouped count(distinct) in a select
projection returns the row count).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexey-milovidov
alexey-milovidov deployed to benchmark-approval August 14, 2026 19:37 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant