Aufbau eines OLTP-Kerns: API-Verträge, Seitengröße und adaptive Abstimmung
Entwickler einer neuen OLTP-Datenbank haben eine strikte Architektur mit isolierten Schichten mittels Trait-Interfaces in Rust umgesetzt. Jede Schicht verfügt über klare API-Verträge, die Seitengröße ist pro Tablespace konfigurierbar, und die Konfiguration wird von einem Resource Broker ohne manuelle Abstimmung verwaltet. Diagnosen erfolgen über USDT und eBPF.
Seitengröße und Tablespace-Einstellungen
Daten-Seitengrößen reichen von 4-8 KB für reines OLTP bis 16-32 KB für HTAP auf NVMe. Jeder Tablespace ist eine separate Datei mit fester Seitengröße nach der Erstellung. Standard ist 16 KB. Die PageId ist zusammengesetzt: [tablespace_id:16][page_index:48]. Der BufferPool leitet Anfragen an Sub-Caches nach Größe weiter.
Der Superblock am Dateianfang speichert Tablespace-Metadaten unabhängig von der Seitengröße. Beim Mount prüft er die Konfiguration; Abweichungen verhindern den Start ohne Abbau der Funktionalität.
Architekturschichten und API-Verträge
Der Kern ist in vier Schichten unterteilt:
- Adapter-Schicht (async tokio): Beendet TLS, parst Wire-Protokoll.
#[async_trait]
pub trait NetworkAdapter {
async fn handle_connection(&self, stream: Box<dyn ConnectionStream>) -> Result<(), NetworkError>;
}
- CompatLayer: Parst SQL zu AST, emuliert pg_catalog, übersetzt zu LogicalPlan, lehnt nicht unterstützte Features ab (Fehler 0A000).
pub trait CompatLayer {
fn translate_query(&self, ast: SqlAst) -> Result<LogicalPlan, CompatError>;
}
- Core Engine (sync): Optimizer, Executor, Transaktionsmanager. Läuft in einem spawn_blocking-Pool.
pub trait ExecutionEngine {
fn execute_plan(&self, plan: LogicalPlan, session: &Session) -> Result<ResultSet, ExecutionError>;
}
- StorageManager: Fassade über PageProvider und TransactionLogSink. Kern fordert Seiten und UNDO-Einträge an.
pub trait StorageManager {
fn pin_page(&self, page_id: PageId, mode: LockMode) -> Result<PageGuard, StorageError>;
fn append_undo(&self, txn_id: TxnId, record: UndoRecord) -> Result<UndoPtr, StorageError>;
}
Schichtgrenzen verhindern Detail-Lecks: Lock-Metriken schließen async-Wartezeiten aus.
Adaptive Abstimmung und Resource Broker
Rund 60 Parameter sind in Budgets, Schranken und Overrides unterteilt. Operatoren legen hochstufige Limits fest:
[resources]
memory_budget = "16GB"
cpu_budget = "auto"
io_iops = 5000
Advisors (MemoryAdvisor, IoAdvisor, CpuAdvisor) verteilen Ressourcen sekündlich neu:
- CpuAdvisor: DOP und Thread-Pool vs. Drosselung.
- MemoryAdvisor: BufferPool vs. work_mem unter Last.
- IoAdvisor: Transaktionspriorität, Burst-Erkennung in der Cloud.
Schutzmechanismen
- Hysteresis: 5s-Fenster, 5%-Schritte zur Vermeidung von Oszillationen.
- Harte Untergrenzen: Mind. 128 MB für BufferPool/UNDO.
- Sanfter Übergang: Neue Limits nur für neue Allokationen.
- Experten-Overrides überschreiben Autotuning.
Hintergrund-Purge des UNDO-Logs statt VACUUM. Laufzeitänderungen ohne Neustart.
Diagnosen: USDT-Probes und eBPF
Diagnosemechanismen sind fest in den Binärcode integriert. USDT-Probes für Laufzeitanalysen, eBPF für Tracing ohne Overhead.
Wichtige Erkenntnisse:
- Seitengröße pro Tablespace mit Startprüfungen.
- Strenge Trait-Verträge zwischen Schichten verhindern Lecks.
- Resource Broker: Budgets → Autotuning mit Schranken.
- Diagnosen via eBPF/USDT im Kern.
- Manuelle Abstimmung durch adaptive Mechanismen ersetzen.
— Editorial Team
Noch keine Kommentare.