ferrite

An embedded SQL database in Rust that keeps its tables in memory. Here it is beside SQLite, on the same disk, in the same mode.

Rust Unlicense

Speed

Operations a second. A table of 100,000 rows, read by key, updated by key, and loaded in one batch. Median of five runs, ext4 on NVMe, SQLite 3.46 with WAL and prepared statements. Green is the faster one, and the grey figure is how much more it does than SQLite.

workloadSQLite FULLferrite FULLSQLite NORMALferrite NORMAL
bulk insert1 174 3361 755 785 +50%1 258 9401 906 607 +51%
all reads422 1251 568 619 +272%418 9301 867 973 +346%
95 reads, 5 updates46 83281 031 +73%284 5341 357 665 +377%
50 reads, 50 updates10 00110 823 +8%112 041709 148 +533%

FULL forces every commit to the disk before it returns, and both engines then wait about 400 µs for one fsync. That is why the two FULL columns sit close together on writes: the disk sets the pace, not the engine.

Through an index

Microseconds for one lookup on a column that is not the key, with about a thousand rows matching. Lower is better, and the grey figure is how much less time ferrite takes.

asking forSQLiteferrite
the key only12353 57% less
the indexed column12154 55% less
a text column, so the row is fetched582277 52% less
walking the whole table, no index3 6612 784 24% less

What each one is

SQLiteferrite
Where the rows liveOn disk in pages, with a cache in memoryIn memory. A log and a snapshot on disk
How much it holdsUp to terabytesWhat fits in RAM
SQLThe whole language. Views, triggers, subqueries, window functions.The core. Tables with defaults, unique and foreign keys, indexes over one column or several, one inner join, WHERE with AND, OR, NOT, IS NULL, BETWEEN and arithmetic, ORDER BY, LIMIT, the four aggregates, INSERT OR IGNORE and OR REPLACE, transactions.
TypesFive, bent to fit the column. A string goes into an INTEGER column and stays a string.Five, kept as declared. A string in an INTEGER column is refused.
DurabilityFULL or NORMAL; FULL by defaultFULL or NORMAL; NORMAL by default, and the README says so first
Crash safetyDecades of itKilled mid-write 10,000 times in each mode. No committed row lost, no file that would not open
Who can use it at onceMany readers and one writer, across processesOne thread in one process
Answers checked againstIts own test suiteSQLite: tens of thousands of generated statements, in memory and on disk, every answer compared
DependenciesNoneNone
SizeAround a quarter of a million lines of CAbout 7,900 lines of Rust, bench and tests included
AgeTwenty-five yearsWeeks
LicensePublic domainPublic domain

How it gets there

Nothing to decode

A read never touches a disk page. The row is in memory, filed under its key in a tree. A prepared lookup costs the same as calling the engine directly.

One record a commit

A commit is one record appended to a log, with its length and a checksum. A crash leaves a torn record at the end, and that is cut off rather than believed.

Snapshots when they pay

The database is written out fresh only when the log holds twice as many records as there are rows. A hundred thousand rows loaded once are not rewritten for nothing.

Cold when idle

No background thread, no timer. An open database that nobody is using makes no syscalls at all. Every speed change came with a number, and nothing merged slower than what it replaced.

What it is not

A server, a database shared between processes, or a home for more data than the machine has memory. If a table would not fit in RAM, use SQLite. If two programs need the same file, use SQLite.