> subsystem-summary-of-database
read this skill for a token-efficient summary of the database subsystem
curl "https://skillshub.wtf/stellar/stellar-core/subsystem-summary-of-database?format=md"Database Subsystem — Technical Summary
Overview
The database subsystem provides the persistence layer for stellar-core, wrapping the SOCI C++ database-access library to manage connections to SQLite or PostgreSQL backends. It handles schema versioning/migration, connection pooling for worker threads, metrics collection, and a dual-database architecture (main + misc) for SQLite to avoid write-lock contention.
Key Files
- Database.h / Database.cpp — Core
Databaseclass; connection management, schema migration, pooling, metrics. - DatabaseTypeSpecificOperation.h — Visitor pattern for backend-specific (SQLite vs PostgreSQL) code paths.
- DatabaseConnectionString.h / .cpp — Utility to redact passwords from connection strings for logging.
- DatabaseUtils.h / .cpp — Helper for batch-deleting old ledger entries from tables.
Key Classes and Data Structures
Database (inherits NonMovableOrCopyable)
The central class that owns all database connections for an Application instance. One Database exists per application.
Members:
mApp(Application&) — Back-reference to the owning application.mQueryMeter(medida::Meter&) — Metrics meter counting all SQL query executions.mSession(SessionWrapper, name="main") — Primary SOCI session for ledger state; used for all writes on the main DB.mMiscSession(SessionWrapper, name="misc") — Secondary SOCI session for miscellaneous/consensus data (SQLite only).mPool/mMiscPool(unique_ptr<soci::connection_pool>) — Lazily-created connection pools for worker-thread read access.gDriversRegistered(staticbool) — Ensures SOCI backend drivers (sqlite3, postgresql) are registered exactly once.
SessionWrapper (inherits NonCopyable)
A thin RAII wrapper around soci::session that carries a human-readable session name (e.g., "main" or "misc"). Two constructors: one for standalone sessions, one that borrows from a connection_pool.
StatementContext (inherits NonCopyable)
RAII handle for borrowing a SOCI prepared statement. On construction it calls clean_up(false) to unbind any prior data; on destruction it does the same cleanup. Returned by Database::getPreparedStatement(). Supports move semantics.
DatabaseTypeSpecificOperation<T> (template, abstract)
A visitor/strategy pattern that allows callers to write code specific to the database backend without switching on backend type everywhere. Has two pure virtual methods:
doSqliteSpecificOperation(soci::sqlite3_session_backend* sq)— SQLite path.doPostgresSpecificOperation(soci::postgresql_session_backend* pg)— PostgreSQL path (conditionally compiled underUSE_POSTGRES).
Used via Database::doDatabaseTypeSpecificOperation() or the free-function overload that takes a raw soci::session&.
DatabaseConfigureSessionOp (local to Database.cpp)
A concrete DatabaseTypeSpecificOperation<void> used internally during connection setup. Performs:
- SQLite: Checks minimum version (3.45+), sets WAL journal mode, autocheckpoint=10000, busy_timeout=10000ms, cache_size=20000 pages, mmap_size=100MB, and registers the
carray()extension. - PostgreSQL: Checks minimum version (9.5+), sets session transaction isolation to SERIALIZABLE.
Schema Versioning
Two independent schema version tracks are maintained:
Main DB Schema
- Constants:
MIN_SCHEMA_VERSION = 25,SCHEMA_VERSION = 26. - Version stored via
PersistentState::kDatabaseSchemain thestorestatetable. applySchemaUpgrade(vers)applies incremental migrations in a SOCI transaction:- v25→v26: Drops deprecated
publishqueuetable; drops misc tables from main DB if misc DB is active. - v24→v25: Removes deprecated
dbbackendentry fromstorestate.
- v25→v26: Drops deprecated
Misc DB Schema
- Constants:
MIN_MISC_SCHEMA_VERSION = 0,MISC_SCHEMA_VERSION = 1. - Version stored via
PersistentState::kMiscDatabaseSchemain the misc DB's own storestate. applyMiscSchemaUpgrade(vers)applies incremental migrations:- v0→v1: Creates overlay/peer tables, persistent state, herder persistence, ban manager tables in the misc DB, then copies data from main via
populateMiscDatabase().
- v0→v1: Creates overlay/peer tables, persistent state, herder persistence, ban manager tables in the misc DB, then copies data from main via
Migration Flow (upgradeToCurrentSchema())
- Migrate misc DB first (if applicable): determines current misc version, runs
doMigration(). - Migrate main DB: determines current main version, runs
doMigration(). doMigration()validates version bounds, then loops applying upgrades one version at a time, persisting the new version after each step.
Dual-Database Architecture (Main + Misc)
SQLite locks the entire database file during writes, blocking parallelism between ledger apply and consensus/overlay operations. To mitigate this, the subsystem splits data across two SQLite files:
- Main DB: Ledger state (ledger headers, transaction history, ledger entries). Touched at startup and during apply.
- Misc DB: Consensus data (SCP quorums, SCP history, slot state), overlay data (peers, bans), upgrades. Tables migrated:
peers,ban,quoruminfo,scpquorums,scphistory,slotstate(defined inkMiscTables).
Applicability: Only for on-disk SQLite (canUseMiscDB() returns true when canUsePool() && isSqlite()). PostgreSQL handles concurrent writes natively, so it uses a single database with mMiscSession falling back to mSession.
Misc DB naming: getMiscDBName() inserts "-misc" before the file extension of the main DB path (e.g., stellar.db → stellar-misc.db).
Data migration (populateMiscDatabase()):
- Attaches main DB as
source_dbin the misc session. - For each table in
kMiscTables, copies all rows viaINSERT INTO ... SELECT * FROM source_db.<table>. - Verifies row counts match.
- Detaches
source_dbafter transaction commit to avoid lock contention.
Connection Pooling
Pools are lazily created on first call to getPool() or getMiscPool().
Pool size: std::thread::hardware_concurrency() entries. If misc DB is active, each pool gets half (min 1).
Pool creation (createPool()):
- Allocates
soci::connection_poolof sizen. - Opens each session in the pool to the target DB.
- Configures each session via
DatabaseConfigureSessionOp.
Access constraints:
getSession()andgetMiscSession()assertthreadIsMain()— direct session access is restricted to the main thread.- Worker threads must use the pool (
getPool()/getMiscPool()).
Key Functions
Database Public API
| Function | Purpose |
|---|---|
Database(Application&) | Constructor: registers drivers, logs connection string (password-redacted), calls open(). |
open() | Opens main session, configures it; opens misc session if canUseMiscDB(). |
initialize() | Drops and recreates all tables (used by new-db command). For SQLite, deletes DB files first. Creates overlay, persistent state, ledger header, herder persistence, ban tables. |
upgradeToCurrentSchema() | Runs schema migrations for both misc and main DBs. |
getPreparedStatement(query, session) | Allocates and prepares a SOCI statement, returns it wrapped in StatementContext. |
getInsertTimer/getSelectTimer/getDeleteTimer/getUpdateTimer/getUpsertTimer(entityName) | Returns a medida::TimerContext for timing and counting SQL operations, grouped by entity name. |
setCurrentTransactionReadOnly() | On PostgreSQL, issues SET TRANSACTION READ ONLY. No-op on SQLite. |
isSqlite() | Returns true if connection string contains "sqlite3://". |
canUseMiscDB() | True for on-disk SQLite only. |
canUsePool() | True unless using in-memory SQLite (sqlite3://:memory:). |
getSimpleCollationClause() | Returns COLLATE "C" for PostgreSQL (byte-value comparison), empty for SQLite. |
getSession() / getMiscSession() | Returns main/misc SessionWrapper; asserts main thread. getMiscSession() falls back to main session if misc DB unavailable. |
getRawSession() / getRawMiscSession() | Convenience accessors returning soci::session&. |
getPool() / getMiscPool() | Returns (lazily-created) connection pools for worker threads. |
getMainDBSchemaVersion() / getMiscDBSchemaVersion() | Reads schema version from persistent state. |
doDatabaseTypeSpecificOperation(session, op) | Dispatches to the correct backend-specific method on op via dynamic_cast on the SOCI backend. |
Free Functions
| Function | Purpose |
|---|---|
doDatabaseTypeSpecificOperation<T>(soci::session&, op) | Non-member overload operating on raw soci::session. |
decodeOpaqueXDR<T>(string, out) | Base64-decodes a string then XDR-deserializes into out. |
decodeOpaqueXDR<T>(string, indicator, out) | Same but handles null indicators (sets out = T{} if null). |
removePasswordFromConnectionString(string) | Regex-replaces password values with ******** in connection strings for safe logging. |
dropMiscTablesFromMain(Application&) | Drops all kMiscTables from the main DB (called during v26 migration). |
validateVersion(vers, min, max) | Throws if schema version is outside supported range. |
DatabaseUtils Namespace
| Function | Purpose |
|---|---|
deleteOldEntriesHelper(sess, ledgerSeq, count, tableName, ledgerSeqColumn) | Batch-deletes old rows: finds MIN of the ledger-seq column, deletes rows up to min(curMin + count, ledgerSeq). Used for pruning historical data from various tables. |
Ownership Relationships
Application
└── Database (1:1, owned by Application)
├── mSession (SessionWrapper, "main") — primary DB session
│ └── soci::session — actual SOCI connection
├── mMiscSession (SessionWrapper, "misc") — secondary DB session (SQLite only)
│ └── soci::session — actual SOCI connection
├── mPool (unique_ptr<soci::connection_pool>) — lazily created, for main DB
│ └── N × soci::session (one per hardware thread)
└── mMiscPool (unique_ptr<soci::connection_pool>) — lazily created, for misc DB
└── N/2 × soci::session
StatementContext objects are transient: created by getPreparedStatement(), hold a shared_ptr<soci::statement>, cleaned up when they go out of scope.
Key Data Flows
Startup / Initialization
ApplicationconstructsDatabase, which registers SOCI drivers and opens connections.open()configures each session (SQLite pragmas or PostgreSQL isolation level).- If
new-db:initialize()drops/recreates all tables, sets schema version toMIN_SCHEMA_VERSION. - Otherwise:
upgradeToCurrentSchema()reads current versions and applies incremental migrations.
SQL Statement Execution (typical pattern used by other subsystems)
- Caller obtains a timer via
getInsertTimer("entity")etc. — starts timing. - Caller obtains session via
getSession()orgetMiscSession(). - Caller either uses raw SOCI syntax (
session << "SQL...") or obtains a prepared statement viagetPreparedStatement(query, session). StatementContextRAII ensures cleanup.- Timer destructor records elapsed time in metrics.
Worker Thread Database Access
- Worker thread calls
getPool()orgetMiscPool()to get asoci::connection_pool. - Worker constructs a
SessionWrapperfrom the pool (SOCI automatically checks out/returns connections). - Worker performs read queries. Write operations are restricted to the main thread's session.
Schema Migration
upgradeToCurrentSchema()is called during startup.- Misc DB migration runs first: if main schema ≥ 26, misc schema version is read; migrations applied (v0→v1 creates tables and copies data from main).
- Main DB migration runs second: v25→v26 drops deprecated tables and removes misc tables from main if misc DB is active.
- Each step runs in a SOCI transaction, version is persisted after commit.
Data Pruning
- Subsystems call
DatabaseUtils::deleteOldEntriesHelper()with a target ledger sequence, batch count, table name, and column name. - Helper finds the minimum ledger sequence in the table, computes an upper bound, and batch-deletes old rows up to that bound.
Constants
| Constant | Value | Purpose |
|---|---|---|
MIN_SCHEMA_VERSION | 25 | Oldest main DB schema this binary can open |
SCHEMA_VERSION | 26 | Current target main DB schema |
FIRST_MAIN_VERSION_WITH_MISC | 26 | Main schema version at which misc DB was introduced |
MIN_MISC_SCHEMA_VERSION | 0 | Oldest misc DB schema (0 = no misc table yet) |
MISC_SCHEMA_VERSION | 1 | Current target misc DB schema |
MIN_SQLITE_VERSION | 3.45 | Minimum SQLite version (compiled check) |
MIN_POSTGRESQL_VERSION | 9.5 | Minimum PostgreSQL version |
Threading Model
- Main thread: Owns
mSessionandmMiscSession. All write operations go through these. Access is guarded byreleaseAssert(threadIsMain()). - Worker threads: Read-only access via connection pools (
mPool,mMiscPool). Each pool entry is an independently configured SOCI session. - SQLite concurrency: WAL mode allows concurrent readers with a single writer. The dual-DB split (main/misc) further reduces write contention by separating consensus writes from ledger-apply writes into different files with independent locks.
- PostgreSQL concurrency: Single DB with SERIALIZABLE isolation; concurrent writes are handled by the database engine natively.
> related_skills --same-repo
> validating-a-change
comprehensive validation of a change to ensure it is correct and ready for a pull request
> regenerating a technical summary of stellar-core
Instructions for regenerating the full set of subsystem and whole-system technical summary skill documents for stellar-core
> subsystem-summary-of-work
read this skill for a token-efficient summary of the work subsystem
> subsystem-summary-of-util
read this skill for a token-efficient summary of the util subsystem