toydb is a free, open source databases project written in Rust and released under Apache-2.0. It has 7,283 GitHub stars, 624 forks and 0 open issues, and was last pushed 2 months ago. On this registry it ranks #115 of 203 tracked projects in Databases, with 5 head-to-head comparisons available.

What is toydb?

toyDB is a distributed SQL database written in Rust as an educational project, aimed at developers and students who want to read and run a working system that shows how Raft consensus, MVCC transactions, and a SQL query engine fit together.

What it is

toyDB is a distributed SQL database implemented from scratch in Rust under the Apache-2.0 licence. Its main features are Raft distributed consensus for linearizable state machine replication, ACID transactions with MVCC-based snapshot isolation, a pluggable storage engine with BitCask and in-memory backends, an iterator-based query engine with heuristic optimization and time-travel support, and a SQL interface that includes joins, aggregates, and transactions. The stated intent is to be simple and understandable while remaining functional and correct.

The problem it addresses is the gap between reading about database internals and seeing them work. Production-grade distributed SQL databases carry complexity for performance, scalability, and availability, and the README states those are major sources of complexity that obscure the basic underlying concepts. toyDB takes shortcuts where possible and treats those three properties as explicit non-goals, so the architecture stays legible. It was first written in 2020 and later rewritten by an author who has since worked on CockroachDB and Neon, so the code illustrates concepts drawn from real distributed SQL systems rather than reproducing their scale.

Key capabilities

  • Raft distributed consensus for linearizable state machine replication, in src/raft/mod.rs.
  • ACID transactions with MVCC-based snapshot isolation, in src/storage/mvcc.rs.
  • Pluggable storage engine with BitCask and in-memory backends, selected through src/storage/engine.rs with implementations in src/storage/bitcask.rs and src/storage/memory.rs.
  • Iterator-based query engine with heuristic optimization and time-travel support, in src/sql/execution/executor.rs and src/sql/planner/optimizer.rs.
  • SQL interface covering joins, aggregates, and transactions, with the parser in src/sql/parser/parser.rs.
  • EXPLAIN query plans rendered as operator trees, showing nodes such as Remap, Order, and Projection.
  • Documentation set: architecture guide at docs/architecture/index.md, SQL examples at docs/examples.md, SQL reference at docs/sql.md, and research references at docs/references.md.

Who uses it and how

  • Engineers learning database internals run the bundled five-node local cluster and query it with the toysql client to watch leader election, replication, and transaction behaviour.
  • Developers studying the architecture behind systems such as CockroachDB and Neon use the architecture guide as a guided tour of the codebase, module by module.
  • Rust developers build the command-line client with cargo run --release --bin toysql and connect it to a node, for example node 1 on localhost:9601.
  • Instructors and self-directed learners use docs/examples.md and docs/sql.md to walk through the supported SQL dialect, including CREATE TABLE, INSERT, and SELECT.
  • Anyone testing distributed behaviour uses the cluster layout: five nodes on SQL ports 9601-9605 and Raft ports 9701-9705, with data written under cluster/*/data/.

Getting started

With a Rust compiler installed, ./cluster/run.sh builds and starts a local five-node cluster, and cargo run --release --bin toysql builds the client that connects to node 1 on localhost:9601. The README documents no package registry, Docker image, compose file, or hosted option.

How it compares

The facts name CockroachDB and Neon, both production distributed SQL databases the author worked on, and toyDB relates to them as an illustration rather than a competitor. Where those systems optimise for performance, scalability, and availability, toyDB treats all three as non-goals in order to keep the underlying concepts visible. It therefore belongs beside them as a readable study of distributed SQL architecture, not as an alternative deployment target.

When to use it — and when not to

Running it means supplying a Rust toolchain, building from source, and operating a multi-node cluster with its own data directories; the README mentions no packaged artifact or managed service to reduce that work. It should not be chosen as a production data store, because performance, scalability, and availability are explicit non-goals and the README states that shortcuts have been taken where possible. Pick it to learn or teach distributed SQL internals, not to serve traffic.

project readme (upstream, from github) — read inline

toyDB

Distributed SQL database in Rust, built from scratch as an educational project. Main features:

toyDB is intended to be simple and understandable, and also functional and correct. Other aspects like performance, scalability, and availability are non-goals -- these are major sources of complexity in production-grade databases, and obscure the basic underlying concepts. Shortcuts have been taken where possible.

I originally wrote toyDB in 2020 to learn more about database internals. Since then, I've spent several years building real distributed SQL databases at CockroachDB and Neon. Based on this experience, I've rewritten toyDB as a simple illustration of the architecture and concepts behind distributed SQL databases.

Documentation

Usage

With a Rust compiler installed, a local five-node cluster can be built and started as:

$ ./cluster/run.sh
Starting 5 nodes on ports 9601-9605 with data under cluster/*/data/.
To connect to node 1, run: cargo run --release --bin toysql

toydb4 21:03:55 [INFO] Listening on [::1]:9604 (SQL) and [::1]:9704 (Raft)
toydb1 21:03:55 [INFO] Listening on [::1]:9601 (SQL) and [::1]:9701 (Raft)
toydb2 21:03:55 [INFO] Listening on [::1]:9602 (SQL) and [::1]:9702 (Raft)
toydb3 21:03:55 [INFO] Listening on [::1]:9603 (SQL) and [::1]:9703 (Raft)
toydb5 21:03:55 [INFO] Listening on [::1]:9605 (SQL) and [::1]:9705 (Raft)
toydb2 21:03:56 [INFO] Starting new election for term 1
[...]
toydb2 21:03:56 [INFO] Won election for term 1, becoming leader

A command-line client can be built and used with node 1 on localhost:9601:

$ cargo run --release --bin toysql
Connected to toyDB node n1. Enter !help for instructions.
toydb> CREATE TABLE movies (id INTEGER PRIMARY KEY, title VARCHAR NOT NULL);
toydb> INSERT INTO movies VALUES (1, 'Sicario'), (2, 'Stalker'), (3, 'Her');
toydb> SELECT * FROM movies;
1, 'Sicario'
2, 'Stalker'
3, 'Her'

toyDB supports most common SQL features, including joins, aggregates, and transactions. Below is an EXPLAIN query plan of a more complex query (fetches all movies from studios that have released any movie with an IMDb rating of 8 or more):

toydb> EXPLAIN SELECT m.title, g.name AS genre, s.name AS studio, m.rating
  FROM movies m JOIN genres g ON m.genre_id = g.id,
    studios s JOIN movies good ON good.studio_id = s.id AND good.rating >= 8
  WHERE m.studio_id = s.id
  GROUP BY m.title, g.name, s.name, m.rating, m.released
  ORDER BY m.rating DESC, m.released ASC, m.title ASC;

Remap: m.title, genre, studio, m.rating (dropped: m.released)
└─ Order: m.rating desc, m.released asc, m.title asc
   └─ Projection: m.title, g.name as genre, s.name as studio, m.rating, m.released
      └─ Aggregate: m.title, g.name, s.name, m.rating, m.released
         └─ HashJoin: inner on m.studio_id = s.id
            ├─ HashJoin: inner on m.genre_id = g.id
            │  ├─ Scan: movies as m
            │  └─ Scan: genres as g
            └─ HashJoin: inner on s.id = good.studio_id
               ├─ Scan: studios as s
               └─ Scan: movies as good (good.rating > 8 OR good.rating = 8)

Architecture

toyDB's architecture is fairly typical for a distributed SQL database: a transactional key/value store managed by a Raft cluster with a SQL query engine on top. See the architecture guide for more details.

toyDB architecture

Tests

toyDB mainly uses Goldenscripts for tests. These script various scenarios, capture events and output, and later assert that the behavior remains the same. See e.g.:

Run tests with cargo test, or have a look at the latest CI run.

Benchmarks

toyDB is not optimized for performance, but comes with a workload benchmark tool that can run various workloads against a toyDB cluster. For example:

# Start a 5-node toyDB cluster.
$ ./cluster/run.sh
[...]

# Run a read-only benchmark via all 5 nodes.
$ cargo run --release --bin workload read
Preparing initial dataset... done (0.179s)
Spawning 16 workers... done (0.006s)
Running workload read (rows=1000 size=64 batch=1)...

Time   Progress     Txns      Rate       p50       p90       p99      pMax
1.0s      13.1%    13085   13020/s     1.3ms     1.5ms     1.9ms     8.4ms
2.0s      27.2%    27183   13524/s     1.3ms     1.5ms     1.8ms     8.4ms
3.0s      41.3%    41301   13702/s     1.2ms     1.5ms     1.8ms     8.4ms
4.0s      55.3%    55340   13769/s     1.2ms     1.5ms     1.8ms     8.4ms
5.0s      70.0%    70015   13936/s     1.2ms     1.5ms     1.8ms     8.4ms
6.0s      84.7%    84663   14047/s     1.2ms     1.4ms     1.8ms     8.4ms
7.0s      99.6%    99571   14166/s     1.2ms     1.4ms     1.7ms     8.4ms
7.1s     100.0%   100000   14163/s     1.2ms     1.4ms     1.7ms     8.4ms

Verifying dataset... done (0.002s)

The available workloads are:

  • read: single-row primary key lookups.
  • write: single-row inserts to sequential primary keys.
  • bank: bank transfers between various customers and accounts. To make things interesting, this includes joins, secondary indexes, sorting, and conflicts.

For more information about workloads and parameters, run cargo run --bin workload -- --help.

Example workload results are listed below. Write performance is atrocious, due to fsync and a lack of write batching in the Raft layer. Disabling fsync, or using the in-memory engine, significantly improves write performance (at the expense of durability).

Workload BitCask BitCask w/o fsync Memory
read 14163 txn/s 13941 txn/s 13949 txn/s
write 35 txn/s 4719 txn/s 7781 txn/s
bank 21 txn/s 1120 txn/s 1346 txn/s

Debugging

VSCode and the CodeLLDB extension can be used to debug toyDB, with the debug configuration under .vscode/launch.json.

Under the "Run and Debug" tab, select e.g. "Debug executable 'toydb'" or "Debug unit tests in library 'toydb'".

Credits

The toyDB logo is courtesy of @jonasmerlin.

Frequently asked questions

Is toydb free to use?

toydb is open source under the Apache-2.0 licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does toydb do?

Distributed SQL database in Rust, written as an educational project

What is toydb written in?

toydb is primarily written in Rust. Its source is publicly available at https://github.com/erikgrinaker/toydb, and it has 7,283 GitHub stars.