TurboKV: Een snelle, embedded key-value store voor Rust

Installatie

Voeg de volgende pakketten toe via cargo:

cargo add turbokv
cargo add tokio --features full

Of voeg de afhankelijkheden direct toe aan je Cargo.toml:

[dependencies]
turbokv = "0.6"
tokio = { version = "1", features = ["full"] }

Het persistente Bloom-filter-formaat van TurboKV maakt gebruik van hardware AES. Bouw x86/x86_64 targets met RUSTFLAGS="-C target-feature=+aes,+sse2" en ARM/AArch64 targets met RUSTFLAGS="-C target-feature=+aes,+neon". Alternatief kun je -C target-cpu=native gebruiken wanneer het binary alleen op hetzelfde CPU-model of een superset van functies zal draaien.

Snelstart

use turbokv::{Db, DbOptions, WriteBatch};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Db::open_with_options("./my-database", DbOptions::durable()).await?;

    db.insert(b"user:1", b"Ada").await?;
    assert_eq!(db.get(b"user:1").await?, Some(b"Ada".to_vec()));

    let mut batch = WriteBatch::new();
    batch.put(b"user:2", b"Grace");
    batch.put(b"user:3", b"Linus");
    batch.delete(b"user:1");
    db.write_batch(&batch).await?;

    for (key, value) in db.scan_prefix(b"user:").await? {
        println!(
            "{} = {}",
            String::from_utf8_lossy(&key),
            String::from_utf8_lossy(&value)
        );
    }

    db.close().await?;
    Ok(())
}

Beschikbare voorbeelden

De volgende runnable voorbeelden zijn beschikbaar:

  • basic: invoegen, ophalen, bijwerken en verwijderen.
  • batch_writes: atomaire puts en deletes.
  • range_queries: geordende range scans en prefix scans.
  • concurrent: gedeelde toegang vanuit Tokio-taken.
  • persistence: herstel van de WAL (Write-Ahead Log) in 'paranoid' modus.
  • configuration: opties voor cache, memtable en compressie.

API-overzicht

Duurzaamheidsinstellingen (Durability presets)

PresetBevestigingsgrens (Acknowledgement boundary)Gebruiksgeval
DbOptions::fast()Zichtbaarheid in het geheugen; geen WALCaches en reproduceerbare data
DbOptions::durable()Toegevoegd aan de WAL zonder per-schrijfactie syncHerstel na procescrash; aanbevolen standaardinstelling
DbOptions::paranoid()WAL groep voltooit sync_all vóór terugkeerSterkste modus, afhankelijk van bestandssysteem/apparaat garanties

Let op: Eén open Db of Engine bezit exclusief zijn datamap. Gebruik close() of closewithstatus() voor een nette afsluiting; het simpelweg laten vallen (dropping) van een handle wordt niet beschouwd als een nette afsluiting.

Database-operaties

Sleutels en waarden zijn willekeurige byte-sequenties die worden aangeleverd via AsRef<[u8]>; strings moeten door de aanroeper worden gecodeerd. Mutatie-API's kopiëren hun inputs voordat ze terugkeren. Point- en collecting-reads retourneren eigendomsrechten op Vec<u8> waarden. Een lege waarde is geldige data en is verschillend van een verwijderde sleutel.

Openen en configuratie

APIParametersResultaat en gedrag
Db::open(path)path: AsRef<Path>Opent of maakt de directory aan met DbOptions::durable(). De open handle bezit exclusief de directory.
Db::openwithoptions(path, options)Database pad en een DbOptions waardeOpent met expliciete instellingen voor duurzaamheid, geheugen, cache en compressie. Weigert tegenstrijdige instellingen (bijv. sync_writes = true terwijl de WAL is uitgeschakeld).
DbOptions::fast()GeenRetourneert de preset zonder WAL.
DbOptions::durable()GeenRetourneert de preset voor herstel na procescrash (WAL).
DbOptions::paranoid()GeenRetourneert de preset voor synchronisatie vóór bevestiging.
options.with_compression(compression)Een Compression variantBuilder-stijl update die de gewijzigde opties retourneert.

Alle presets starten standaard met een memtable van 64 MiB, een block cache van 64 MiB en LZ4-compressie. De volgende publieke velden kunnen worden aangepast vóór het openen:

Veld van DbOptionsBetekenis
wal_enabled: boolVoegt mutaties toe aan de WAL. Uitschakelen kan leiden tot dataverlies bij een procescrash tot de volgende succesvolle flush of afsluiting.
sync_writes: boolWacht op een WAL sync-barrier vóór bevestiging van elke mutatiegroep. Vereist wal_enabled.
memtable_size: usizeBenaderde drempelwaarde in bytes in het geheugen die een memtable-rotatie en achtergrond-flush triggert.
blockcachesize: usizeBudget in bytes voor de gedecomprimeerde SSTable block-cache. Zet op 0 om de cache uit te schakelen.
compression: CompressionSSTable-compressie voor nieuw geschreven data: Lz4, Snappy, Zstd, of None. Bestaande tabellen behouden hun eigen formaat.

Point, bulk en batch operaties

APIParametersResultaat en semantiek
insert(key, value)Byte-achtige sleutel en waardeResult<()>. Voegt de sleutel toe of vervangt deze. De gekozen duurzaamheidsgrens is bereikt vóór succes.
insert_many(entries)Iterator van (sleutel, waarde) parenResult<()>. Kopieert de volledige iterator en past entries sequentieel toe; de laatste duplicate sleutel wint. Dit is een bulk-API, geen atomaire overgang.
get(key)Byte-achtige sleutelResult<Option<Vec<u8>>>. Retourneert None voor ontbrekende of verwijderde sleutels en Some(Vec::new()) voor een opgeslagen lege waarde.
remove(key)Byte-achtige sleutelResult<()>. Schrijft een tombstone; het verwijderen van een ontbrekende sleutel is toegestaan.
contains_key(key)Byte-achtige sleutelResult<bool>. Geeft dezelfde status als get en veroorzaakt momenteel dezelfde waarde-allocatie.
write_batch(batch)&WriteBatchResult<()>. Publiceert alle operaties atomair; lezers zien ofwel de staat vóór de batch, of de volledige batch. De laatste operatie voor een duplicate sleutel wint.

Wanneer de WAL is ingeschakeld, moet één record of een volledige batch passen binnen de u32 payload-lengte van de WAL. Een mislukte of geannuleerde mutatie kan mogelijk al in de WAL zijn terechtgekomen; controleer de sleutel of open de database opnieuw voordat je een niet-idempotente operatie opnieuw probeert.

WriteBatch bezit kopieën van elke sleutel en waarde:

APIParametersEffect
WriteBatch::new()GeenMaakt een lege batch aan.
WriteBatch::with_capacity(capacity)Verwacht aantal operatiesPre-alloceert slots voor operaties, maar niet de bytes voor sleutels of waarden.
batch.put(key, value)Byte-achtige sleutel en waardeVoegt een 'owned' put-operatie toe.
batch.delete(key)Byte-achtige sleutelVoegt een 'owned' delete-operatie toe.
batch.ops()GeenLeent de geordende &[BatchOp] operatielijst.
batch.len() / batch.is_empty()GeenRapporteert het huidige aantal operaties.
batch.clear()GeenVerwijdert alle operaties terwijl de allocatie behouden blijft voor hergebruik.

Range en prefix scans

Sleutels zijn lexicografisch geordend op basis van ruwe bytes. Elke scan legt een coherent momentopname-beeld (point-in-time view) vast. Het maken van een scan kan een actieve memtable bevriezen, waardoor frequente kleine scans het latere flush-werk kunnen verhogen.

APIParametersResultaat en allocatie
range(start, end)Inclusieve start-sleutel en exclusieve eind-sleutelResult<Vec<(Vec<u8>, Vec<u8>)>>; alloceert direct elke geretourneerde sleutel en waarde.
scan_prefix(prefix)Byte prefix; een lege prefix matcht allesCollecteert direct alle matching key/value paren in volgorde.
range_iter(start, end)Dezelfde [start, end) grenzenMaakt een RangeIter. Items zijn Result<EntryGuard, ScanError> omdat corruptie kan worden ontdekt tijdens het itereren.
scanprefixiter(prefix)Byte prefixMaakt een PrefixIter, een alias van dezelfde streaming implementatie.

Het voortbewegen van een streaming iterator is synchroon en kan mmap-reads, checksum-validatie, decompressie en cache-locking uitvoeren. Vernietig de iterator snel: de iterator pinst snapshot-readers en het eigendom van de database-directory.

Iterator of guard API

APIParametersResultaat
iter.count()GeenConsumeert de iterator en retourneert Result<usize, ScanError>.
iter.keys()GeenConsumeert de iterator en collecteert eigendomsrechten op sleutels zonder memtable waarden te materialiseren.
iter.collect_pairs()GeenConsumeert de iterator en collecteert eigendomsrechten op key/value paren.
iter.paginate(offset, limit)Aantal entries om over te slaan en maximum entriesRetourneert een lazy iterator; overgeslagen entries worden getraverseerd, maar hun memtable waarden worden niet gekopieerd.
guard.key()GeenLeent de sleutel zonder de waarde te laden.
guard.value() / guard.value_len()GeenLeent de waarde, of rapporteert de lengte; een memtable-waarde wordt pas gekopieerd wanneer value() voor het eerst wordt aangevraagd.
guard.intopair() / intokey() / into_value()GeenConsumeert de guard en retourneert de gevraagde eigendomsrechten op bytes.

Persistentie, onderhoud en statistieken

APIParametersResultaat en kosten
flush()GeenResult<()>. Leegt wachtende schrijfacties, installeert SSTables en het manifest, synchroniseert de WAL en reclaimt geschikte WAL-segmenten.
compact()GeenResult<CompactionResult>. Leegt de vastgelegde compactie-scope en rapporteert bestanden, bytes, duur, gereclaimde tombstones en of er werk overblijft.
status()GeenGoedkope DatabaseStatus snapshot van onderhoudsfouten, retries en schrijf-backpressure.
logical_stats()GeenExacte Result<LogicalStats> voor unieke live sleutels en bytes. Scant fysieke versies en kan I/O uitvoeren.
physical_stats()GeenGoedkope PhysicalStats meters en process-lifetime counters voor de WAL, memtables, SSTables, cache, stalls en amplificatie.
stats()GeenVerouderde gemengde fysieke counters, behouden voor compatibiliteit.
close()Consumeert DbFlushed wachtende schrijfacties, stopt onderhoud en geeft eigendom vrij bij succes.
closewithstatus()Consumeert DbGestructureerde vorm van afsluiten; maakt onderscheid tussen storage-fouten en onopgeloste flush- of compactie-gezondheid.

De meeste database-methoden retourneren DbError. Streaming iterator creatie retourneert DbError, terwijl fouten die later worden ontdekt worden yielded als ScanError. Voor geavanceerde API's, inclusief de Engine en component-configuraties, zie de crate-documentatie.

Benchmarks

De benchmark gebruikte TurboKV 0.6.0, fjall 2.11.2, en redb 2.6.3 over drie herhalingen. Doorvoer wordt gemeten in bevestigde sleutels per seconde; hoger is beter.

WorkloadTurboKV FastTurboKV DurableTurboKV Paranoidfjall Bufferredb Eventual
Sequential fill (1 key/txn)2,989,5371,774,574213485,2521,397*
Random fill (1 key/txn)1,217,087906,806226456,9241,549*
Overwrite (1 key/txn)1,278,894929,340210446,7331,516*
Sequential batch (100 keys/txn)3,856,2022,277,03120,670511,60080,197
Sequential batch (1,000 keys/txn)3,724,6352,380,390162,938572,671134,636

\ macOS barrier/txn*

Toelichting bij modi:

  • Fast: Schakelt de WAL uit.
  • Durable: Schrijft een herstelbare WAL-record zonder elke bevestiging naar persistente opslag te synchroniseren.
  • Paranoid: Voert die synchronisatie uit vóór terugkeer; de doorvoer voor enkele sleutels wordt hierdoor beperkt door de latentie van de opslag-sync, terwijl expliciete batches één barrier over vele sleutels verspreiden.

Protocol en Hardware:

  • 200.000 deterministische 20-byte sleutels, 400-byte waarden (84 MB logische input, boven de 64 MiB memtable).
  • Eén aanroeper, atomaire batches waar aangegeven, compressie en block cache uitgeschakeld, en een niet-geleegde OS page cache.
  • redb 2.6.3's Durability::Eventual voert een macOS F_BARRIERFSYNC uit voor elke transactie, terwijl de TurboKV Recoverable en fjall Buffer modi stoppen bij hun procescrash-herstelbare OS-cache grenzen. Batching amortiseert die vaste redb-barrier.
  • Gemeten tussen 28 en 29 augustus 2026 op een Apple M4 (Mac16,1), 32 GiB RAM, macOS 15.3.2 (24D81), APFS, en rustc 1.88.0.