Skip to content

Database Internals

This page describes how Stalwart organises its data inside the data store. It is meant for contributors, and for administrators who inspect a database directly and need to know what they are looking at.

Stalwart divides its data into subspaces. A subspace is a logical partition that holds one category of data, such as the directory, the outbound queue or the change log. Each subspace is identified by a single ASCII letter. The same layout is used with every data store backend, so the letters mean the same thing whichever database holds them.

Each backend represents a subspace with its own native structure. With an SQL database (PostgreSQL, MySQL or SQLite), each subspace is a separate table, named after its letter. PostgreSQL and MySQL have no z and m tables, because they provide their own full-text search. On those two databases the search index lives instead in the s_email, s_cal, s_card, s_file and s_trace tables, one for each kind of indexed data.

With RocksDB, each subspace is a separate column family, again named after its letter. FoundationDB has a single ordered key space, so there the subspace letter is the first byte of every key.

The following table lists every subspace and the data it holds:

SubspaceNameContents
aACLAccess control lists that grant other accounts access to shared resources.
bRegistry indexIndexes over configuration and directory objects, used to look objects up by field value.
cImmutableValues written once and never modified, such as the parsed metadata and sort keys of each email message.
dDirectoryAccounts, domains, roles, mailing lists, OAuth clients and grants, public keys, tenants and masked email addresses.
eQueue messagesMessages in the outbound mail queue.
fTask queueBackground tasks, including tasks waiting for a retry and failed tasks kept until an administrator acts on them.
gRegistry primary keysUnique keys and references between configuration and directory objects.
hOutgoing reportsDMARC and TLS reports waiting to be sent.
iIndexesIndexes over account data, such as message and mailbox properties used for sorting and filtering.
jDeleted itemsDeleted items kept for recovery during the retention period (Enterprise edition).
kBlob linksLinks between blobs and the documents that reference them.
lLogsThe change log used for incremental cache updates and client synchronisation (IMAP CONDSTORE, JMAP changes, WebDAV sync), and share notifications.
nCountersEvery counter: per-account document identifiers, change identifiers, mailbox UIDs, object counts and quota usage, as well as tenant quota usage, identifier sequences for configuration and directory objects, outbound queue quota usage, and the rows of aggregate reports being collected.
oTelemetry spansStored traces.
pPropertiesProperties of account data objects, and object metadata.
qQueue eventsDelivery schedule entries for messages in the outbound queue, and the index of queued messages by tenant (Enterprise edition).
rIncoming reportsDMARC, TLS and ARF reports received from other servers.
sRegistryConfiguration objects.
tBlobsBlob data, when the data store also serves as the blob store.
uSystemServer-wide values such as the schema version and node identifier assignments.
vIndex propertiesIndexed values of individual properties, used to look up objects by value.
wSpam samplesMessages kept for training the spam classifier.
xTelemetry metricsStored metrics history.
ySearch queueDocuments waiting to be added to the search index, and the status of each index partition.
zSearch termsTerms of the internal full-text search index.
mSearch documentsDocuments of the internal full-text search index.

The in-memory store is not part of this layout, because it does not live in the data store. The Default in-memory store keeps its data in the server process, and the Redis variants keep it in Redis.