Rindle is designed to provide both fast hydration (comparable to OLTP databases) and fast updates (like differential dataflow). It achieves this with a new IVM approach.
Chinook ×30 (105k tracks), in-process, same SQLite file and indexes. Rindle in sink mode over SQLite vs. SQLite re-running the query. MacBook M5 Max, measured 2026-09-16.
Time to first complete result from a query.
Rindle is already close to SQLite for most queries with limits, which are common in OLTP use cases. For queries without limits we are currently slower, and there are a few known open issues where we are much slower than SQLite.
We expect to reach parity with SQLite for most query forms.
| Query | Rindle | SQLite | Rindle vs. SQLite |
|---|---|---|---|
| top 50 tracks by length ( ORDER BY … LIMIT 50) |
23.9 µs | 22.4 µs | 0.94× |
| one genre’s 100 longest tracks ( WHERE … ORDER BY … LIMIT 100) |
48.7 µs | 49.8 µs | 1.0× |
| 10 albums, each with its 10 newest tracks (nested, LIMIT 10 both levels) |
49.8 µs | 33.3 µs | 0.67× |
| 100 newest albums with a track in a genre ( EXISTS … LIMIT 100) |
1.3 ms | 44.4 µs | 0.03×* |
| all albums with all their tracks (10,410 albums, 105,090 tracks, nested) |
53.8 ms | 21.9 ms | 0.41× |
* A known issue with EXISTS hydration that we’re working on.
CPU spent keeping a query live.
An app that polls SQLite re-runs its query on a timer, whether or not anything changed. Rindle does nothing until a write happens, and then does work proportional to the size of the edit, not the size of the result. Set how often you would poll and how long the task you’re waiting on takes:
| Query | CPU/s SQLite | CPU/s Rindle | Rindle advantage | Latency SQLite | Latency Rindle |
|---|---|---|---|---|---|
| top 50 tracks by length | 5.26 µs | 0.06 µs | 88× | 2.5 s | 3.6 µs |
| one genre’s 100 longest tracks | 10.7 µs | 0.063 µs | 170× | 2.5 s | 3.8 µs |
| 10 albums, each with its 10 newest tracks | 7.42 µs | 0.062 µs | 120× | 2.5 s | 3.7 µs |
| 100 newest albums with a track in a genre | 9.54 µs | 0.06 µs | 160× | 2.5 s | 3.6 µs |
| all albums with all their tracks | 4.32 ms | 0.087 µs | 50,000× | 2.52 s | 5.2 µs |
Polling CPU is the re-run time divided by the poll interval. Rindle CPU is the update time divided by the task’s duration. Latency is the average time from a write to the new result: half the poll interval plus a re-run for polling, one update for Rindle. Polling faster cuts latency but multiplies its CPU.
Time to update many queries affected by a single write.
Rindle queries are deduplicated, but Rindle also shares work among closely related query families. As a result, the advantage increases with the number of outstanding live queries.
| Live queries | Rindle | SQLite (re-run all N) | Rindle vs. SQLite |
|---|---|---|---|
| 1 | 3.6 µs | 53.4 µs | 15× |
| 8 | 3.8 µs | 435 µs | 115× |
| 64 | 6.3 µs | 3.7 ms | 588× |
| 512 | 24.0 µs | 29.7 ms | 1,240× |
Rindle against the Zero code it ports.
We expect the delta to grow as Rindle continues to be optimized.
| Operation | Rindle (Rust) | Zero (TypeScript) | Speedup |
|---|---|---|---|
| In-memory storage | |||
| scan 1,000 rows | 3.6 µs | 110 µs | 31× |
| filtered fetch (~100 rows) | 0.8 µs | 12.8 µs | 16× |
| start from middle (~500 rows) | 2.4 µs | 58 µs | 24× |
| insert + delete a row | 0.11 µs | 3.1 µs | 28× |
| edit a row | 0.28 µs | 3.9 µs | 14× |
| SQLite storage | |||
| scan 1,000 rows | 99 µs | 673 µs | 6.8× |
| filtered fetch (~100 rows) | 17 µs | 80 µs | 4.7× |
| start from middle (~500 rows) | 72 µs | 369 µs | 5.2× |
| insert + delete a row | 2.9 µs | 7.6 µs | 2.6× |
| edit a row | 2.7 µs | 8.1 µs | 3.0× |