Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
turbolite — SQLite VFS mit kalten JOIN-Abfragen unter 100 ms von S3 + Seitenebenen-Komprimierung und -Verschlüsselung | Kitploit
Tools/GitHubGitHub/russellromney/turbolite
Verschlüsselungs-/EntschlüsselungstoolsKryptographieCloud-SicherheitDienstprogramme & FrameworksDatenbanksicherheit
GitHubrussellromney/turbolite

turbolite

SQLite VFS mit kalten JOIN-Abfragen unter 100 ms von S3 + Seitenebenen-Komprimierung und -Verschlüsselung

Repository anzeigen
48012vor 2 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

turbolite

turbolite ist ein SQLite-VFS in Rust, das Punktabfragen und Joins direkt von S3 mit einer Kaltstartlatenz von unter 250 ms ausführt.

Dieses Repository ist ein Cargo-Workspace mit zwei Crates:

  • turbolite – Reine Rust-Bibliothek. SQLite-VFS mit Seitenkomprimierung, Verschlüsselung und S3-Tiering.
  • turbolite-ffi – C-FFI / ladbare Erweiterung + Sprachbindungen (Python, Node.js, Go).

Es bietet außerdem seitenbasierte Komprimierung (zstd) und Verschlüsselung (AES-256) für Effizienz und Sicherheit im Ruhezustand, die unabhängig von S3 verwendet werden können.

Experimentell. turbolite wird aktiv weiterentwickelt und enthält Fehler. Sei vorsichtig.

Objektspeicher wird immer schneller. S3 Express One Zone liefert GETs im einstelligen Millisekundenbereich und Tigris ist ebenfalls extrem schnell. Die Lücke zwischen lokaler Festplatte und Cloud-Speicher schrumpft, und turbolite macht sich das zunutze.

Das Design und der Name sind inspiriert von turbopuffers Ansatz, konsequent um die Einschränkungen von Cloud-Speicher herum zu architektonieren. Das anfängliche Ziel des Projekts war es, Neons 500ms+ Kaltstarts zu schlagen. Ziel erreicht.

Wenn du eine Datenbank pro Server hast, verwende ein Volume. turbolite untersucht, wie man hunderte oder tausende von Datenbanken (eine pro Mandant, eine pro Arbeitsbereich, eine pro Gerät) haben kann, ohne ein Volume für jede zu benötigen, und mit einer einzigen Schreibquelle auskommt.

turbolite wird als Rust-Bibliothek, als ladbare SQLite-Erweiterung (.so/.dylib) und als Sprachpakete für Python und Node.js sowie GitHub-Abhängigkeiten für Go ausgeliefert. Jeder S3-kompatible Speicher funktioniert (AWS S3, Tigris, R2, MinIO, usw.). Es handelt sich um ein standardmäßiges SQLite-VFS, das auf Seitenebene arbeitet, daher sollten die meisten SQLite-Funktionen funktionieren: FTS, R‑Tree, JSON, WAL-Modus, usw.

turbolite ist Teil des breiteren hadb-Ökosystems. Das eigenständige turbolite ist ein Speicher-VFS mit einem einzigen sicheren Schreiber; wenn du HA-Leader-Election plus kontinuierliche WAL-Replikation möchtest, verwende es über haqlite-turbolite, das HaQLite und walrust darüberlegt. Dieser HA-Pfad ist noch sehr experimentell.

Wenn du zu turbolite beitragen oder Fehler finden möchtest, erstelle bitte einen Pull-Request oder öffne ein Issue.

Leistung

1 Mio. Beiträge / 100.000 Benutzer (~1,5 GB gespeichert) ohne Caching, jedes Byte von S3. EC2 c5.2xlarge + S3 Express One Zone (gleiche AZ, ~4 ms GET-Latenz). Fly performance-8x + Tigris (~25 ms GET-Latenz). Beide: 8 dedizierte vCPUs, 16 GB RAM, 7 Prefetch-Worker-Threads. Siehe Benchmarking und Speicher-Backend ist wichtig.

Benchmarks sind nach Cache-Ebene organisiert (was bei der Ausführung der Abfrage bereits auf der lokalen Festplatte ist):

interior ist der realistischste Kalt-Benchmark: innere Seiten werden beim Verbindungsaufbau eager geladen, sodass sie beim Ausführen der ersten Abfrage bereits gecached sind. Indexseiten werden beim ersten Zugriff im Hintergrund aggressiv prefetched und sind möglicherweise noch nicht bereit.

Warmer Cache (VFS-Overhead im Vergleich zu reinem SQLite)

100.000 Zeilen, Fly.io performance-2x (dedizierte vCPU, NVMe, IAD):

Punktabfragen haben den höchsten Overhead pro Seite (~2x). Alles andere erreicht oder übertrifft die Parität. Die lock‑freie Cache-Architektur bedeutet, dass gleichzeitige Lesevorgänge nie Schreibvorgänge blockieren.

Checkpoint-Kosten

NachLokalS3 (gleiche Region RustFS)
1.000 Inserts19ms38ms
10.000 Batch17ms114ms
1.000 Updates9ms36ms

Schreibvorgänge sind immer lokal schnell. Die S3-Kosten fallen nur beim Checkpoint an. Zahlen mit RustFS in derselben Fly-Region (~2ms RTT). S3 Express One Zone wäre vergleichbar.

Schnellstart

Python```bash

pip install turbolite

root@kitploit:~
Please provide the Markdown content to translate.```python
import turbolite

conn = turbolite.connect("my.db", mode="s3",
    bucket="my-bucket",
    endpoint="https://t3.storage.dev")

conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
conn.execute("INSERT INTO users VALUES (1, 'alice', '[email protected]')")
conn.commit()

alice = conn.cursor().execute("SELECT * FROM users").fetchone()
print(alice[1])
>>> "alice"

Siehe Installation für Node, Go, Rust, Nur-lokal-Modus und direkte Nutzung der .so-Erweiterung

Design

turbolite ist für die Zwänge von S3 und nicht für Dateisystem-Zwänge ausgelegt. Jede Entscheidung leitet sich aus diesem Modell ab:

Architektur

turbolite fügt zwischen SQLite und S3 eine Introspections- und Indirektionsschicht ein, die Seiten effizient gruppiert, komprimiert, verfolgt und abruft.

SQLite verwendet einen B‑Tree‑Index und fordert jeweils eine Seite an. Es weiß, dass Seite N am Byte‑Offset N * page_size liegt. Und diese Seiten sind zufällig über die Pagemap verteilt, um effizienten wahlfreien Zugriff zu ermöglichen. Bei S3 würde das Abrufen einer Seite pro Anfrage jedoch Tausende potenziell zufällige GETs pro Abfrage bedeuten.

Aber Seiten sind nicht gleich geschaffen. SQLite hat verschiedene Seitentypen. turbolite trennt Seitengruppen nach Typ: innere B‑Tree‑Seiten, Index‑Blattseiten und Daten‑Blattseiten.

Innere Seiten werden bei jeder Abfrage berührt, um Lookups zu Blattseiten zu leiten. turbolite erkennt sie, speichert sie in komprimierten Bündeln in S3 und lädt sie beim Öffnen des VFS eagerly. Danach ist jede B‑Tree‑Traversierung ein Cache‑Treffer.

Index‑Blattseiten erhalten die gleiche Behandlung: separate Bündel, lazy background prefetch, gegen Verdrängung gepinnt. Kalte Abfragen müssen nur noch Datenseiten abrufen.

turbolite nutzt B‑Tree‑Introspection, um zu verstehen, zu welchem Baum (einer Tabelle oder einem Index) eine Seite gehört, und speichert diese Seiten intelligent zusammen in S3 als Seitengruppen: viele Seiten, die in ein einzelnes S3‑Objekt gebündelt sind. Groß genug, um die Bandbreite beim Prefetch zu sättigen, klein genug für Punktabfragen. Standard: 256 Seiten pro Gruppe, ~16 MB bei 64‑KB‑Seiten.

Das gemeinsame Speichern derselben Tabelle/Index bedeutet, dass wir für kalte Abfragen die geringstmögliche Anzahl von GETs durchführen.

turbolite indireziert Seitenlookups mit einer Manifest-Datei, die die Quelle der Wahrheit dafür ist, wo sich jede Seite befindet. Es ersetzt SQLites implizites offset = page * size durch explizite Zeiger. Alte Seitengruppenversionen werden nie überschrieben; der Manifest‑PUT ist der atomare Commit‑Punkt. Alte Versionen werden zu Müll, der von gc() bereinigt wird.

SQLite verwendet standardmäßig 4‑KB‑Seiten, um der Dateisystem‑Seitengröße zu entsprechen. Auf S3 ist die Dateisystem‑Seitengröße irrelevant. Entscheidend ist die Minimierung der Anfragenanzahl und die Maximierung der B‑Tree‑Fächerung. Die Antwort ist große Seiten: turbolite verwendet standardmäßig 64‑KB‑Seiten. Weniger Seiten = weniger S3‑Rundläufe, um ein Blatt zu erreichen.

Um Punktabfragen schnell zu machen, verwendet turbolite seekable compression: jede Seitengruppe wird als mehrere zstd‑Frames (~4 Seiten pro Frame) codiert. Das Manifest speichert Byte‑Offsets pro Frame, sodass ein Cache‑Miss nur das ~256‑KB‑Sub‑Chunk mit der benötigten Seite per S3‑Range‑GET abruft, nicht die gesamte Gruppe.

Prefetching hat zwei Ebenen: proaktiv (Abfrageplan‑Frontrunning) und reaktiv (adaptiv, missbasiert).

Abfrageplan‑Frontrunning läuft zuerst. Bevor eine Abfrage ausgeführt wird, fängt turbolite den SQLite‑Abfrageplan über EXPLAIN QUERY PLAN ab, extrahiert die genauen Tabellen und Indizes, die die Abfrage berühren wird, und übergibt alle ihre Seitengruppen an den Prefetch‑Pool, bevor die erste Seite überhaupt gelesen wird. Ein Join über fünf Tabellen, der sonst fünf sequenzielle Miss‑dann‑Fetch‑Zyklen auslösen würde, startet stattdessen alle fünf Abrufe parallel beim Abfragestart. Für SCAN-Abfragen bedeutet dies, dass die gesamte Tabelle vorab geholt wird.

Hinweis: SQLite unterstützt nur einen Trace‑Callback pro Verbindung. Wenn eine andere Erweiterung den Slot zuerst belegt, fällt Frontrunning stillschweigend auf reaktives Prefetch zurück.

Reaktives Prefetching behandelt das, was Frontrunning verpasst, und dient als Fallback. Bei einem Cache‑Miss passieren zwei Dinge gleichzeitig:

  1. Inline‑Range‑GET: das spezifische Sub‑Chunk abrufen, das die benötigte Seite enthält, sofort an SQLite zurückgeben.
  2. Hintergrund‑Prefetch: Schwesterngruppen für diesen Baum gemäß einem Zeitplan an den Prefetch‑Pool übergeben.

Miss‑Zähler werden pro B‑Tree, nicht global geführt. Eine Profilabfrage, die zuerst users (Miss 1) und dann posts (Miss 1) trifft, verfolgt korrekt jeden Baum mit 1, nicht 2. Dies verhindert, dass ein Multi‑Table‑Join versehentlich das Prefetc`hing auf jedem Baum eskaliert, nur weil er mehrere berührt.

Jeder aufeinanderfolgende Miss durchläuft einen Prefetch‑Zeitplan, der steuert, welcher Anteil der gleichartigen Baumgruppen vorabgeholt wird. turbolite wählt automatisch einen Zeitplan basierend auf dem Abfrageplan:

  • Search schedule [0.3, 0.3, 0.4]: für SEARCH ... USING INDEX-Abfragen, die unbekannte Teile von Indizes durchsuchen. Aggressiv ab dem ersten Miss, da wir nicht wissen, wie viel des Index durchsucht wird.
  • Lookup schedule [0.0, 0.0, 0.0]: für Punktabfragen und Indexlookups, die 1–2 Seiten pro Baum treffen. Drei freie Hops vor jeglichem Prefetch. Null‑lastige Zeitpläne übertreffen frühe Anstiege sowohl auf S3 Express als auch auf Tigris.

Sie können den Prefetch‑Zeitplan beim Öffnen anpassen, indem Sie prefetch.search / prefetch.lookup auf TurboliteConfig setzen – Sie kennen die erwartete Arbeitslastform, sodass der VFS nicht raten muss. Siehe Konfiguration des Prefetch.

Beide Zeitpläne nutzen die B‑Tree‑Introspection: jede vorabgeholte Gruppe enthält garantiert Seiten aus dem richtigen Baum. Ein Beispiel: Wenn SQLite eine Seite aus der Tabelle users anfordert und dann eine weitere aus derselben Tabelle, nimmt turbolite einen Scan an und holt den Rest der users-Tabelle im Hintergrund vorab – und nichts anderes. Ohne B‑Tree‑Introspection würde es versehentlich die Hälfte der users-Tabelle und die Hälfte der posts-Tabelle abrufen, nur weil die Daten auf der Festplatte nebeneinander liegen.

Index‑Leaf‑Lookahead macht dasselbe für indizierte SEARCH-Abfragen. Ein Index‑Blatt listet bereits die Tabellen‑Rowids auf, die SQLite als Nächstes anfordern wird – also löst turbolite sie über die gecachten inneren Seiten auf und holt diese Tabellen‑Frames in einem Batch statt einzeln vorab, wodurch die Anzahl der Anfragen reduziert wird.

In-Memory-Page-Cache

turbolite hat seinen eigenen In-Memory-Page-Cache, der SQLites integrierten Page-Cache ersetzt. SQLites Pager cached Seiten intern und liest nie erneut vom VFS für gecachte Seiten. Dies ist für Datenbanken mit nur einem Schreiber in Ordnung, aber bei Lesereplikaten (HA‑Follower, Manifest‑pollierende Leser) wird SQLites Cache veraltet, wenn sich die zugrunde liegenden Daten durch Replikation ändern.

turbolites Cache ist manifest‑bewusst: Wenn set_manifest() ausgelöst wird (neue Daten durch Replikation), invalidiert es betroffene Seiten sowohl im Festplatten‑Cache als auch im In-Memory‑Cache. Schreibvorgänge invalidieren ebenfalls ihre Seiten im In-Memory‑Cache. Dies garantiert frische Lesevorgänge nach Replikation oder Schreibvorgängen.

Architektur:``` SQLite (PRAGMA cache_size=0) -> turbolite VFS xRead -> in-memory page cache (64MB default, AtomicPtr, zero-lock reads) -> disk cache (NVMe pread) -> S3 (on miss)

root@kitploit:~
**Konfiguration:**
- `cache.mem_budget` in `TurboliteConfig` (Bytes). Standard: 64MB.
- Umgebungsvariable `TURBOLITE_MEM_CACHE_BUDGET` (z.B. `128MB`, `1GB`).
- Auf `0` setzen, um den In-Memory-Cache vollständig zu deaktivieren.

`turbolite.connect()` (Python/Go/TypeScript) deaktiviert automatisch den Seiten-Cache von SQLite und verwendet stattdessen den von turbolite. Rust-Benutzer, die `Connection::open_with_flags_and_vfs` direkt verwenden, sollten `PRAGMA cache_size=0` setzen, um das gleiche Verhalten zu erzielen.

### Verschlüsselung & Komprimierung

#### Komprimierung

Alle Daten werden vor der Speicherung mit zstd komprimiert. Seitengruppen verwenden eine seekbare Multi-Frame-Codierung, die jeden Frame (~4 Seiten, ~256KB) unabhängig komprimiert, sodass eine Punktsuche nur den relevanten Frame dekomprimiert, nicht die gesamte Seitengruppe. Benutzerdefinierte zstd-Wörterbücher können die Komprimierungsverhältnisse weiter verbessern.

Der lokale (Nicht-S3)-Modus komprimiert ebenfalls auf Seitenebene mit zstd. Siehe CLI für Werkzeuge zum Training von Wörterbüchern.

#### Verschlüsselung

Wenn die Verschlüsselung aktiviert ist, verschlüsselt turbolite alles: S3-Objekte, lokalen Cache, WAL, Metadaten. S3-Daten verwenden AES-256-GCM mit zufälligen Nonces pro Frame (authentifiziert, manipulationserkennend). Lokale Daten verwenden AES-256-CTR ohne Größen-Overhead. Die Verschlüsselung erfolgt nach der Komprimierung: `plaintext → zstd → encrypt → S3`.

**Schlüsselrotation:** `rotate_encryption_key(config, new_key)` verschlüsselt erneut, fügt Verschlüsselung hinzu oder entfernt sie auf allen S3-Daten, ohne zu dekomprimieren. `Some` zu `Some` rotiert Schlüssel, `Some` zu `None` entfernt Verschlüsselung, `None` zu `Some` fügt sie hinzu. Absturzsicher: alte Objekte werden nie überschrieben, der Manifest-Upload ist der atomare Commit-Punkt, und ein Verifizierungsschritt bestätigt, dass neue Daten lesbar sind, bevor committet wird. Waisen von teilweisen Läufen werden von `gc()` bereinigt.

## Stärken und Einschränkungen

### Wo turbolite schnell ist

**Punktsuchen sind die Stärke.** Auf Cache-Ebene `index` holt eine Punktsuche 1-2 Sub-Chunks per S3-Bereichs-GET (~100KB pro Stück). Innere und Indexseiten sind bereits zwischengespeichert. Auf Cache-Ebene `none` kommen ~120ms für erneutes Holen innerer Seiten + erste Datenseite hinzu. Dies funktioniert auf jeder Maschinengröße.

**Scans mit genügend Kernen.** Der Prefetch-Pool sättigt die S3-Bandbreite mit baumweiser adaptiver Planung. Suchabfragen erhöhen den Prefetch aggressiv ab dem ersten Fehlschlag; planbewusste SCAN-Abfragen führen einen Bulk-Prefetch der gesamten Tabelle im Voraus durch. Ausreichend Threads können Multi-GB-Datenbanken in Sekunden mit 2-3 Prefetch-Batches synchronisieren.

### Wo turbolite langsam ist

**Scans auf kleinen Maschinen.** Mit 1 Prefetch-Thread dauert ein Scan über 1,46GB Sekunden, nicht Millisekunden. Der Engpass sind S3-Roundtrips: jeder Hop holt Gruppen seriell. Wenn Ihre erste Abfrage ein vollständiger Scan auf einer 1-vCPU-Maschine ist, rechnen Sie mit schmerzhaftem Start.

**Schlechte Thread-Einstellung.** Zu wenige Prefetch-Threads und Scans bleiben hängen, während sie auf S3 warten. Zu viele und die SQLite-Vordergrundarbeit beginnt mit Downloads zu konkurrieren. Die Standardeinstellung (`max(num_cpus - 1, 1)`) lässt einen Kern für Vordergrundarbeit übrig, aber scan-lastige Arbeitslasten auf großen Datenbanken benötigen dennoch genügend CPUs.

**Strafe für die erste Abfrage.** Die erste Abfrage auf Cache-Ebene `none` kostet ~50-200ms für das Laden innerer Seiten plus mindestens einen Datenabruf. Wenn die Abfrage eine Indexseite benötigt, bevor der Hintergrund-Prefetch abgeschlossen ist, fällt sie auf einen Inline-Bereichs-GET zurück.

### Aktuelle Einschränkungen

- **Standalone-turbolite ist Einzelschreiber.** Zwei Maschinen, die direkt in dasselbe Präfix schreiben, beschädigen das Manifest.
- **HA/Failover-Modus ist experimentell und befindet sich in `haqlite-turbolite`.** Dieser Stack kombiniert HaQLite-Leases, turbolite-Seiten-Tiering und walrust-kontinuierliche WAL-Replikation. Es ist der vorgesehene Weg für Multi-Node-Bereitstellungen, nicht direkter Multi-Schreiber-Zugriff auf ein turbolite-Präfix.
- **WAL-Versand ist experimentell.** Erfordert das Feature-Flag `wal` + walrust. Siehe [Haltbarkeit](#durability).

SQLite-Funktionen, die **funktionieren**: FTS, R-tree, JSON, WAL-Modus, DELETE-Journal-Modus, VACUUM, Autovacuum.

## Optimierung

### Allgemeine Parameter

| Parameter | Steuerung | Standard |
|-----------|-----------|----------|
| `prefetch.threads` | Arbeiter-Threads für parallele S3-Abrufe | max(num_cpus - 1, 1) |
| `cache.pages_per_group` | Seiten pro S3-Objekt, größer = weniger PUTs, mehr Bytes pro Abruf | 256 |
| `cache.gc_enabled` | Alte Seitengruppenversionen nach Checkpoint löschen | true |
| `sync_mode` | Checkpoint-Haltbarkeit: `Durable` (S3-Upload im Checkpoint) oder `LocalThenFlush` (Upload verschieben) | Durable |

### Prefetch-Pläne

Query-Plan-Frontrunning (siehe Architektur) ist der primäre Prefetch-Mechanismus. Die untenstehenden reaktiven Pläne dienen als Fallback, wenn Frontrunning nicht verfügbar ist oder wenn Abfragen auf Seiten zugreifen, die nicht im Plan waren.

| Strategie | Wann | Standardplan | Was passiert |
|-----------|------|--------------|--------------|
| **SCAN** (Frontrun) | EQP sagt `SCAN table` | Alle Gruppen im Voraus | Bulk-Prefetch der gesamten Tabelle vor dem ersten Lesen. Kein Hop-Plan erforderlich. |
| **SEARCH** (reaktiv) | EQP sagt `SEARCH ... USING INDEX` | `[0.3, 0.3, 0.4]` | Aggressiver Prefetch ab erstem Fehlschlag; scannt unbekannte Indexteile. |
| **Lookup** (reaktiv) | Punktabfragen, keine EQP-Info | `[0.0, 0.0, 0.0]` | Drei freie Hops, null Prefetch. Punktabfragen profitieren selten von Prefetch. |

Jedes Element ist der Anteil der Geschwistergruppen, die beim N-ten aufeinanderfolgenden baumweisen Cache-Fehlschlag vorab abgerufen werden. Wenn die Fehlschläge die Array-Länge überschreiten, fraction=1.0 (alle verbleibenden).

**Warum zwei reaktive Pläne?** SEARCH-Abfragen durchsuchen unbekannte Teile von Indizes/Tabellen und benötigen aggressives Vorwärmen. Lookups treffen 1-2 Seiten pro Baum und benötigen kaum Prefetch. Baumweise Fehlschlagzähler stellen unabhängige Verfolgung sicher: eine Profilabfrage, die Benutzer trifft (Fehlschlag 1) und dann Beiträge (Fehlschlag 1), verfolgt jeden Baum separat.

### Konfigurieren des Prefetch

Setzen Sie `prefetch.search` und `prefetch.lookup` in `TurboliteConfig` bei der VFS-Konstruktion:```rust
use turbolite::tiered::{TurboliteConfig, PrefetchConfig};

let config = TurboliteConfig {
    prefetch: PrefetchConfig {
        search: vec![0.4, 0.3, 0.3],
        lookup: vec![0.0, 0.0, 0.2],
        query_plan: true,
        ..Default::default()
    },
    ..Default::default()
};

Für die Neuabstimmung pro Abfrage ohne Wiederherstellen der Verbindung verwenden Sie die turbolite_config_set SQL-Funktion (Phase Cirrus c). Jeder Push ist auf den Handle der aufrufenden Verbindung beschränkt und bleibt in Kraft, bis Sie ihn erneut ändern:```sql SELECT turbolite_config_set('prefetch_search', '0.5,0.5,0.0'); SELECT turbolite_config_set('prefetch_lookup', '0.0,0.0,0.0'); SELECT * FROM posts WHERE created_at > ?; -- runs with the new schedule

root@kitploit:~
### Index-leaf lookahead

Wenn eine Abfrage einen Index verwendet, um Tabellenzeilen zu finden (`SEARCH ... USING INDEX`), benennt das Index-Blatt, das SQLite liest, bereits die Tabellen-Rowids, die es abrufen wird. Lookahead parst diese Rowids, löst sie über die zwischengespeicherten Innenseiten in ihre Tabellen-Blatt-Frames auf und ruft die Frames in einem Batch vorab ab — sodass die Tabellenzeilen gemeinsam ankommen, anstatt jeweils einen einzelnen S3-Roundtrip zu erfordern.

Es ist **standardmäßig aktiviert** und greift nur bei indizierten `SEARCH`-Anfragen, die in eine Tabelle folgen. Scans, punktgenaue Rowid-Lesevorgänge und vollständig aufgewärmte Abfragen durchlaufen den normalen Pfad unverändert, sodass es selten einen Grund gibt, es zu deaktivieren. Es benötigt query-plan prefetch (`plan_aware`, standardmäßig true).

Der einzige Fall, in dem es deaktiviert werden sollte, ist eine vollständig aufgewärmte, CPU-sensitive Arbeitslast, bei der das Parsen jedes Indexblatts ein wenig kostet und nichts vorab lädt, da die Seiten bereits zwischengespeichert sind:```sql
SELECT turbolite_config_set('lookahead', 'false');

Oder setze lookahead auf TurboliteConfig / die Umgebungsvariable TURBOLITE_LOOKAHEAD beim Öffnen.

Rust-Aufrufer können denselben Pfad über turbolite::tiered::settings::set verwenden.

Empfohlene Konfigurationen

Hinweis: Prefetch ist pro Verbindung. Jede neue Verbindung startet mit leeren, baumbezogenen Fehlzählern. Der Cache wird geteilt, sodass eine zweite Verbindung von den durch die erste zwischengespeicherten Seiten profitiert.

Speicher-Backend ist entscheidend

Optimale Prefetch-Pläne hängen vom Latenz-Bandbreiten-Kompromiss Ihres S3-Backends ab. Wir haben 10 Planpaare bei 6 Abfragen sowohl auf S3 Express (~4ms GET) als auch auf Tigris (~25ms GET) getestet:

Auf S3 Express ist aus/aus (gar kein Prefetch) für Punktabfragen überraschend konkurrenzfähig, da jeder Sub-Chunk-Bereichs-GET nur ~4ms dauert. Der Abstand zwischen „kein Prefetch" und „optimalem Prefetch" ist gering (23% bei Punkt-Lookups), weil einzelne GETs billig sind. Auf Tigris profitiert dieselbe Abfrage deutlich mehr von Prefetch (bis zu 39% bei idx-filter), weil jede unnötige Roundtrip 25ms kostet.

Der praktische Effekt: Bei Backends mit hoher Latenz forcieren Sie die Suchpläne stärker und behalten Sie bei Lookup-Plänen mehr führende Nullen. Auf S3 Express funktionieren die Standardeinstellungen gut und Optimierungen bringen geringere Gewinne. Die Vollscan-Leistung ist bei beiden Backends unabhängig vom Plan, da die query-plan-gesteuerte Vorausberechnung die gesamte Tabelle im Voraus im Bulk vorlädt.

Verwenden Sie tiered-tune (siehe unten), um optimale Pläne für Ihr spezifisches Backend und Ihre Abfragen zu finden.

Optimierungswerkzeug

tiered-tune verbindet sich mit einer vorhandenen Turbolite-Datenbank und durchläuft Prefetch-Pläne mit Ihren tatsächlichen Abfragen. Statt Pläne zu erraten, führen Sie Ihre echte Arbeitslast aus und lassen Sie das Werkzeug das beste Paar finden:```bash

Connect to existing database, test your queries

cargo run --release --features cloud,zstd --bin tiered-tune --
--prefix "databases/tenant-123"
--query "SELECT * FROM users WHERE id = ?1"
--query "SELECT p.*, u.name FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ?1"
--iterations 10

Custom schedule grid

cargo run --release --features cloud,zstd --bin tiered-tune --
--prefix "databases/tenant-123"
--query "SELECT * FROM orders WHERE user_id = ?1 ORDER BY created_at DESC LIMIT 20"
--search-schedules "0.3,0.3,0.4;0.5,0.5;1.0"
--lookup-schedules "0;0,0,0.1;0,0,0,0.1,0.2"
--iterations 10

root@kitploit:~
Die Ausgabe ist eine Pro-Abfrage-Vergleichstabelle (wie `tiered-bench --matrix`) mit p50, p90, GET-Anzahl und Bytes für jedes Schedule-Paar. Das Tool empfiehlt einen Schedule und gibt die `TurboliteConfig`-Zuweisung aus, um ihn anzuwenden.

## Dauerhaftigkeit

turbolite ist eine Speicherschicht, kein Replikationssystem. Die Dauerhaftigkeit hängt davon ab, wann die Daten S3 erreichen.

**Nach dem Checkpoint**: Seitengruppen + Manifest befinden sich in S3. S3 bietet 11 Neunen Dauerhaftigkeit. Diese Daten überleben den Verlust einer Maschine.

**Zwischen Checkpoints**: Schreibvorgänge leben nur im lokalen WAL auf der lokalen Festplatte. Wenn die Maschine vor dem nächsten Checkpoint ausfällt, sind diese Schreibvorgänge verloren.

Die Checkpoint-Häufigkeit steuert den Kompromiss: häufigere Checkpoints = kleineres Datenrisikofenster, aber mehr S3 PUTs. Die Voreinstellung ist SQLites Auto-Checkpoint (alle 1000 WAL-Frames).

### Checkpoint-Modi

turbolite unterstützt zwei Checkpoint-Modi über `sync_mode` in `TurboliteConfig`:

**`SyncMode::Durable`** (Standard). Der Checkpoint lädt Seitengruppen zu S3 hoch, während die EXCLUSIVE-Sperre von SQLite gehalten wird. Einfach, auf jedem Checkpoint vollständig dauerhaft. Keine Schreib- oder Lesevorgänge können fortgesetzt werden, bis der Upload abgeschlossen ist. Gut für die meisten Arbeitslasten.

**`SyncMode::LocalThenFlush`**. Der Checkpoint schreibt nur in den lokalen Festplatten-Cache (~1ms Sperrdauer), gibt dann die Sperre frei. Der Aufrufer lädt separat über `flush_to_s3()` zu S3 hoch, währenddessen Lese- und Schreibvorgänge normal fortgesetzt werden. Dies ist nützlich für schreibintensive Arbeitslasten, bei denen das Blockieren von Lesern für die Dauer eines S3-Uploads nicht akzeptabel ist.

Zwischen Checkpoint und Flush existieren Daten nur im lokalen Festplatten-Cache. Ein Prozessabsturz ist in Ordnung (Daten sind auf der lokalen Festplatte, und Staging-Logs erfassen die genauen Seiteninhalte für den Upload). Maschinenverlust vor dem Flush bedeutet, dass diese Schreibvorgänge verloren sind. Cache-Verdrängung ist sicher: turbolite schützt ausstehende Seiten automatisch vor der Verdrängung.

**Absturzwiederherstellung**: Wenn der Prozess zwischen Checkpoint und Flush abstürzt, überleben Staging-Logs auf der Festplatte. Beim nächsten `TurboliteVfs::new()` werden sie automatisch wiederhergestellt und für den nächsten `flush_to_s3()`-Aufruf in die Warteschlange gestellt. Lesevorgänge werden sofort aus dem lokalen Cache bedient, ohne auf den Flush zu warten.

### WAL-Versand (experimentell)

Mit aktiviertem `wal`-Feature-Flag versendet turbolite WAL-Frames über [walrust](https://github.com/russellromney/walrust) zu S3 und schließt damit die Dauerhaftigkeitslücke zwischen einzelnen Schreibvorgängen und Checkpoints.```toml
# Cargo.toml
turbolite = { version = "0.5", features = ["cloud", "zstd", "wal"] }

💡 Hinweis: Fügen Sie weitere Quellen mit Daten zur Datei src/maigret/sites.md hinzu!```rust let config = TurboliteConfig { wal_replication: true, // enable WAL shipping ..Default::default() };

root@kitploit:~
turbolite und walrust bleiben über den Wiedergabe-Cursor synchronisiert, der als `manifest.change_counter` gespeichert ist. Import-/Checkpoint-Pfade speisen diesen Cursor aus dem Dateiänderungszähler von SQLite; die direkte Seitenwiedergabe kann ihn bis zur neuesten festgeschriebenen Änderungssatzsequenz fortschreiben. Bei einem Kaltstart materialisiert turbolite die Datenbank aus Seitengruppen, dann spielt walrust WAL-Segmente mit txid > `change_counter` ab, um Schreibvorgänge wiederherzustellen, die nach dem letzten Checkpoint aufgetreten sind.

**Dauerhaftigkeitsmodell mit WAL-Versand**: Jede festgeschriebene Transaktion wird innerhalb des Sync-Intervalls (Standard 100ms) als WAL-Segment an S3 gesendet. Wenn die Maschine ausfällt, geht höchstens ein Sync-Intervall an Schreibvorgängen verloren. Nach einem Checkpoint werden WAL-Segmente mit txid <= `change_counter` automatisch per Garbage Collection entfernt.

WAL-Versand ergänzt SyncMode: SyncMode steuert, wie Checkpoints S3 erreichen, WAL-Versand macht einzelne Schreibvorgänge vor dem Checkpoint dauerhaft.

### Konsistenzmodell

Einzelschreiber, Snapshot-Leser. Ein Prozess schreibt; Leser sehen das zuletzt festgeschriebene Manifest zum Zeitpunkt des Öffnens. turbolite ist keine verteilte Datenbank und koordiniert nicht zwischen mehreren Schreibern.

## Lokaler Modus (ohne S3)

turbolite funktioniert auch als rein lokales komprimiertes/verschlüsseltes VFS:

Komprimierung: zstd (Standard), lz4, snappy, gzip. Mit zstd können Sie benutzerdefinierte Komprimierungswörterbücher trainieren und einbetten und automatisch rotieren, um eine effizientere Komprimierung zu erzielen. Größere Seitengrößen komprimieren besser. Siehe CLI für Trainingswerkzeuge.

Verschlüsselung: AES-256-GCM pro Seite.

Die Operation auf Seitenebene bedeutet, dass die meisten SQLite-Funktionen weiterhin funktionieren: FTS, R-Baum, JSON, WAL-Modus. Die meisten anderen SQLite-Komprimierungs-/Verschlüsselungserweiterungen arbeiten auf Dateiebene oder erfordern benutzerdefinierte Builds.

## Installation

Dieses Repository ist ein Cargo-Workspace. Die `turbolite`-Crate ist die reine Rust-Bibliothek im Workspace-Root. Sprachbindungen und die ladbare Erweiterung befinden sich in `turbolite-ffi/`.

**Python**: `pip install turbolite` — siehe [turbolite-ffi/packages/python/](https://github.com/russellromney/turbolite/blob/HEAD/turbolite-ffi/packages/python/)```python
import turbolite

# Local compressed (no S3 needed)
conn = turbolite.connect("my.db")

# S3 cloud
conn = turbolite.connect("my.db", mode="s3", bucket="my-bucket", endpoint="https://t3.storage.dev")

# Manual extension loading for full control
import sqlite3
conn = sqlite3.connect(":memory:")
turbolite.load(conn)
conn.close()
conn = sqlite3.connect("file:my.db?vfs=turbolite", uri=True)       # local
# For S3, prefer turbolite.connect(..., mode="s3", bucket=..., prefix=...).
# It registers a per-database VFS so multiple S3 volumes can share one process.

Node.js: npm install turbolite — siehe turbolite-ffi/packages/node/

Rust:```toml [dependencies] turbolite = "0.5" # local VFS turbolite = { version = "0.5", features = ["cloud"] } # + S3 storage turbolite = { version = "0.5", features = ["encryption"] } # + encryption

root@kitploit:~
**Go** (cgo, verknüpft die gemeinsam genutzte Bibliothek):```bash
make lib-bundled  # build libturbolite.{so,dylib}
root@kitploit:~
// #cgo LDFLAGS: -L/path/to/target/release -lturbolite
// #include <stdlib.h>
// extern int turbolite_register_local_file_first(const char* name, const char* db_path, int level);
// extern void* turbolite_open(const char* path, const char* vfs_name);
// extern int turbolite_exec(void* db, const char* sql);
// extern char* turbolite_query_json(void* db, const char* sql);
// extern void turbolite_close(void* db);
import "C"

The recommended turbolite_register_local_file_first(name, db_path, level) ist auf den benutzerseitigen Datenbankpfad abgestimmt. Die untergeordnete Funktion turbolite_register_local(name, cache_dir, level) wird weiterhin für Entwickler exportiert, die das Cache-Verzeichnis selbst verwalten möchten. Siehe examples/go/ für ein vollständiges HTTP-Server-Beispiel.

Ladbare Erweiterung (beliebige Sprache)

Erstellen Sie die ladbare Erweiterung für jede Sprache mit SQLites load_extension:```bash

after cloning the turbolite repo

make ext # produces target/release/turbolite.{so,dylib}

root@kitploit:~
### Erforderliche Berechtigungen```c
sqlite3_enable_load_extension(db, 1);
sqlite3_load_extension(db, "path/to/turbolite", NULL, NULL);
// "turbolite" VFS (local) is always registered
// "turbolite-s3" is a single-volume convenience VFS when TURBOLITE_BUCKET is set

Für die File-first-Benutzergeschichte registrieren Sie einen pro Datenbank VFS, der die app.db des Aufrufers besitzt:```sql SELECT turbolite_register_file_first_vfs('app', '/data/app.db'); -- now open /data/app.db via vfs=app; turbolite stores its sidecar -- metadata at /data/app.db-turbolite/.

root@kitploit:~
Um das standardmäßige `"turbolite"` VFS für den datei-ersten Modus beim Laden der Erweiterung
zu konfigurieren, setzen Sie `TURBOLITE_DATABASE_PATH=/data/app.db` in der Umgebung,
bevor Sie die Erweiterung laden. Der Sidecar ist dann `/data/app.db-turbolite/`
und die untergeordnete `TURBOLITE_CACHE_DIR`-Einstellung wird ignoriert.

### Node.js```bash
npm install turbolite

INPUT:```js const { connect } = require("turbolite");

// File-first: /data/app.db is the local page image. // /data/app.db-turbolite/ holds hidden implementation state. const db = connect("/data/app.db"); db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"); db.prepare("INSERT INTO users VALUES (?, ?)").run(1, 'alice');

const rows = db.prepare("SELECT id, name FROM users").all(); // [{ id: 1, name: 'alice' }] db.close();

root@kitploit:~
`db` ist eine standardmäßige better-sqlite3-Datenbank. `connect()` registriert für Sie ein datei-zuerst-VFS pro Datenbank. Um eine standardmäßige SQLite-Datei zu exportieren (z. B. zur Inspektion mit der `sqlite3`-CLI), verwenden Sie die better-sqlite3-Backup-API: `await db.backup('export.sqlite')`. Siehe [turbolite-ffi/packages/node/](https://github.com/russellromney/turbolite/blob/HEAD/turbolite-ffi/packages/node/) für die vollständige Dokumentation.

### Rust (lokal, datei-zuerst)```rust
use turbolite::tiered::{TurboliteVfs, TurboliteConfig};

// `app.db` is the user-visible local page image.
// `app.db-turbolite/` holds hidden implementation state.
let config = TurboliteConfig::for_database_path("/data/app.db");
let vfs = TurboliteVfs::new_local(config)?;
turbolite::tiered::register("turbolite", vfs)?;

let conn = rusqlite::Connection::open_with_flags_and_vfs(
    "/data/app.db",
    rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
    "turbolite",
)?;

Die untergeordnete Form ermöglicht es Ihnen, das Cache-Verzeichnis direkt auszuwählen:```rust let config = TurboliteConfig { cache_dir: "/path/to/data".into(), // turbolite owns this dir ..Default::default() };

root@kitploit:~
In diesem Fall ist das lokale Image `/path/to/data/data.cache` anstelle einer
vom Aufrufer benannten `app.db`. Neue Embedder sollten die datei-erste Form bevorzugen.

### Rust (S3 cloud)```rust
use turbolite::tiered::{TurboliteVfs, TurboliteConfig};
use hadb_storage::StorageBackend;

let config = TurboliteConfig::for_database_path("/data/app.db");
let storage: Arc<dyn StorageBackend> = /* your S3 backend */;
let vfs = TurboliteVfs::with_backend(config, storage, tokio::runtime::Handle::current())?;
turbolite::tiered::register("turbolite", vfs)?;

let conn = rusqlite::Connection::open_with_flags_and_vfs(
    "/data/app.db",
    rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
    "turbolite",
)?;

app.db ist das komprimierte Seitenabbild von turbolite. Es wird nicht garantiert, dass es direkt mit dem Standard sqlite3 geöffnet werden kann. Für eine normale SQLite-Datei (z.B. für die sqlite3 CLI) verwenden Sie die SQLite Online-Backup-API oder den bindungsspezifischen Export-Helfer (conn.iterdump() in Python, db.backup() in Node).

CLI

turbolite enthält ein CLI zum Inspizieren, Verwalten und Interagieren mit turbolite-Datenbanken, ohne Rust schreiben zu müssen.```bash cargo install turbolite --features cloud,zstd

root@kitploit:~
### Befehle```bash
# Inspect a database manifest
turbolite info --db my.db
turbolite info --db my.db --bucket my-bucket --endpoint https://t3.storage.dev

# Interactive SQLite shell (with turbolite VFS)
turbolite shell --db my.db
turbolite shell --db my.db --bucket my-bucket --read-only

# Download entire database from S3 into local cache
turbolite download --db my.db --bucket my-bucket --threads 8

# Export to plain SQLite (for migration or backup)
turbolite export --db my.db --output plain.db

# Import a plain SQLite file into turbolite S3 format
turbolite import --input plain.db --bucket my-bucket --prefix databases/my-db

Alle S3-Befehle akzeptieren die Flags --bucket, --prefix, --endpoint und --region oder lesen aus den Umgebungsvariablen TURBOLITE_BUCKET, TURBOLITE_PREFIX, AWS_ENDPOINT_URL und AWS_REGION.

Verwandte Projekte und Vergleich

Es gibt viele Projekte im Bereich SQLite über Netzwerk. turbolite leiht sich Ideen von allen.

Bereichsanfragen auf rohe .db-Dateien (schreibgeschützt)

Der häufigste Ansatz: Eine unveränderte .db-Datei auf S3 oder ein CDN legen und HTTP-Range-GETs ausführen, wenn SQLite eine Seite liest.

  • sql.js-httpvfs: Das Original. WASM SQLite mit HTTP-Range-Anfragen. Hat virtuelle Leseköpfe mit exponentiellem Prefetching für Scans. Pionierarbeit für die Idee, dass man nicht die gesamte Datenbank herunterladen muss, um sie abzufragen.
  • sqlite_web_vfs: Native C++ VFS-Erweiterung mit adaptiver Anfragebündelung und einer optionalen .dbi-Indexdatei, die B-Baum-Innenknoten für Prefetching vorab sammelt – die gleiche Idee wie turbolites Innenknoten-Pakete. Entwickelt für die Kombination mit sqlite_zstd_vfs.
  • sqlite3vfshttp: Sauberes, minimales Go VFS. Entwickelt für die Abfrage von SQLite in S3 aus Lambda ohne Herunterladen der Datei.
  • sqlite-s3-query: Python-Bibliothek, die mithilfe von ctypes Datei-I/O abfängt und Lesevorgänge in S3-Range-GETs übersetzt. Erfordert Versioned Buckets für Konsistenz beim Austausch der Datenbank.
  • sqlite-wasm-http: Geistiger Nachfolger von sql.js-httpvfs unter Verwendung des offiziellen SQLite-WASM-Builds. Gemeinsamer Seiten-Cache über SharedArrayBuffer. Aktiv gepflegt.
  • s3sqlite: Python, verwendet s3fs (FUSE) + APSW. Lässt FUSE die Bereichsanfragen abwickeln.

Diese sind alle schreibgeschützt und rufen unkomprimierte Seiten aus der Rohdatei ab. Eine Punktabfrage überträgt eine rohe 4-KB (oder 64-KB)-Seite pro Anfrage.

Seiten-Replikation / Edge-Sync

Diese betrachten Objektspeicher als Quelle der Wahrheit und replizieren einzelne Seiten oder Änderungssätze, was Teilreplikate und Offline-First / Edge-Deployments ermöglicht.

  • Graft (orbitinghail/graft): Eine transaktionale Speicher-Engine für faule, partielle, stark konsistente Replikation über S3. Die libgraft-SQLite-Erweiterung implementiert ein VFS, das 4-KB-Seiten über Graft-Volumes liest und schreibt. Verwendet gerahmte Zstd-Kompression und Splinter-basierte Änderungssätze. Architektonisch nächster Cousin von turbolite im Bereich „Seiten replizieren, nicht WAL-Frames“, mit Fokus auf Multi-Writer-Edge-Sync statt Kaltleselatenz.
  • mvsqlite: Seiten werden in FoundationDB als inhaltsadressierte KV-Paare gespeichert. Vollständiges MVCC mit Time-Travel zu beliebigen Snapshots, XOR+Zstd-Delta-Kodierung zwischen Seitenversionen. Die ausgefeilteste Speicher-Engine in diesem Bereich, erfordert jedoch FoundationDB, nicht S3.

Replikation und Backup nach S3

Diese replizieren lokale Schreibvorgänge zur Sicherung oder Wiederherstellung nach S3.

  • Litestream: Überträgt kontinuierlich WAL-Frames an S3. Der Goldstandard für SQLite-Backups. Ein neueres Litestream VFS kann Lesevorgänge aus S3 unter Verwendung von Range-Anfragen auf LTX-Dateien mit einem LRU-Cache und Seitenindex bedienen – architektonisch das dem Lesepfad von turbolite am nächsten kommende, aber schreibgeschützt und an das Litestream-Replikationsformat gebunden.
  • LiteFS: Fly.io’s FUSE-basiertes Primary/Replica-System. Erfasst Seitenänderungssätze und streamt sie an Replicas. Löst Verfügbarkeit, nicht Speicherung.
  • Verneuil: Teilt die Datenbank in 64-KB-Blöcke mit Zstd-Kompression und einer Manifestdatei auf, repliziert asynchron nach S3. Das Block+Manifest-Modell ähnelt turbolites Seitengruppen + Manifest, aber Verneuil ist ein Replikationstool – Sie fragen die lokale Festplatte ab, nicht S3.
  • libSQL/sqld (von Turso): Fork von SQLite mit einer virtuellen WAL-Schnittstelle. Der „Bottomless“-Modus überträgt WAL-Frames an S3. Abfragen sind lokal; S3 dient zur Wiederherstellung.

Benutzerdefinierte Speicher-Engines

  • sqlite-s3vfs: Jede SQLite-Seite wird als separates S3-Objekt gespeichert. Ermöglicht Schreibvorgänge, aber mit einem PUT pro Seite, was $0,02 pro 4096 Seiten kostet, verglichen mit turbolites $0,000005 für denselben Batch (bei 64-KB-Seitenstandard). Siehe Benchmark-Tabelle oben für den Vergleich der Kaltabfragelatenz; turbolite ist auf demselben Datensatz 7,5–263× schneller.
  • wa-sqlite-s3vfs: TypeScript / Browser-Port von sqlite-s3vfs für wa-sqlite. Gleiches Modell (ein Objekt pro Seite), angepasst an WASM / clientseitige Nutzung.

Kompression

  • sqlite_zstd_vfs: Speichert komprimierte Seiten als Zeilen in einer äußeren „Wrapper“-Datenbank. Zstd mit Wörterbuchtraining. Kombiniert mit sqlite_web_vfs für komprimierte Range-Request-Lesevorgänge über HTTP. Die Kombination von sqlite_web_vfs + sqlite_zstd_vfs ist wahrscheinlich das derzeit nächstgelegene Pendant zu turbolites Lesepfad, ist aber schreibgeschützt und fasst Seiten nicht in Gruppen zusammen.
  • SQLCipher: Seitenweise AES-256-Verschlüsselung für lokales SQLite. Kein Remote-Speicher.

Wo sich turbolite unterscheidet

Benchmarking

Alle Benchmarks befinden sich in benchmark/. Siehe benchmark/README.md für Bereitstellungsszenarien (lokal, Fly.io, EC2).

Das tiered-bench-Binary generiert einen Social-Media-Datensatz (Benutzer, Beiträge, Likes, Freundschaften) und benchmarkt Abfragen auf jeder Cache-Ebene gegen S3.

Ein separates benchmark/bench_s3vfs.py-Skript führt dieselben Abfragen gegen sqlite-s3vfs für einen direkten Vergleich aus. Es wird über benchmark/fly-s3vfs.toml bereitgestellt und verwendet den gleichen deterministischen Datensatzgenerator wie tiered-bench.```bash

Basic benchmark: 100K posts, default settings

TIERED_TEST_BUCKET=my-bucket AWS_ENDPOINT_URL=https://t3.storage.dev
cargo run --features zstd,cloud --bin tiered-bench --release --
--sizes 100000

1M posts, 8 prefetch threads, only interior-level point queries

cargo run --features zstd,cloud --bin tiered-bench --release --
--sizes 1000000 --prefetch-threads 8 --queries post --modes interior

Quick local VFS comparison (no S3 needed)

cargo run --example quick-bench --features encryption --release

root@kitploit:~
Key flags: `--sizes` (Zeilenanzahlen), `--ppg` (Seiten pro Gruppe), `--prefetch-threads`, `--prefetch-search` (SEARCH-Plan), `--prefetch-lookup` (Lookup-Plan), `--grouping` (positional oder B-Baum), `--queries` (Beitrag/Profil/Wer-mag/Mutual), `--modes` (keine/inner/Index/Daten), `--skip-verify` (COUNT(*) auf kleinen Maschinen überspringen), `--iterations`, `--plan-aware` (Look-Ahead-Prefetch aktivieren), `--matrix` (Sweep-Planpaare). Pro Abfrage-Pläne: `--post-prefetch`/`--post-lookup`, `--profile-prefetch`/`--profile-lookup`, usw. (Search und Lookup sind pro Abfrage unabhängig).```bash
# Matrix mode: test 10 schedule pairs x 6 queries at cold level
cargo run --features zstd,cloud --bin tiered-bench --release -- \
    --sizes 1000000 --import auto --plan-aware --matrix --iterations 10

# Tune schedules for your own database and queries
cargo run --features zstd,cloud --bin tiered-tune --release -- \
    --prefix "databases/my-db" \
    --query "SELECT * FROM users WHERE id = ?1" --param 42 \
    --plan-aware --iterations 10

Testen```bash

cargo test --features zstd # local VFS tests cargo test --features zstd,cloud # + S3 integration tests cargo test --features zstd,encryption # + encryption tests

root@kitploit:~
## Anmerkungen

turbolite hieß früher `sqlite-compress-encrypt-vfs`, auch bekannt als `sqlces`.

### Details zum Sicherheitsmodell

S3-Daten verwenden AES-256-GCM mit eindeutigen zufälligen Nonces pro Frame (authentifiziert, manipulationserkennend). Lokale Dateien verwenden AES-256-CTR mit deterministischen Nonces (Seitennummer / Byte-Offset) und bieten Vertraulichkeit gegenüber Angreifern, die auf ruhende Festplatten zugreifen. Die deterministischen Nonces von CTR bedeuten, dass Angreifer mit mehreren Snapshots XOR von Klartexten an wiederverwendeten Offsets wiederherstellen könnten, was dem Kompromiss der eigenen SEE-Erweiterung von SQLite entspricht. Der lokale Cache ist flüchtig und aus S3 reproduzierbar.

## Lizenz

Apache-2.0
Tool herunterladen
AbfrageTypKalt (S3 Express)Kalt (Tigris)
Beitrag + BenutzerPunktabfrage + Join86ms172ms
ProfilMulti-Table-Join (5 JOINs)251ms479ms
Wer hat gelikedIndex-Suche + Join206ms302ms
Gemeinsame FreundeMulti-Search-Join19ms49ms
Indizierter Filtergedeckter Index-Scan79ms88ms
Vollscan + Filtervollständiger Tabellenscan476ms532ms
Cache-EbeneWas gecached istWas von S3 geholt wirdWann das passiert
nonenichtsallesFrischer Start, leerer Cache
interiorinnere B‑Tree-SeitenIndex + DatenseitenErste Abfrage nach Verbindungsaufbau
indexinnere + Indexseitennur DatenseitenNormaler turbolite-Betrieb
dataallesnichtsÄquivalent zu lokalem SQLite
OperationSQLiteturboliteOverhead
Punktabfrage145K/s73K/s2,0x
Bereichsscan8,8K/s8,3K/sParität
Vollständiger Tabellenscan56/s60/sParität
INSERT19K/s23K/sParität
UPDATE nach PK40K/s27K/s1,5x
Batch-INSERT (in Txn)685K/s740K/sParität
S3-EinschränkungAuswirkung
Hin- und Rückrufe sind langsamAnzahl der Anfragen minimieren. Schreibvorgänge bündeln, aggressiv vorauslesen.
Bandbreite ist ein EngpassBandbreitenauslastung maximieren.
PUTs und GETs werden pro Vorgang berechnetEin 64-KB-GET kostet genauso viel wie ein 16-MB-GET. Anfragenanzahl optimieren, nicht Byte-Effizienz.
Objekte sind unveränderlichNiemals an Ort und Stelle aktualisieren. Neue Versionen schreiben, einen Zeiger austauschen. Keine Korruption durch partielle Schreibvorgänge.
Speicher ist günstigNicht auf Platz optimieren. Überprovisionieren, alte Versionen behalten, später von der GC bereinigen lassen.
WorkloadKonfigurationGrund
Gemischte OLTPStandardeinstellungenPlan-aware bewältigt Scans, Suchplan erwärmt Indizes, Lookup-Plan bleibt konservativ.
Punktlastig (Agent-DBs)prefetch.lookup: vec![0.0, 0.0, 0.0]Lookups benötigen fast nie Prefetch.
Scan-lastige Analysenprefetch.search: vec![0.5, 0.5], prefetch.query_plan: trueAggressive Such-Warmup plus plan-aware Bulk-Prefetch.
Konservativ (bursty Serverless)prefetch.search: vec![0.1, 0.2, 0.3], prefetch.lookup: vec![0.0, 0.0, 0.1]Minimales Prefetch-Rauschen.
BackendGET-LatenzBester Punkt-LookupBestes ProfilOptimierungsgewinn
S3 Express~4ms74ms (aus/aus: 96ms)188ms (aus/aus: 212ms)5-23% über kein Prefetch
Tigris~25ms192ms (aus/aus: 231ms)524ms (aus/aus: 616ms)8-34% über kein Prefetch
📛 Name🔑 API-Schlüssel erforderlich?🔄 Ratenbegrenzung?❓ Warum?
pimeyes✅ Ja✅ Ja (25 req/min)Hervorragende Gesichtssuchergebnisse, durchweg besser als Alternativen. Möglicherweise eines der besten Ergebnisse für Frauen-/Männersuchen.
whatsmyname❌ Nein❌ NeinBeste Benutzernamensuche, von Strafverfolgungsbehörden genutzt. Abfragen sind schnell und einfach.
mastodon❌ Nein❌ NeinDezentrales soziales Netzwerk. Große Gemeinschaft mit allgemein verfügbaren Daten.
yandex✅ Ja❌ NeinGute Gesichtssuchergebnisse.
google✅ Ja✅ Ja (100 req/Tag)Robust und genau.
facebook❌ Nein❌ NeinRiesiges soziales Netzwerk mit großen Mengen öffentlicher Daten.
instagram✅ Ja✅ Ja (200 req/Stunde)Hervorragende Quelle für die Sammlung von Personeninformationen.
twitter✅ Ja✅ Ja (15 req/15 Min)Hervorragend für die Suche nach Beiträgen und Benutzern.
turboliteRohdatei-Bereichs-GETsLitestream VFSsqlite_web_vfs + zstd_vfsmvsqliteGraftsqlite-s3vfs
Liest von S3suchbare Bereichs-GETs auf komprimierten SeitengruppenBereichs-GETs auf rohen SeitenBereichs-GETs auf LTX-DateienBereichs-GETs auf komprimierter äußerer DBKV-Nachschlagevorgänge auf FoundationDBfaules Abrufen von 4-KB-Seiten / Änderungssätzenein GetObject pro Seite
Schreibt nach S3Checkpoint (ein PUT pro Gruppe)neinneinneinja (MVCC)ja (async changeset replication)ein PUT pro Seite
Kompressionsuchbares Multi-Frame-ZstdkeinekeineZstd (verschachtelt DB)Zstd-Delta-Kodierunggerahmtes Zstdkeine
VerschlüsselungAES-256-GCM pro Seitekeinekeinekeinekeinenicht aufgeführtkeine
PrefetchingVorausschau + Hop-Plankeines oder einfaches Read-AheadLRU-Cacheadaptive BündelungClient-Pufferfaul / bedarfsgesteuertkeines
Innenknoten-Optimierungerkannt, fixiert, separat gebündeltkeineSeitenindex aus LTX-Traileroptionale .dbi-Dateikeinenicht aufgeführtkeine
Bytes pro Punktabfrage (Cache: Index)~100 KB (ein komprimierter Frame)4–64 KB (eine rohe Seite)variiertvariiertvariiert4 KB (eine Seite)4 KB (eine Seite)
Schreibkosten pro 4096 Seiten~$0,000005 (ein PUT)n/an/an/aFoundationDB-Operationengebündelte Änderungssätze~$0,02 (4096 PUTs)