Skip to content

Commit 0acc092

Browse files
committed
DOCS(database): Add some crude high-level docs for DB
1 parent 4ac51e8 commit 0acc092

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

docs/dev/ServerDatabase.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Server database
2+
3+
The Mumble server uses a database to persist data across restarts. This document is about the (high-level) implementation details.
4+
5+
The database implementation consists of two separate parts. First, the general DB wrappers that allow managing the database convenient from within C++
6+
(mostly) without having to worry about the exact backend that is used. Second, the server-specific implementation which encodes the exact identity of
7+
tables and ways to access them. The former implementation lives under `src/database` and is completely decoupled from the server and even mostly
8+
decoupled from Mumble as a whole. The latter lives under `src/murmur/database`.
9+
10+
Overall, we are using [SOCI](https://github.com/SOCI/soci/) for abstracting and unifying the low-level communication with different database backends.
11+
12+
13+
## Transactions
14+
15+
A core design philosophy of our database implementation is that **every** database operation (READ _and_ WRITE) is encapsulated by a transaction
16+
ensuring that all operations happen atomically (from a logical point of view).
17+
18+
Oftentimes, a given function doesn't know whether the parent function has already created a transaction. To avoid creating deeply nested transactions,
19+
the `Database` and `Table` base classes have a member function `ensureTransaction` that will create a transaction, if none is already in progress. The
20+
returned `TransactionHolder` object can be treated the same in both cases as it has the necessary logic embedded to deal with cases in which no new
21+
transaction was created.
22+
23+
24+
## Exceptions
25+
26+
Errors during any database operation are communicated by means of exceptions. In order to ensure that the exceptions will have the expected type, make
27+
sure to always wrap SOCI operations in a `try`-`catch` block that rethrows exceptions wrapped in one of Mumble's own database exception classes. Use
28+
`std::throw_with_nested` function of the C++ standard.
29+
30+
31+
## Database migrations
32+
33+
If a server is started on a database that has an older scheme version than the server wants to use, an automatic migration procedure is started that
34+
will update the database to conform to the new scheme. This encompasses a complete re-creation of all tables followed by data migration from the old
35+
tables into the new ones. Once everything has successfully completed, the old tables will be dropped.
36+
37+
38+
## Changing the database scheme
39+
40+
The database scheme is a monotonically increasing number that acts as a version number for the database layout. The layout encompasses the existence
41+
of specific tables, columns (along with their data types) within tables and constraints, keys, etc. within tables.
42+
43+
Any changes to the database layout needs to be accompanied by the following steps:
44+
45+
1. Increase the scheme version by one. The scheme version is defined by the constant `ServerDatabase::DB_SCHEME_VERSION` (see
46+
[here](https://github.com/mumble-voip/mumble/blob/4ac51e86ff7b2243774d86a4d9fdac548127389a/src/murmur/database/ServerDatabase.h#L40).
47+
2. Adapt the `migrate` function of affected tables to data from the old table (which has the same name but with an added suffix of
48+
`Database::OLD_TABLE_SUFFIX`) into the new table. If all data is simply copied from a column in the old table into a column of the same name in the
49+
new table, without any required type transformations, it is sufficient to call the base implementation `Table::migrate` which will take care of
50+
that. When implementing these function explicitly, it is important that while columns names in the new table should use the constants defined in
51+
the `column` namespace, the names of the columns in the old table should always be written out **explicitly**. This is to ensure that even if the
52+
names change in newer versions of the table (i.e. the ones in the `column` namespace change), the migration code still works as expected. For an
53+
example see [here](https://github.com/mumble-voip/mumble/blob/4ac51e86ff7b2243774d86a4d9fdac548127389a/src/murmur/database/BanTable.cpp#L372).
54+
3. Add test cases for the migration path as well as the new DB functionality you added.
55+
56+
57+
### Migration test cases
58+
59+
Test cases for server migration are steered by the data found in `src/tests/TestDatabase/server/table_data`. This directory contains JSON files
60+
describing the tables and their contents at different scheme versions along with the expected outcome after a successful migration has been performed
61+
on that data.
62+
63+
By looking through the existing data, you should be able to get a feeling for the format. It should be noted that it is possible to inherit data from
64+
previous scheme versions and then only perform modifications based off that. Again, search the existing examples for how to achieve this. Worst case,
65+
you can also inspect `src/tests/TestDatabase/server/JSONAssembler.cpp` which is responsible for assembling the final JSON used to initialize the test
66+
tables.

0 commit comments

Comments
 (0)