Skip to content

Commit c66aecf

Browse files
committed
more design info
1 parent 61d1e36 commit c66aecf

1 file changed

Lines changed: 43 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,46 @@ def merge(directory, timeout \\ 15_000)
4545
def close(directory)
4646
```
4747

48+
## Design
49+
50+
This is Elixir, so databases in B4 are structured as a supervision tree of processes that manage database operations:
51+
52+
![B4 process supervision tree](https://i.imgur.com/xWlFn7R.png)
53+
54+
A database is a directory, and that directory contains the data files for the database. A directory can contain exactly one database.
55+
56+
The database files on disk exist as two sets: the current write file, and the read files. The write file is the file the database is currently writing to. It is only ever appended to, never never overwritten. It is rotated out and becomes a read file once it reaches a configured size. So, the read files are the previous write files that have been rotated out, exactly like log rotation.
57+
58+
B4 can start an arbitrary number of databases. Databases do not know about and cannot interfere with each other.
59+
60+
Each database requires only 3 processes and 1 ETS table.
61+
62+
`DatabasesSupervisor` starts a `DatabaseSupervisor` per database.
63+
64+
Each `DatabaseSupervisor` supervises a `Writer` and `KeydirOwner` process.
65+
66+
The `Writer` process ensures all write access to the database is serialized. Writing (inserting and deleting) is always single threaded.
67+
68+
`KeydirOwner` starts and owns the ETS table that maps in-memory keys to on-disk values.
69+
70+
Reading is uncoordinated and can be done with arbitrary concurrency (e.g. with `Task.async` or similar).
71+
72+
This is because in Bitcask, all data files on disk except the current writer file are immutable. Once a writer file is rotated out and becomes a read-only file, it is never changed (except through the "merge" process that eventually removes dead tuples). Because of this, arbitrary readers can read from the on-disk database files without fear that they will read partially written data.
73+
74+
### Writing
75+
76+
Writing to a database involves appending append a record to a file disk and then updating the in-memory "keydir" structure (ETS table) that associates a key with the location of that data in that file on disk. It look slike `K -> (file_id, file_offset, data_length)`. The "keydir" is updated after the write to disk has completed. This means that the write is considered committed only when the keydir has been updated. Because ETS guarantees isolation, no intermediate updates to the keydir can be seen, so if a given key exists in the keydir, that data exists on disk.
77+
78+
### Reading
79+
80+
Reading is the inverse. Readers look up a key in the keydir, which provides them with the file, offset, and length of data. They then perform an `:erlang.pread` (offset read) at that location, loading the data into memory, deserialize it, and return it to the caller. This makes reads in B4 effectively constant time, subject to the time complexity of the ETS keydir read itself.
81+
82+
### Merging
83+
84+
In merging, the `KeydirOwner` process reads through all read files in the database and rewrites their contents into a new set of read files, preserving only live data. All old inserts and deletes are discarded. This process then updates the keydir with the new locations of any data on disk, which causes any readers to obseve the data at its new locations.
85+
86+
Because the read files on disk are immutable and the keydir is only updated once data has been moved, this process is transparent to both readers and writers, who may continue to access the database throughout the merge process.
87+
88+
There exists a small window of time during the swapping from the previous read file set to the new read file set *before* the keydir points to the newest data where it is possible that a reader could attempt a read of a now-deleted file. This is handled with retries, since the keydir is eventually updated with the new data location. This is not specificied in the Bitcask paper, but is rather a choice I made in lieu of file locking that would halt reads outright. Note that durability is not affected since the old data is only removed after the new data has been written, but readers may not yet be able to retrieve the location of the newest data.
89+
90+
Writing is not affected by the merge process - other than pausing write file rotation until the merge is complete - since the merge process does not touch the current active write file.

0 commit comments

Comments
 (0)