Black Rainbow LabsInillucent
Research · 2 studies

How it got
faster

A lab report on how Inillucent's engine went from 432% slower than SQLite to 505% faster in five weeks. Every number comes from a run in the repository, and the charts show each change where it landed.

01 · Study · 29 September 2026Which retrieval techniques find the right text for RAG02 · Lab · 8 October 2026How Inillucent was made faster than SQLite
Lab · 8 October 2026

How Inillucent was made faster than SQLite

On 5 September 2026 the engine was 432% slower than SQLite 3.53.4 on its own benchmark. On 8 October release 2.4.2 was 505% faster on the same benchmark, 276% faster through the API a program calls, and 74% faster when called from the command line, the shell, Python and Node. This report explains the loop we ran, the benchmarks we trusted and why, the changes that moved the numbers most, the advice we took and the advice we did not, and the mistakes in measurement we found on the way.

Engine, 5 September
432% slower
Engine, 8 October
505% faster
Through the API, 3 to 8 October
18% slower to 276% faster
Called by programs, 5 to 8 October
216% slower to 74% faster
Workloads faster than SQLite, 11 October
110 of 116

01The short version

A hill climb is a loop: measure, change one thing, measure again, and keep the change only when the numbers say it helped. The work in September followed that loop, and from 3 October we ran six formal hill climbs, each with a fixed eval, against SQLite on one Windows machine. Every speed figure on this page is a ratio of SQLite’s time to Inillucent’s in the same round, so 2.00x is 100% faster and 0.50x is 100% slower.

SQLite’s time over Inillucent’s
SQLite
  1. The engine was rewritten. Leaf pages store rows column by column, and the executor works on batches of rows. 432% slower to 206% faster.
  2. A write statement stopped rebuilding itself on every execution, and the transaction family rose above SQLite. 283% to 326% faster.
  3. A commit became one log append and one sync, count(*) one addition per batch, and an index build writes its pages once. 255% to 353% faster.
  4. A fix to the measurement. Unpinned, Windows had put Inillucent on the slow efficiency cores and SQLite on the fast ones. Both are now pinned to the same eight performance cores.
  5. Correlated subqueries stopped growing a 3.2 MB array on every execution. The last graded run: 419% faster.
  6. Release 2.4.2, after the hill climbs through the API and through how programs call it. 505% faster.
The engine plan, 100,000 rows, against SQLite 3.53.4. The numbered points are the changes listed under the chart. The 5 September figure is computed from that day’s ten family ratios. The runs of 7 and 8 October were not graded, because the machine was not quiet enough, so they read somewhat high.
DateWhat changedWhat it did
5 to 6 SeptemberThe engine was rewritten around column leaf pages and a batch executor432% slower to 206% faster
20 SeptemberOne log append and one sync per commit255% to 353% faster
23 SeptemberBoth engines pinned to the same fast coresA measurement fix
26 SeptemberCorrelated subqueries stopped allocating419% faster, the last graded run
3 to 5 OctoberThree climbs through the API applications call18% slower to 116% faster
5 to 6 OctoberTwo climbs on how programs call it216% slower to 64% faster
7 OctoberA climb on common operations and search51.5% faster than release 2.3.4
8 OctoberRelease 2.4.2 measured for this report505% faster on the engine, 276% through the API, 74% called by programs

02What a hill climb is

Each round changes one thing and is judged by a rule written down before the first measurement. The rule, the workloads and the split into training and held out workloads do not change once the climb starts.

  • A workload is held out when the FNV-1a hash of its name is 0 mod 3. Held out workloads are never used to design a change, only to accept one.
  • Every change is a general mechanism, such as a set lookup, a cached setup or a lock that is not needed. None is keyed on a workload’s SQL text or its data.
  • Builds are run in turn, pass by pass, in one quiet window, so a change in the machine’s speed moves both builds.
  • A change is kept when the workloads it targets are faster with their whole 95% interval above 1.00, the training and held out means do not fall by more than 2.5%, processor time and memory do not rise by more than 10% and 3%, the tests pass, and every answer still agrees with SQLite.
Before the climb
  1. 01Fix the workloads
  2. 02Fix the train and held out split
  3. 03Write the keep rule
  4. 04Measure a baseline
Each round
  1. 01Profile a losing training workload
  2. 02Change one thing
  3. 03Measure beside the base
  4. 04Keep or revert
One round of a hill climb. A change that is kept gets a test that fails without it. A change that is reverted is written down with its numbers, so nobody tries it again.

03The benchmarks

Every benchmark runs both engines in the same rounds. A workload’s score in one round is SQLite’s time over Inillucent’s in that round, so a machine that slows down slows both. A workload’s result is the median of its rounds, and a set’s result is the geometric mean of its workloads, so one very fast workload cannot carry it. The engine headline weights ten families, with the weights fixed in a file before any measurement was taken.

BenchmarkWhat it runsSize
The engine plan, inillucent-fullgateThe engine’s own entry points, in ten weighted families. The published headline34 workloads
The same plan with --api connectionThe same workloads through Connection::prepare and Statement::step, which every language driver calls34 workloads
The hill climb planWhat applications meet and the engine plan does not: rows written by INSERT, tables that churn, queries applications write, edge cases, and checks that every written row is still there48 workloads, 28 train and 20 held out
The usage benchmarkOne process per command, SQL scripts piped into the shell, the Python driver and the npm package, against the sqlite3 shell, Python’s sqlite3 module and Node’s node:sqlite25 workloads
inillucent-searchprobe5,000 rows of 384 numbers in a search table: inserts, keyword, vector and hybrid searches6 cases
inillucent-hcprobeOne statement shape at a time, in nanoseconds a callper shape
The nightly runThe long test suites, every release target and the gates, every night at 02:00all of them

04Making the numbers trustworthy

A speed number is only useful when a reader can believe it. Each of these rules came from a number that looked like an answer and was not.

  • A time counts only after the answer is checked. Every answer is hashed and compared with SQLite’s on every round. The first climb found two wrong answers this way: a reverse ordered LIMIT 20 after 20,000 inserts returned rows from the middle, and a prepared OFFSET returned the first page every time.
  • Both engines run on the same cores. The machine has 8 fast performance cores and 16 slow efficiency cores. The same build read 4.40x unpinned and 4.97x pinned, and nothing in the output said so.
  • The machine must be quiet. A busy machine slows SQLite about twice as much as Inillucent, so it makes Inillucent look faster. The gate times SQLite against its own idle speed and grades a run only when SQLite is within 3%.
  • Measure the noise first. In the climb of 7 October, two rounds that changed no code the gate runs read 1.003 and 0.999.
  • Use medians. One SQLite round sometimes took 20 to 35 times its usual time when its own checkpoint landed in it.
  • Design from the training set and accept on the held out set. A change that helps only the workload it was written for shows up as a held out set that did not move.
A number that looked like an answerWhat it wasThe fix
scan.aggregate read 2.95 ms one day and 1.76 ms another, same buildThe core type each run happened to land onPin both engines to the performance cores
txn.large read 0.09x, far slower than SQLiteSQLite timed 2,000 updates that changed nothing. It also hid a real defect: an update that changed nothing took the most expensive pathReset the column before the clock; skip a write that changes nothing, 4,067 ns to 867 ns
read.join had an interval of 2.8x to 6.5xThe bootstrap mixed a 21x workload with a 0.86x one, so it measured the distance between themGrade a family on one value per round
14 command line workloads could never reach three times SQLiteAn empty program takes 13 to 17 ms to start from the benchmark, 11 ms of it Node’s hidden consoleTime an empty program every round and report the time above it
One threshold made one workload 2.3 times faster and another 2.4 times slowerA 4 MiB log fold moved from one timed statement to the nextProfile each statement on its own
A test guard read anywhere from 1.18 to 59.22 for the same codeA stopwatch on a loaded machineCount log writes, page fetches and compiles instead

05Through the API applications call

The engine headline calls the engine directly. The path every driver uses was much slower: SELECT 1 once cost 132,884 ns outside a transaction against 1,126 ns inside one, because every statement took and released the file lock. From 3 October the climbs measured through Connection::prepare and Statement::step, on 48 workloads that applications meet.

Processor time for one round of the 48 workloads went from 3,906 ms against SQLite’s 1,820 ms on release 2.1.1, 115% more than SQLite, to 641 ms against 1,945 ms on release 2.4.2, 67% less. Peak memory went from 100.96 MiB to 59.78 MiB.

SQLite’s time over Inillucent’s, geometric mean
  • Training, 28 workloads
  • Held out, 20 workloads
SQLite
  1. An OR of two indexed columns reads each index: 588 ms to 0.88 ms. A statement that reads no table takes no file lock. Binary search for long IN lists.
  2. A correlated IN or EXISTS probes an index. A hash GROUP BY keeps its keys in one table. A reader keeps a packed copy of a written leaf.
  3. length() counts eight bytes at a time. On Windows the file length comes from one system call that asks for nothing else.
  4. A read keeps its lock for the next statement, the log length is known without asking Windows, and aggregates read a batch at a time.
The hill climb plan through the Connection API, against SQLite. The dashed line is the 20 held out workloads, which no change was designed from. Each point is that day’s run of 10 rounds.

06Called the way programs call it

On 5 October we asked whether the engine’s speed reached a person using it. It did not. Release 2.1.5 was 216% slower than SQLite over 25 workloads, and the npm package was 29,270% slower, because every call started a new process. Two climbs fixed it in two days.

The command line stays close to SQLite because starting a program costs both engines the same and is most of a short command. From a terminal an empty program starts in 5.7 ms, and a one row query takes 7.55 ms in Inillucent and 7.95 ms in SQLite.

SQLite’s time over Inillucent’s, geometric mean
SQLite
  1. Node kept one session process instead of starting one per call: a lookup 10.35 ms to 0.076 ms. The shell stopped rescanning its input: one INSERT of 20,000 rows 5,348.9 ms to 81.8 ms. A Python close stopped doing work it did not owe: 12.1 ms to 1.2 ms.
  2. Python results arrive as Python objects, and the C library loads into Node directly: a Node lookup to 0.046 ms. Followed Fable’s ranked advice in order.
  3. The Windows release is built with profile guided optimisation, trained on these workloads.
  4. The CRC of every page uses the processor’s instruction: 3.93 us to 0.64 us for a 32 KiB page.
  5. One Python execution is one foreign call, and a read keeps its lock for the next statement: a Python point query 17.25 us to 3.3 us.
All 25 workloads, geometric mean against SQLite. Rounds 5 and 6 were separate runs on a machine with different loads, which is why round 6 reads lower.

07Each way of calling it, before and after

Release 2.1.5 on 5 October against release 2.4.2 on 8 October, each against the same use of SQLite. Every one of the 25 workloads is now faster than SQLite, and 7 of them are three times as fast or more.

SQLite’s time over Inillucent’s, log scale
  • Release 2.1.5
  • Release 2.4.2
  • The npm package, 2 workloads29,270% slower to 418% faster
  • The Python driver, 9 workloads173% slower to 135% faster
  • SQL scripts piped into the shell, 6 workloads218% slower to 49% faster
  • The command line, one process per command, 8 workloads20% slower to 6% faster

Dashed line: SQLite

Geometric mean of each group against SQLite. The axis is a log scale, because the npm package moved from 0.0034x to 5.18x.

08The climb of 7 October, round by round

The goal was 50% faster than release 2.3.4 on common operations: select, join, group by, insert and search. The eval was 82 workloads through the Connection API plus 6 search cases, 56 training and 32 held out. A case’s score is its speedup against 2.3.4. The goal was met at round 10.

Round 6 was the largest single step. Half of an autocommit point read was one system call, asking the open log segment its length to learn whether another process had committed. On Windows the process now holds an opportunistic lock on the segment. While it holds the lock no other handle to the file exists on the machine, so the length is known from this process’s own writes. Another process’s open breaks the lock and waits until this process has stopped trusting its count. An autocommit point read went from 1.74 to 1.91 us to 0.46 to 0.55 us.

Speedup against release 2.3.4
  • Training, 56 cases
  • Held out, 32 cases
Release 2.3.4
  1. A connection keeps its loaded search index across its own writes: one insert and one search, 51 ms to 0.44 ms. It moved the held out set, where the search cases are.
  2. A read after a write keeps the read lease, so the next read does not take the lock and read the meta record again: 4.6 us to 1.4 us.
  3. The log oplock on Windows, Fable’s design: an autocommit point read 1.74 us to 0.46 us. The largest step of the climb.
  4. Aggregates and GROUP BY keys are computed a batch of rows at a time, Fable’s second piece of advice. The goal was met here.
Speedup against release 2.3.4, geometric mean over each set. Rounds 1 and 2 change no code the gate runs, so the gate alone read 1.003 and 0.999 there: that is the noise. The held out line rose with the training line in every round that moved the gate.

09The largest single changes

Each bar is one change, measured as the time before over the time after on the workload it was written for. Each of the largest removed work the engine did not need to do: a full scan where two index lookups would do, a list searched from the start for every row, an array of 3.2 MB grown and freed on every execution, a new process for every call.

Times faster, log scale
  • An OR of two indexed columns reads each index: 588 ms to 0.88 ms668 times
  • Binary search for an IN list of 8 or more constants: 958 ms to 4.44 ms216 times
  • Node keeps one session process: 10.35 ms to 0.076 ms136 times
  • A correlated IN stops growing a 3.2 MB array: 118.19 ms to 0.92 ms128 times
  • A search after an insert keeps the loaded index: 51 ms to 0.44 ms116 times
  • The shell checks for a statement end only on lines with a semicolon: 5,348.9 ms to 81.8 ms65 times
  • A statement that reads no table takes no file lock: 23.3 ms to 0.82 ms28 times
  • A commit is one log append and one sync: autocommit UPDATE 0.13x to 0.94x7.2 times
  • count(*) adds once per batch: 11.41x to 52.16x4.6 times
  • The log oplock: an autocommit point read 1.74 us to 0.46 us3.8 times
Times faster, from the before and after times of each change. The axis is a log scale.

10Asking Fable for advice

Fable is an AI model that is slower and more expensive to run than the one that did the building. We asked it for design advice where measurement could not answer the question, such as whether a change to the log is safe beside an older release. Every answer was a written document. Fable measured nothing and changed no code. The builders measured each claim and kept what held.

DateQuestionAdviceWhat happened
5 OctoberHow to be faster than SQLite on every usage workloadTen changes ranked with arithmetic for each. Keep the logical insert record in the log. Defer everything at open except the file handle, the format check and the meta recordFollowed in order in round 2: 17 of 25 workloads faster than SQLite, up from 5. One suggested change made no difference
6 OctoberThe last eight workloads slower than SQLiteTwelve ranked changes, two premises of the question corrected, and three decisions only the owner could makeThe owner approved them, and every one of the 25 workloads became faster than SQLite. Fable estimated a sync at 1.2 ms; a trace measured 46 us
7 OctoberHow to stop asking the log its length before every statementReject both ideas offered. Hold an opportunistic lock on the log segment instead, with the order of steps that makes it correctBuilt as round 6. Fable expected under 1.0 us; it measured 0.46 to 0.55 us. The tests found one case the advice had not listed, and it was fixed
7 OctoberHow to reach 1.5 from 1.415Four changes ranked by gain and risk, with the arithmetic that 82 workloads need the sum of the logs of their speedups to rise by 4.8Changes 1 and 2 became rounds 10 and 11. The goal was met at round 10, so change 4 was not needed

11What we tried and did not keep

Every climb ends with a table of changes that were measured and reverted, so the next climb does not try them again. A few of them:

ChangeMeasuredWhy it was not kept
An index nested loop that reads its inner side into a hash tableapp.dashboard 0.83x to 0.45xA probe costs about 184 ns, and reading a row into the table costs the same
A finishing step for the hash of a GROUP BY keyai.group.total 0.91The old hash kept keys inserted in order close together, and the cache liked that
x % k by a reciprocal multiplyNo changeThe cost per row is the calls through the expression tree, not the division
8 KiB pagesThe same time as 32 KiB
A larger free list for the library’s allocator10,000 Python inserts 13.7 ms to 16.1 to 17.5 msA block taken from a long free list is a cold cache line
A memory mapped read of the data file532 us against 473 us for 64 pages
Making a reader block writers during its leaseNot builtA writer would wait for a busy reader, and readers do not hold writers up

12Where it stands on 8 October 2026

Release 2.4.2 on the engine plan, the median of three runs of 30 rounds. These runs were not graded: SQLite ran 4.0% to 4.4% slower than its idle reference, which makes the figures read somewhat high. The pale bars are the last graded run, release 1.0.32 on 26 September.

Processor time is 73% less than SQLite’s and peak memory 14% more. Memory is the one measure where SQLite is better, and most of the difference is what a Rust process holds before the engine starts. One workload of the engine plan is slower than SQLite: compiling SELECT 1 on every call, 75% slower. Through the Connection API, which keeps the compiled plan, it is 76% faster.

One regression is open. A DELETE of 40,000 rows by a range of rowids was 4% faster than SQLite after the third climb and is 233% slower in 2.4.2. It slowed down in a release between 2.1.5 and 2.3.4, and the climb of 7 October, which measured against 2.3.4, could not see it. It is filed as a known bug.

SQLite’s time over Inillucent’s, log scale
  • Release 1.0.32, 26 September, graded
  • Release 2.4.2, 8 October
  • read.point, one row by key3,319% faster
  • large.values, text and blobs1,134% faster
  • read.analytical, scans and aggregates1,025% faster
  • read.range, selective ranges668% faster
  • read.join, joins422% faster
  • write, insert, update, delete, upsert344% faster
  • transaction, autocommit and batches141% faster
  • extension, JSON, FTS5, R-Tree127% faster
  • open.prepare, parse, bind, step98% faster
  • schema, CREATE INDEX5% faster

Dashed line: SQLite

Each family of the engine plan against SQLite. open.prepare and schema miss their bars of 5.00x and 3.00x. Every family is faster than SQLite. The axis is a log scale.

13After 8 October: 110 of 116 workloads

An eighth climb set a harder goal than a weighted mean: every one of the 116 workloads the benchmark times, in all three measurements, at least as fast as SQLite. It started from 74 of 116, because release 2.5.2 had added three questions to the file system per statement that cost about 20 us each on Windows. Release 2.5.4 ended it at 110 of 116, and 499% faster on the engine plan in a graded run of 11 October 2026, with SQLite within 2% of its idle reference.

The changes: the file system questions are skipped while the log’s oplock shows no other process has opened it; a range delete unlinks the leaves it empties; a join with a large outer side walks the inner index once; an update keeps one copy of each leaf as its undo record; an INSERT ... SELECT into an empty table collects its rows in a few large buffers; and a WHERE of integer comparisons is tested a batch of rows at a time. One change was to the benchmark: its Connection arm compiled a statement inside the clock where SQLite’s arm compiles before it, and now compiles first.

Six workloads are not faster. A range DELETE pays for folding the log into the database file, which lands on it in the benchmark’s fixed order; on its own it is level with SQLite. SELECT 1 compiled from text on every call through the engine’s own API costs more than SQLite’s compile; through the Connection API it is 66% faster. A join over an index range and an autocommit update are within 6% of SQLite, where the same index probes and one disk sync per commit decide the time.

MeasurementRelease 2.5.2Release 2.5.4
The engine plan through the engine API31 of 3431 of 34
The engine plan through the Connection API17 of 3432 of 34
48 common and edge case workloads through the Connection API26 of 4847 of 48

14What we learned

  • Fix the eval before the first measurement. A weighting or a threshold chosen after the results are in chooses the answer.
  • Check the answer before trusting the time.
  • Measure the noise before judging a change, and use medians when one sample can be 20 times the others.
  • Run both engines on the same cores on a quiet machine, and say in the output when it was not quiet.
  • When a timing will not hold still, count something that does: compactions, log writes, page fetches, compiles.
  • Profile before changing, and profile each statement on its own.
  • Measure the way people use the product as well as the engine. The engine was 419% faster while a person calling it from Node waited 29,270% longer than with SQLite.
  • Report a floor no program can go under as a floor. Starting an empty program takes longer than three times SQLite’s speed would allow on 14 command line workloads.
  • Ask a stronger model for design advice, and measure what it says. Its estimates were wrong twice, and its designs held every time they were built.
  • Write down every rejected change with its numbers, so nobody tries it again.

15Limits of this report

  • Every figure is from one Windows 11 machine with an Intel Core Ultra 9 285. A Linux run on 23 September measured 53% faster where Windows measured 279% on the same build, and has not been repeated since.
  • The newest engine runs were not graded, because the machine was not quiet enough.
  • The 5 September figure is computed from that day’s ten family ratios and weights. No run printed it.
  • The hill climb workloads were written by the people improving the engine. The held out set guards against tuning to them, and the engine plan’s weights were fixed before any measurement.