Architektura bazy danych OLTP: od zasad do implementacji w Rust
Zaimplementowano kluczowe podsystemy silnika OLTP: unified storage w jednym pliku na bazę, disk-backed storage z UNDO-log MVCC, WAL i recovery według ARIES, wspólny BufferPool, silnik in-memory oraz protokół pgwire zgodny z PostgreSQL. Kod działa, ale wymaga testów obciążeniowych i stabilizacji przypadków brzegowych. To nie produkt finalny, lecz wczesny etap z ustalonymi kontraktami.
Główne zasady projektowania
Projekt opiera się na ścisłych regułach podejmowania decyzji. Każda nowa decyzja musi ich przestrzegać lub uzasadniać odstępstwo.
Restrictive by Default
Komponenty mają wyraźne limity z zachowaniem fail-closed zamiast throttlingu. Zapewnia to przewidywalność pod obciążeniem.
| Komponent | Granica | Działanie przy naruszeniu | SQLSTATE |
|-----------|---------|------------------------|----------|
| BufferPool | buffer_pool_size_mb | Eviction (CLOCK), flush z priorytetem WAL | — |
| TxnWriteSet | txn_max_write_set_mb | Odrzucenie DML | 54023 |
| UndoStore | undo_max_size_mb | Odrzucenie zapisów | 53100 |
| Connection pool | max_connections | Odrzucenie nowych połączeń | 53300 |
| Statement timeout | statement_timeout_ms | Anulowanie zapytania | 57014 |
| Snapshot age | max_snapshot_age | Wymuszenie zamknięcia przestarzałych snapshotów | 40001 |
Fail-closed zapewnia jasne SQLSTATE dla retry lub circuit breaker w aplikacji.
Podejście Contract-First
Kontrakty architektoniczne są ustalane w traitach Rust: TableEngine, PageProvider, TransactionLogSink, StorageIo. Zapobiega to dryfowi implementacji, sprawdzając niezmienniki na etapie kompilacji.
Wybór Rust
Rust wybrano ze względu na przewidywalne opóźnienia, kontrolę alokacji i bezpieczeństwo typów. Send/Sync na granicach async/sync, jawne błędy zamiast problemów runtime. C++ wymaga dodatkowej dyscypliny, Go nie nadaje się z powodu GC dla opóźnień ogonowych OLTP.
Podział odpowiedzialności
Baza danych działa jako data engine bez logiki biznesowej: brak triggerów, PL/pgSQL, procedur składowanych na starcie. User-defined functions możliwe później z sandboxingiem i bez efektów ubocznych.
Hybrydowa asynchroniczność
- Warstwa sieciowa/pgwire: async (accept, TLS, send).
- Jądro (query execution, storage, WAL, MVCC): sync dla poprawności i debugowania.
- Granica jednokierunkowa przez bridge.
StorageIo jest abstrahowany dla przyszłego async I/O bez przepisywania transakcji.
| Warstwa | Runtime | Przykłady |
|------|---------|---------|
| Network/Protocol | async | pgwire, TLS |
| Query Execution | sync | plan, execute |
| Storage/WAL | sync (async I/O później) | HeapStore, BufferPool |
| MVCC/Transactions | sync | snapshot, locks |
Zgodność z PostgreSQL
Pgwire zapewnia integrację z driverami i ORM bez migracji klienta. Zgodność na poziomie wire przez warstwę boundary, bez kopiowania wewnętrznych struktur PostgreSQL (MVCC heap, VACUUM). Jądro zachowuje swobodę architektoniczną.
Ograniczenia platformowe i niezawodność
Linux-only dla skupienia na io_uring, eBPF bez kompromisów wieloplatformowych. Priorytety: brak uszkodzenia danych, bezpieczny rollback optymalizacji, odporność na user input, wbudowane bezpieczeństwo.
Co jest ważne
- Restrictive by Default z fail-closed dla przewidywalności pod obciążeniem.
- Contract-First przez Rust traits dla kontroli niezmienników.
- Hybryda async/sync: stabilność jądra przed przyspieszeniem I/O.
- Zgodność pgwire bez kopiowania internals PostgreSQL.
- Data engine bez logiki biznesowej dla czystości i testowalności.
— Editorial Team
Brak komentarzy.