HoloDb 5.1.1 · embeddable .NET · holographic SQL

One store. Query by key, content, or aggregate.

HoloDb encodes every value as a phase vector and every row as a hologram, in a single associative store. Because rows can be retrieved by resemblance as well as by key, one copy of the data answers three kinds of question, exact SQL by primary key, similarity search by content with NEAREST, and analytics that read maintained totals instead of scanning. No separate vector index, no OLAP cube, nothing to keep in sync.

HoloDb 5.1.1 is built with Pleroma, the exact count-and-sum structure in the Phasor package. What Pleroma brings, and what it costs ↓ · Where the plane is the right tool ↓

Total invoiced across 10,000,000 invoices: SELECT SUM(amount_cents), same query, same data, all results identical

HoloDb
under 0.01 ms
DuckDB
2.56 ms
SQLite
414.54 ms
SQL Server
943.63 ms

HoloDb reads a maintained total rather than adding up 10 million rows: no scan, and response time holds constant as the ledger grows. One run on one machine. This result is the engine's, not the plane's: it ran on the HoloDb engine alone, with no Pleroma plane attached. The full per-query results follow, with the queries where HoloDb loses.

dotnet add package EvaluatedApplications.HoloDb
Holographic · one associative store SQL · real ACID Vector · NEAREST in SQL Analytics · maintained, not scanned Pleroma · plane, index SQL, page store

Quick start

install, then this

dotnet add package EvaluatedApplications.HoloDb
using HoloDb; var db = new HoloDbService(new HoloDbOptions { WalPath = "app.wal" }); db.Execute("CREATE TABLE users (id INT PRIMARY KEY, age INT, name TEXT)"); db.Execute("INSERT INTO users VALUES (1, 30, 'Ada')"); var result = await db.ExecuteAsync("SELECT name FROM users WHERE age >= 18"); foreach (var row in result.Rows) Console.WriteLine(row["name"]);

Built on Pleroma

what it brings, and what it costs

HoloDb 5.1.1 is built with Pleroma, the exact count-and-sum structure in the Phasor package. Three parts of HoloDb are Pleroma parts, and this section says what each gives you today and what it costs. The first two are optional and off until you ask for them.

Where the plane is the right tool

optional
121.40x
Live dashboard (A). Median speed-up of the plane over the engine on the 9 scalar slices of a 10,000,000-event table. Against the best of SQLite, DuckDB and SQL Server: 191.5x.
Stated costQueries outside the plane (the 7 X queries) tie with the engine: 0.97x, class median. On the first call, five of the seven were slower with the plane attached, at 0.42x to 0.84x of the engine's first call; these are single samples. Three of the nine W cells are not interpreted (the engine's own two timing loops disagreed by more than 25%); the class median is as printed.
11.95x
Live ingest while querying (B). The plane makes the eight-query panel 11.95x faster than the engine on a 2,000,000-row base table, the round total 5.78x faster, and the trickle pair (one INSERT plus one query) 7.00x faster.
Stated costWhat the plane costs on the write side: the 10,000-row bulk load takes 11.62 ms against 9.85 ms (1.18x), and 200 single-row INSERTs take 1.55 ms against 1.23 ms (1.26x). The first panel query after each ingest gains only 1.66x (2.304 ms against 3.835 ms).
11.70x
Multi-tenant slices (C). A filter and a COUNT and SUM on one of six tenant tables: median 11.70x faster than the engine and 25.0x faster than the best other engine. The largest tenant reads 0.171 ms with the plane, 4.263 ms for the engine.
Stated costThe C2c cell is not interpreted (its two timing loops on the HoloDb arms disagreed by more than 25%); the medians are as printed. Joins with a WHERE or a GROUP BY on the dimension table are not served by the plane at all; they run the engine's path and tie with it (0.93x to 1.02x).
0.002 ms
A repeated join (C). Join COUNT on the largest tenant (2,000,000 rows), embedded: 0.002 ms with the plane against 0.380 ms for the engine and 1.749 ms for DuckDB. Through the server, 0.078 ms.
Stated costThis is a memo hit: a repeated identical statement with no write to either table in between. A join that is not a memo hit usually costs more than the engine's own join. The first call of a statement costs more through the plane than through the engine in 11 of the 12 join cells.

Constructed to suit the plane, and measured separately from the engine-only comparisons below, which do not use it. One run on one machine on 13 October 2026 on the build released as 5.1.1, not an idle-machine run. Every HoloDb figure is an in-memory database with no write-ahead log. Each figure and its stated cost are set out in full below.

The optional Pleroma aggregate plane (described under "Built on Pleroma" below) pays off on three kinds of workload, and it has costs that sit next to the wins. These workloads were constructed to suit the plane and are measured separately from the engine-only comparisons below, which do not use it. These figures are the EXP-PS1 plane showcase, one run on one machine on 13 October 2026 on the build released as 5.1.1: an 11th Gen Intel Core i9-11950H at 2.60 GHz (16 logical cores), 18.1 GB of 31.7 GB memory free at the start, no other .NET process running, with ordinary desktop applications and the SQL Server service open, so this is not an idle-machine run. Every HoloDb figure is an in-memory database with no write-ahead log; SQLite and DuckDB ran in memory and SQL Server LocalDB used tempdb on disk. Each cell is the median of the lower of two timing loops; the per-round figures in B are medians over rounds. Every timed query returned identical results on all engines, and after each workload PleromaVerify found the plane equal to a rebuild from the columns. "Engine" is HoloDb without a plane, "plane" is HoloDb with the plane attached and default routing, both embedded.

What the three workloads have in common: the filter combinations cannot be pre-indexed because nobody knows in advance which dimensions a person will cut by, the answers must be exact, and the table keeps changing while it is queried.

A. A live dashboard: 10,000,000 events, 32 ad-hoc slices

Six small-domain dimensions (a 14-bit joint) and the measures. Building the plane for 10,000,000 rows took 1,759 ms and its cells take 920,248 bytes. The 32 slices are scalar filters (W), group-bys with and without a filter (G), HAVING (H), COUNT DISTINCT and MIN/MAX with a filter (D), plus queries outside the plane (X) and whole-table queries the engine already answers from a maintained total (S).

Class (queries)Plane vs engine, medianPlane vs best of SQLite, DuckDB, SQL Server, median
W scalar slices (9)121.40x191.5x
G group by (7)20.90x27.4x
H having (2)24.57x42.2x
D distinct, min/max with a filter (3)18.42x36.0x
X outside the plane (7)0.97x (tie)1.5x
Plane speed-up by query class, workload A (10,000,000 events). Each bar is the plane's median speed-up over the HoloDb engine (purple spectrum bar, first row of each class) and over the best of SQLite, DuckDB and SQL Server (blue bar, second row), taken from the table above. Log scale; the dashed line is 1x, a tie.
W scalar slices (9)
Plane vs engine
121.40x
Plane vs best other
191.5x
G group by (7)
Plane vs engine
20.90x
Plane vs best other
27.4x
H having (2)
Plane vs engine
24.57x
Plane vs best other
42.2x
D distinct, min/max with a filter (3)
Plane vs engine
18.42x
Plane vs best other
36.0x
X outside the plane (7)
Plane vs engine
0.97x (tie)
Plane vs best other
1.5x

The best other engine on the W, G, H and D slices is DuckDB in every row shown. In W, three of the nine cells (A10, A11, A12) are not interpreted because the engine's own two timing loops disagreed by more than 25%; the class median is as printed.

First calls. The first call of each query, which pays any one-time work, is also faster with the plane: the median first-call speed-up over the engine is 16.99x for W, 10.90x for G, 13.97x for H and 10.17x for D. A04 takes 1.047 ms on its first call with the plane against 17.792 ms for the engine. Across the first four rows below, the plane takes 0.153 to 2.110 ms, the engine 18.526 to 50.189 ms and DuckDB 25.185 to 67.130 ms.

QueryDuckDBSQL ServerHoloDb engineHoloDb plane
A04 WHERE region = 'north' AND device = 'mobile', COUNT and SUM25.185827.21818.5260.153
A13 GROUP BY region, device, COUNT and SUM56.7123,012.04743.2222.068
A19 GROUP BY region, channel HAVING COUNT(*) > ...67.1303,047.37443.8152.110
A22 COUNT(DISTINCT channel), COUNT(DISTINCT device) with a WHERE37.5062,336.28050.1890.803
A25 (outside the plane: filter on user_id, a column that is not a dimension)12.139486.46611.30011.484

The queries the plane declines or leaves to the engine (the X class: a filter on a non-dimension column, COUNT(DISTINCT user_id), a sum of a non-measure column, a top-10 by user, a filter on a measure, a row list and a point lookup by event id) run the engine's own path with the plane attached and tie with the engine: the seven X queries read 0.95x to 1.04x warm. The plane arm is ahead of the best other engine on them by 1.1x to 3.3x; A25, A28 and A31 are ahead by only 1.1x, and the 3.3x is the point lookup (0.002 ms against SQLite's 0.007 ms). On the first call, five of the seven (A25, A27, A28, A30, A31) were slower with the plane attached, at 0.42x to 0.84x of the engine's first call; these are single samples, and the warm ratios are 0.95x to 1.04x.

B. Live ingest while querying

A 2,000,000-row base table, then 20 rounds of a 10,000-row bulk load, 200 single-row INSERTs and an eight-query dashboard panel, then 300 trickle pairs (one INSERT followed by one group-by query). The panel's results matched in every round. Per round, medians:

Per round (ms)SQLiteDuckDBSQL ServerHoloDb engineHoloDb plane
panel (8 queries)3,794.93112.303,057.90137.9011.53
round total (bulk load, INSERTs, panel)3,816.72239.883,109.90149.4725.84
trickle pair (INSERT plus query)760.78416.088419.6107.6341.090
Workload B, per round, median ms by engine (shorter is faster). Same numbers as the table above, on a log scale. The two HoloDb bars are the engine without a plane (grey) and with the plane (purple spectrum); the other engines are blue.
panel (8 queries)
SQLite
3,794.93 ms
DuckDB
112.30 ms
SQL Server
3,057.90 ms
HoloDb engine
137.90 ms
HoloDb plane
11.53 ms
round total (bulk load, INSERTs, panel)
SQLite
3,816.72 ms
DuckDB
239.88 ms
SQL Server
3,109.90 ms
HoloDb engine
149.47 ms
HoloDb plane
25.84 ms
trickle pair (INSERT plus query)
SQLite
760.784 ms
DuckDB
16.088 ms
SQL Server
419.610 ms
HoloDb engine
7.634 ms
HoloDb plane
1.090 ms

The plane makes the panel 11.95x faster than the engine and the round total 5.78x faster; the trickle pair is 7.00x faster. Through the server the round total is 179.34 ms for the engine and 45.65 ms with the plane. What the plane costs on the write side: the 10,000-row bulk load takes 11.62 ms against 9.85 ms (1.18x), and 200 single-row INSERTs take 1.55 ms against 1.23 ms (1.26x), about 7.8 microseconds against 6.2 per INSERT. The first panel query after each ingest (P1, the A04 shape) gains only 1.66x (2.304 ms against 3.835 ms), while the other seven gain 2.87x to 319.60x. The likely cause is the flush of the plane's pending changes that the first query after an ingest triggers; the flush was not timed separately. DuckDB takes 6.288 ms on that query, so the plane is still ahead of it (2.7x), and on the others the plane takes 0.202 to 2.082 ms against DuckDB's 5.147 to 28.316 ms.

C. Multi-tenant: slices and joins

Five million events spread over six tenant tables (2,000,000 down to 150,000 rows) and a 2,000-product dimension table.

Tenant slices. A filter and a COUNT and SUM on one tenant (C0c to C5c): median 11.70x faster than the engine and 25.0x faster than the best other engine. The largest tenant reads 0.171 ms with the plane, 4.263 ms for the engine, 6.312 ms for DuckDB and 195.195 ms for SQL Server. The C2c cell is not interpreted (its two timing loops on the HoloDb arms disagreed by more than 25%); the medians are as printed.

Joins, stated exactly. The join in these tests is the same statement repeated: tenant events joined to the products table on the product key (about 2,000 keys), with COUNT(*) alone (a) or COUNT(*) and two SUMs (b). The plane serves a repeat from a memo of the exact answer, which by design (and by the joincache checks, not by this run) is valid until either of the two tables is written. So the very low figures below are for a repeated identical statement with no write in between:

QueryDuckDBSQL ServerHoloDb engineHoloDb plane, embeddedHoloDb plane, server
C0a join COUNT, largest tenant (2,000,000 rows)1.749307.0390.3800.0020.078
C0b join COUNT and two SUMs, largest tenant2.906477.0432.7160.0030.079
C5a join COUNT, smallest tenant (150,000 rows)0.94723.6780.1100.0020.071
C5b join COUNT and two SUMs, smallest tenant1.39237.1580.9990.0030.073

Embedded, a memo hit takes 0.001 to 0.003 ms; through the server it takes 0.071 to 0.084 ms, against DuckDB's 0.947 to 2.906 ms and the engine's 0.110 to 2.716 ms for the same joins. A join that is not a memo hit usually costs more than the engine's own join. The first call of a statement costs more through the plane than through the engine in 11 of the 12 join cells. A call after a write to either table was not timed in this run; by design it runs the same computation as a first call, but a first call also pays one-time costs, so treat the first-call figures below as indicative of it, not as a measurement. First calls in this run: join COUNT 1.016 to 2.921 ms through the plane on the five later tenants (1.372, 1.088, 2.921, 1.016, 1.245 ms) against 0.179 to 0.573 ms for the engine; the first join of the whole run, which also pays one-time start-up, was the one cell where the plane was lower: 13.800 ms against the engine's 18.540 ms. Join COUNT and two SUMs 2.348 to 8.134 ms through the plane (8.134, 6.230, 3.138, 2.610, 4.498, 2.348 ms) against 1.364 to 3.076 ms for the engine. If the tables are written between nearly every read, expect the plane join to cost more than the engine's own join, and the plane's value on such a workload to come from the filters and group-bys above, not the joins. Joins with a WHERE or a GROUP BY on the dimension table are not served by the plane at all; they run the engine's path and tie with it (C-X1 to C-X4, 0.93x to 1.02x).

The checks that guard these figures

Queries the plane declines or leaves to the engine moved within noise: of the 16 queries that run the same path in both arms, none fell outside the registered band of 0.8x to 1.25x, and the class medians were 0.97 (workload A) and 0.99 (workload C). A placebo built to catch a wrong plane (the measure column shuffled at 1,000,000 rows) was caught on every query it was registered to change and matched on every query it was registered not to change: 0 failures of 32. PleromaVerify found the plane identical to a rebuild from the columns after each workload.

The headline results are the engine's, not the plane's. The 10,000,000-invoice billing comparison against SQLite, DuckDB and SQL Server (EXP-PB12), and the single-row write tables against SQLite, were run on the HoloDb engine alone, with no Pleroma plane attached. All HoloDb figures on this page come from in-memory databases with no write-ahead log. The plane is a separate, opt-in feature, measured in its own runs below, and it was not on in those comparisons.

The aggregate plane: exact answers from a derived summary

optional

You name a few small-domain columns (dimensions, INT or TEXT) and the INT columns to sum (measures). HoloDb keeps a Pleroma summary of them, updated by every write, and answers filters, multi-column GROUP BY, COUNT(DISTINCT), MIN/MAX of a dimension and equi-join COUNT and SUM from it, without scanning the table.

What it gives you

  • Exact and derived. The columns stay the source of truth; the plane is rebuilt from them. PleromaVerify rebuilds it and compares, and returns nothing when they agree. Sums are held exactly past 64 bits: the plane raises an error rather than wrap, and PleromaSumExact returns the exact 128-bit total.
  • It declines safely. Anything the plane does not hold (AVG, a column that is not a dimension or measure, a dimension over its cap of distinct values, a join over 4,095 keys) goes to the engine, so a query returns the same rows with the plane on or off.
  • Measured on the billing workload (EXP-PB11, 10,000,000 invoices, plane on region, status, month and the measure amount_cents). Building the plane took 741 ms and its cells take 34,528 bytes. "Overdue outstanding" went from 5.93 ms to 0.01 ms, and "revenue by region and status" from 32.30 ms to 0.18 ms. All 16 results were identical across all engines.

What it costs, stated plainly

  • At 1,000,000 rows (17 query classes) the plane is slower than the engine alone on 4 of them: whole-table COUNT(*) (0.48x), whole-table SUM (0.47x), single-column GROUP BY (0.43x) and MIN/MAX (0.53x), because the engine reads a maintained total in microseconds there; it ties on 3 and is not interpreted on 2 (noise). At 100,000 rows the engine is faster on 7 of the 17 classes.
  • Single-row INSERT costs 1.4x to 1.9x more with a plane attached and UPDATE 1.6x to 1.8x (100,000 to 1,000,000 rows); these cells are microseconds and move with arm order.
  • On the billing workload it does not help the joins or the top-customers query: it declined the big-ticket count (the filter is on a measure), top customers (200,000 distinct customer ids against a cap of 1,024) and all five joins (200,000 join keys against a limit of 4,095). The default routing, PleromaMode.Auto, also leaves to the engine the shapes where the engine already reads a maintained total.
  • Limits. Memory-resident tables only (a paged table refuses a plane; a database opened with a log is still durable). A plane attached with EnablePleroma is not persisted: call it again after Open. A plane created with SQL is replayed from the log and survives Checkpoint(). Opt-in per table.

The plane against the engine alone, 1,000,000 rows (EXP-PB2 re-run, EXP-PB3, EXP-PB5): the 8 classes where it is faster and the 4 where it is slower

Query classHoloDb engineHoloDb planePlane vs engine
Join on a TEXT key37.36 ms8.0 us4,670x faster
WHERE = on 1 column1.28 ms3.8 us336x faster
Range count343.4 us2.7 us127x faster
Join SUM (both sides)4.92 ms85.5 us58x faster
WHERE LIKE ... OR1.24 ms37.2 us33x faster
WHERE on 3 columns (AND/IN)1.63 ms144.2 us11x faster
COUNT DISTINCT with WHERE1.91 ms630.7 us3.0x faster
GROUP BY with WHERE3.10 ms2.32 ms1.34x faster
COUNT(*) whole table1.6 us3.3 usslower (0.48x)
SUM(qty) whole table1.8 us3.8 usslower (0.47x)
GROUP BY 1 column4.4 us10.2 usslower (0.43x)
MIN/MAX of an INT column1.8 us3.4 usslower (0.53x)

One run on one machine. The other 5 classes (3 ties, 2 not interpreted as noise) are in the full table, with the 100,000 and 300,000 row results and every open limit. Against SQLite the engine alone is faster on every one of the 17 classes.

The join rows use a very small dimension table. The benchmark joins the 1,000,000-row table to a 40-row table: the two INT-key joins have 40 distinct join keys, and the TEXT-key join has 4 distinct keys (4 labels, each on 10 of the 40 rows). Those key counts are far below the roughly 2,000 product keys of workload C above and the 200,000 join keys of the billing workload, so these join figures say little about joins over many keys.

Index SQL and EXPLAIN: build it and see why

optional

The plane has a SQL form, so a remote client can build it through the server like any statement. SHOW INDEXES reports its size and how many queries it served and declined, and EXPLAIN reports why a query was served or declined, without running it.

using HoloDb; var db = Database.Open(); db.Execute("CREATE TABLE sales (id INT PRIMARY KEY, region TEXT, tier INT, qty INT)"); db.Execute("INSERT INTO sales (id, region, tier, qty) VALUES (1,'north',1,10),(2,'north',2,-4),(3,'south',1,7),(4,'east',3,5),(5,'south',2,9)"); // build the plane from SQL: key columns are dimensions, INCLUDE columns are measures db.Execute("CREATE INDEX by_region ON sales (region, tier) INCLUDE (qty) USING SUMMARY"); db.PleromaMode = PleromaMode.Always; Console.WriteLine(db.Execute("SELECT SUM(qty) FROM sales WHERE region = 'north' OR tier = 3").Rows[0]["sum(qty)"]); // 11 // why was it served, or declined? foreach (var r in db.Execute("EXPLAIN SELECT region, SUM(qty) FROM sales WHERE region = 'north' GROUP BY region").Rows) Console.WriteLine($"{r["kind"]}: {r["route"]}"); // route: Pleroma plane (exact aggregate from the derived plane) foreach (var r in db.Execute("EXPLAIN SELECT COUNT(*) FROM sales WHERE qty > 5").Rows) Console.WriteLine($"{r["kind"]}: {r["route"]}"); // decline: WHERE uses a column that is not a plane dimension, a dimension over its cap, a column-to-column comparison or a subquery // access: block-wise mask scan // route: predicate count (block-wise byte-mask, no position list) Console.WriteLine(db.PleromaVerify("sales") ?? "plane equals a rebuild from the columns"); // plane equals a rebuild from the columns Console.WriteLine(db.PleromaSumExact("SELECT SUM(qty) FROM sales")); // 27

Limits. EXPLAIN runs the engine's own routing code in a mode that stops before the heavy work, so what it reports is the decision the real statement takes. It does not run an IN (SELECT ...) subquery (it is shown unresolved). Older binaries cannot open a log or snapshot that contains this SQL. A checkpoint on 5.1.0 produces a file an older binary can read only if no table has a named index or SQL-created plane.

The Pleroma page store: durable, checksummed pages

optional

PleromaPageStore holds many exact Pleroma pages as the data itself, durably and larger than memory, with no columns behind them. PleromaSparsePageStore does the same for sparse pages over a large vocabulary.

What it gives you

  • Checksummed pages. Every page carries a check prime (a mirror of its cells in a second prime) and a header hash. Verify proves every record and parity group intact; VerifyOnLoad does the same on every load and is off by default (about 100 microseconds for a 110-key page).
  • Parity repair. Records are grouped (8 by default) with one exact-sum parity record per group, and the members of a group sit on different 8 KB pager pages. Any one lost pager page, or any one damaged record per group, is rebuilt by Repair. A rebuilt page must pass its own check before it is written; a page and its group's parity both damaged are reported as unrecoverable, never rebuilt wrong.
  • Restore points. Checkpoint() returns a restore point and RestoreTo returns the store to exactly that state, with no recount. It is copy-on-write, so a restore point costs one before-image per page changed since, not a copy of the data. Eight are kept by default.
  • Exactly-once replay. Every AddBatch is journaled (fsynced by default) before it is applied, and each page remembers the last batch applied to it, so crash recovery applies a logged add exactly once, even over pages already flushed. A torn journal tail is discarded, so a batch survives whole or not at all.
  • Proof. 72 of 72 store checks pass (crash replay, torn tail, restore, repair from a parity group); eight deliberate mutations and a tiny-cache case each turned the suite red.

What it costs, stated plainly

  • One shape per store, and it must be CompleteKeys and admissible for a check prime; shapes with a continuous component or a relation face cannot be saved.
  • One process opens a store at a time and one writer works at a time.
  • A batch is applied page by page, so a reader sees every page whole but may see a batch on some pages and not yet on others.
  • The sparse store has no parity groups, restore points or event journal yet.
  • Page-store throughput is not reported on this page.
using HoloDb; using Phasor; var shape = new HologramShape { KeyCapacity = 2, KeyCycle = 110, CompleteKeys = true }; var dir = Path.Combine(Path.GetTempPath(), "plrstore-restore"); if (Directory.Exists(dir)) Directory.Delete(dir, true); using (var store = PleromaPageStore.Open(dir, shape)) { store.AddBatch(new[] { new PageAdd(42, 5, 3) }); var point = store.Checkpoint(); // a restore point: id and journal position store.AddBatch(new[] { new PageAdd(42, 5, 10), new PageAdd(9, 1, 1) }); Console.WriteLine($"{store.Counts(42)[5]} {store.Contains(9)}"); // 13 True store.RestoreTo(point.Id); // back to the point, no recount Console.WriteLine($"{store.Counts(42)[5]} {store.Contains(9)}"); // 3 False Console.WriteLine(store.Verify().Clean); // True Console.WriteLine(store.Repair().Unrecoverable.Count); // 0 } Directory.Delete(dir, true);

The engine itself, without the plane or the page store, opens a database of 1,000,000 rows in about 2.0 s by replaying its write-ahead log and about 1.1 s from a checkpoint (see Opening a database). Method, experiment ids and every open limit are on the benchmarks page; the structure underneath is described on the Pleroma page.

How the holographic store works

how it works

Most databases store rows and bolt the rest on top, a separate analytical cube, a separate vector index, each kept in sync with the source of truth. HoloDb stores the source of truth as holograms.

Every value is encoded as a phase vector, and a row's columns are bound into one hologram; those holograms form a single associative store. Retrieval works by resemblance, not only by address, NEAREST (region='EU', status='overdue') returns the rows whose holograms are closest to a partial description, ranked by similarity: a vector query in SQL, over the same rows, with no separate index.

Aggregates fall out of the same algebra. An integer column's running total is a single vector, a residue-phase encoding read back in constant time, so SUM, COUNT and single-column GROUP BY read a maintained total rather than scanning, and hold their speed from one million rows to tens of millions.

From 8,000 rows upward an approximate sublinear index (locality-sensitive hashing) ranks candidates, so the result can differ from an exact scan (its recall is not measured on this page); below that, and whenever the query has a WHERE, ranking is an exact scan. Integer aggregates are exact and constant-time; real-valued columns use a fast SIMD pass.

writes inv 4250 inv 1700 inv 384217 …10,000,000 invoices running total fixed-size vector SELECT SUM(amount_cents) → reads the tally, skips the rows under 0.01 ms

A billing SaaS's invoice ledger

the shape of the data

The billing comparison on this page comes from one realistic workload: a normalised invoices fact joined to a customers dimension, what a subscription business runs its dashboards on. 10 million invoices across 200,000 customers and 24 months of history, and the queries are the ones a billing dashboard actually fires: revenue by status, month and region; the running total; the biggest customers; a single-invoice lookup; and cross-table rollups by customer segment and signup cohort.

CREATE TABLE customers ( customer_id INT PRIMARY KEY, -- 200k customers name TEXT, segment TEXT, -- smb / mid / enterprise / strategic tier TEXT, -- gold / silver / bronze signup_month INT ); CREATE TABLE invoices ( invoice_id INT PRIMARY KEY, customer_id INT, -- FK → customers, ~50 invoices each month INT, -- yyyymm, 202301 … 202412 region TEXT, -- US / EU / APAC / LATAM status TEXT, -- paid / pending / overdue / refunded / failed amount_cents INT -- money as integer minor units, never a float );
  • Invoices 10,000,000
  • Customers 200,000
  • History 24 months
  • paid / pending / overdue 78% / 10% / 7%
  • refunded / failed 3% / 2%
  • Regions US·EU·APAC·LATAM 40/30/20/10%
  • Invoice size (long-tail) $20 to $50,000

Money is stored as integer cents, the usual way ledgers store money, and the reason HoloDb can keep revenue totals exact and constant-time.

Same SQL. Same data. Verified identical.

the scoreboard

Five engines on one billing workload: SQLite (in-memory), DuckDB (in-process), SQL Server 2022 LocalDB (on disk, in tempdb), HoloDb embedded and HoloDb server (networked over TLS on the loopback interface), over 10,000,000 invoices joined to 200,000 customers, each loaded through its own bulk path. Before any query is timed, its full result set is normalised and compared across all five engines; every one of the 16 results was identical. Every figure is one run on one machine (EXP-PB12, the final run, 12 October 2026, with no other benchmark or heavy application running), the median of 5 runs through each engine's typed client API, and every loss is shown.

The headline results are the engine's, not the plane's. This comparison, and the single-row write tables against SQLite on the benchmarks page, were run on the HoloDb engine alone, with no Pleroma plane attached. What the plane adds, and what it costs →

under 0.01 ms
Total invoiced across 10,000,000 invoices: SQLite 414.54 ms, DuckDB 2.56 ms, SQL Server 943.63 ms. HoloDb reads a maintained total, not 10 million rows.
15 of 16
As a networked service HoloDb server is faster than SQL Server on 15 of the 16 billing queries, including all five joins (21x to 185x). It is slower on the single-row lookup: 0.18 ms against SQL Server's 0.07 ms.
13 / 2 / 1
Embedded HoloDb against DuckDB: faster on 13 queries, tied on 2, slower on 1 (the signup-cohort join, 1.25x). SQLite is ahead on the single-invoice lookup (0.01 ms against 0.02 ms).

In-process, in memory HoloDb embedded against DuckDB (and SQLite), median ms

Billing querySQLiteDuckDBHoloDb embeddedFaster (vs DuckDB)
invoices by status3,719.6641.630.02HoloDb, more than 1,600x
revenue by region3,492.7034.330.02HoloDb, more than 1,300x
total invoiced414.542.560.00HoloDb, more than 510x
revenue by month3,584.015.480.04HoloDb, more than 120x
invoice count13.900.680.00HoloDb, more than 130x
look up one invoice0.010.600.02SQLite ahead, 0.01 ms
revenue by segment (JOIN)6,527.9749.0114.71HoloDb 3.3x
top named customers (JOIN)8,944.31291.33132.35HoloDb 2.2x
revenue by segment and status (JOIN)10,685.4177.5436.17HoloDb 2.1x
overdue outstanding440.8011.936.12HoloDb 1.9x
revenue by region and status7,495.5258.0935.58HoloDb 1.6x
top customers by revenue4,346.30129.6189.87HoloDb 1.4x
largest invoices573.943.282.42HoloDb 1.4x
big-ticket invoices (over $5k)382.922.572.49tie (1.03x)
enterprise revenue (JOIN)3,047.438.688.97tie (DuckDB 1.03x)
revenue by signup cohort (JOIN)6,469.1611.9614.92DuckDB 1.25x

The purple cell is HoloDb where it is faster than DuckDB; the green cell and the amber note mark the rows where another engine is ahead: DuckDB on the signup-cohort join, SQLite on the single-invoice lookup. The five rollups and the lookup read a maintained total or the primary-key index, so they take microseconds and do not grow with the ledger (the benchmark prints two decimals, so those ratios are lower bounds). Where HoloDb lost: the signup-cohort join, 1.25x behind DuckDB (a parallel scan of the 10 million invoices at about 80 ns per row per core against DuckDB's columnar engine), and the single-row lookup against SQLite. Bulk load, rows per second: DuckDB 1,159,279; HoloDb server 1,051,473; SQLite 726,050; SQL Server 693,231; HoloDb embedded 679,271.

Networked service HoloDb server against SQL Server 2022 LocalDB (and SQLite), median ms

Billing querySQLiteSQL ServerHoloDb serverFaster (vs SQL Server)
revenue by region3,492.702,088.590.19HoloDb, about 11,000x
invoices by status3,719.662,178.580.20HoloDb, about 10,900x
revenue by month3,584.011,485.330.35HoloDb, about 4,200x
total invoiced414.54943.630.25HoloDb, about 3,800x
invoice count13.90470.330.17HoloDb, about 2,800x
largest invoices573.941,472.472.74HoloDb, about 540x
revenue by signup cohort (JOIN)6,469.162,562.8413.88HoloDb, about 185x
enterprise revenue (JOIN)3,047.431,550.708.55HoloDb, about 180x
revenue by segment (JOIN)6,527.972,626.8414.65HoloDb, about 180x
revenue by segment and status (JOIN)10,685.415,089.7034.35HoloDb, about 150x
big-ticket invoices (over $5k)382.92397.233.06HoloDb, about 130x
overdue outstanding440.80588.615.97HoloDb, about 100x
revenue by region and status7,495.522,792.4434.46HoloDb, about 81x
top customers by revenue4,346.302,426.1087.17HoloDb, about 28x
top named customers (JOIN)8,944.312,616.03127.51HoloDb, about 21x
look up one invoice0.010.070.18SQL Server, 2.6x

Both are networked services paying a client/server round trip; HoloDb serves its working set from RAM and SQL Server reads its on-disk table. HoloDb server is slower on the single-row lookup, where SQL Server answers in 0.07 ms and HoloDb server in 0.18 ms. The server figures include the first-call cost of the network path: a separate longer loop through the same client measured the rollups and the lookup at a steady 0.06 to 0.10 ms.

One run on one machine; one synthetic workload; HoloDb figures are from an in-memory database with no write-ahead log and no fsync. The optional Pleroma plane is not on in this comparison; a separate run with a plane attached took two of these queries from 5.93 ms to 0.01 ms and from 32.30 ms to 0.18 ms. Before 5.1.0 shipped, every benchmark cell was run twice on the release build, back to back on an idle 16-core machine (EXP-PB13). HoloDb is built for the mixed workload: a transactional ACID store, durable and larger-than-memory, that also resolves dashboard rollups in microseconds and runs vector search, in a single embeddable dependency. Full methodology, the plane results and every open limit →

One store, four databases' worth of jobs

capabilities

Relational rows, analytical rollups and a vector index are usually three separate systems kept in sync. Here they are three views of the same holographic store, one dotnet add package, one copy of the data.

Holographic, associative store

Values are encoded as complex phase vectors, and a row's columns are bound into one hologram, so rows can be retrieved by resemblance, not just by key. The maintained totals are superpositions, sums of encodings, that a row is removed from by exact subtraction, which keeps UPDATE and DELETE exact. Holograms are derived from the columns, not stored per row, so there is no per-row space cost.

Transactional SQL

Real ACID: a reader/writer lock, rows journaled as they change so BEGIN does not copy the table and ROLLBACK replays the journal, write-ahead logging (a durable database flushes each commit to disk with fsync before it returns), and crash recovery bounded by live data, not write history. A substantial SQL dialect, joins, subqueries in IN, LIKE, BETWEEN, GROUP BY/HAVING, transactions.

One PRIMARY KEY per table (INT or TEXT). ALTER TABLE t ADD COLUMN is supported; no OUTER joins, DROP, rename, or type change yet.

Content-addressable search

Retrieve by resemblance, not just by key: SELECT … FROM t NEAREST (col=val,…) WHERE … LIMIT k ranks rows by similarity to a partial description, a vector query in SQL, over the same rows, with no separate index to build or keep in sync. From 8,000 rows upward an approximate sublinear index (locality-sensitive hashing) ranks candidates, so the result can differ from an exact scan (its recall is not measured on this page); below that, and whenever the query has a WHERE, ranking is an exact scan.

NEAREST ... WHERE filters first and then ranks the survivors; NEAREST does not combine with GROUP BY; an UPDATE/DELETE/ALTER TABLE forces its index to rebuild.

Size-independent analytics

Because an integer total is itself one vector, whole-table COUNT/SUM/MIN/MAX and single-column GROUP BY read a maintained value in O(1), no scan, flat as the table grows. Zone maps, compiled predicates and a columnar path keep ordinary filters fast.

Integer aggregates are exact + constant-time; real-number sums use a SIMD scan.

Durable, larger-than-memory

The paged engine stores tables in 8 KB pages in a buffer pool, plus a write-ahead log: cold pages spill to disk, so a table can exceed RAM, with bounded crash recovery, and the size-independent aggregates are maintained on disk.

A paged table's primary-key index stays in RAM, at about 35 bytes per row (measured: 39 and 34 bytes per entry for a 1,000,000 and a 10,000,000 entry map), the limit for tables far bigger than memory.

Dashboard reads during live ingest

Whole-table counts and sums, and GROUP BY on a column with up to 64 distinct values, are served from a snapshot republished after every write, without taking the engine's write lock. Measured (EXP-PB10, one run: 2,000,000 events in 1,000-row batches at 1.16 million events a second, 4 concurrent readers): median read 0.009 to 0.09 ms, p99 0.8 ms (group by region), 1.1 ms (sum) and 6.6 ms (count), worst single read 6.7 ms. A GROUP BY with more than 64 distinct values (the per-minute series in the same test has 167) takes the normal locked path and waited behind the ingest for up to 0.9 s (p99 0.9 s).

Opening a database

Opening a database of 1,000,000 rows (a 43 MB log) takes about 2.0 s by replaying its write-ahead log (1.95 to 2.05 s across two runs) and about 1.1 s from a checkpoint (1.09 to 1.11 s), with the loaded state checked correct and a heap of about 110 bytes per row.

Conditions: in process, a newly started process each time, an idle 16-core machine on 12 October 2026. This is the engine's own recovery and checkpoint; it does not use the plane or the page store.

Pleroma aggregate plane (optional)

Attach an exact, derived plane to chosen small-domain columns with EnablePleroma. Writes keep per-column shadows current, and the plane answers WHERE aggregates over any AND/OR/NOT/LIKE/IN/BETWEEN predicate, multi-column GROUP BY, COUNT(DISTINCT col), MIN/MAX and equi-join COUNT/SUM from Phasor Pleroma stores, with signed sums held exactly past 64 bits. Anything outside the plane's columns or caps is answered by the engine, so results never differ. The columns remain the source of truth; the plane is rebuilt from them in batch.

Measured on one machine, with losses stated: at 1,000,000 rows the plane is slower than the engine on four of the 17 query classes and wins where there is a real scan or join to avoid. Full results and open limits →

Indexes and EXPLAIN

Manage indexes in SQL: CREATE INDEX [name] ON t (cols) [INCLUDE (cols)] [USING SORTED | SUMMARY], with an optional WITH (max_dim_bits = n, max_joint_bits = n). USING SUMMARY builds the aggregate plane from SQL; DROP INDEX name removes an index and SHOW INDEXES [ON t] lists them. EXPLAIN SELECT ... reports which route and which index or zone map answers a query, and why a faster one was declined, without running it.

A USING SUMMARY plane built from SQL covers in-RAM tables only and is rebuilt after a restart and a checkpoint; a paged table refuses it.

Pleroma page store (optional)

PleromaPageStore holds many exact Phasor Pleroma pages as the data itself, durably and larger than memory, and PleromaSparsePageStore does the same for sparse large-vocabulary pages. Readers do not block on a writer working on other pages, adds are journaled before they are applied and replay exactly once after a crash, restore points rewind the store to a journal position without recounting, an opaque event journal shares the same order, and every page carries a Phasor check prime plus parity groups so Repair can rebuild a lost or corrupted page.

The Pleroma plane is in-RAM tables only. A plane attached with EnablePleroma is not persisted (call it again after Open); one created with SQL is replayed from the log and survives Checkpoint(). The sparse page store has no parity groups, restore points or event journal yet; the dense store has all three. Page-store throughput is not reported here.

Runs everywhere .NET runs. Including this tab.

deploy

Embedded, in-process

var db = new HoloDbService(new HoloDbOptions { WalPath = "app.wal" }); db.Execute("CREATE TABLE t (id INT PRIMARY KEY, region TEXT, amount REAL)"); var r = await db.ExecuteAsync("SELECT region, SUM(amount) FROM t GROUP BY region");

Sync or async, plus a column-native BulkLoad. Register with DI via AddHoloDb(), or attach to an EvalApp pipeline.

Networked server + typed client

docker run -p 5432:5432 -v holodb-data:/data \ ghcr.io/evaluatedapplications/holodb:latest --token s3cret

A self-hosted TLS server; the .NET client mirrors the embedded surface, so moving from in-process to networked changes only how the handle is obtained. Columnar results stay columnar on the wire via HoloDb.Protocol.

In the browser (WebAssembly)

The in-memory engine is pure managed .NET, so it compiles to WebAssembly and runs client-side, the Analyst tool is a real HoloDb in a browser tab, nothing leaving the device. Ideal for local/offline analytics and "your data stays with you" apps.

The Docker image is rebuilt from source and published as :latest (and :<version>) on every release, so :latest is the current engine.

Get started

Current version: HoloDb 5.1.1 (NuGet package EvaluatedApplications.HoloDb).

Add the package and go, a commercial licence, with evaluation and non-commercial use permitted without one, and no feature gated in code. Licence details →

dotnet add package EvaluatedApplications.HoloDb