gosuda.org/sqlite3: CGO_ENABLED=0 database/sql Driver over Shared-Memory IPC

gosuda.org/sqlite3 er en database/sql-driver for Go med CGO_ENABLED=0 som kjører den offisielle C-SQLite 3.54.1-amalgamasjonen i en isolert arbeidsprosess via HQQ-delt minneringbuffere. Den registreres under det standardiserte driver-navnet "sqlite3", støtter DSN-spørringsparametere fra github.com/mattn/go-sqlite3, inkluderer SLSA-attesterte, forhåndskompilerte serverbinærfiler for seks målplattformer for operativsystem og arkitektur, og bruker forespørselspipelining med forhåndshenting av batcher på 64 KiB for å yte bedre enn in-process cgo ved spørringer som returnerer flere rader.
1go get gosuda.org/sqlite3@v0.3.3540100
Hvorfor en ny Go-driver for SQLite?
SQLite er en av de mest utbredte programvarekomponentene i verden, men bruk av den fra Go har tradisjonelt tvunget utviklere til et ubehagelig valg mellom byggeenkelhet og trofasthet mot selve databasemotoren.
På den ene siden står github.com/mattn/go-sqlite3, den langvarige standarden i fellesskapet. Den omslutter den offisielle C-SQLite-amalgamasjonen direkte via cgo. Man får den reelle C-spørringsplanleggeren, full WAL-samtidighetssemantikk og forutsigbar minnehåndtering i C. Prisen er CGO_ENABLED=1: lokale bygg og CI-pipelines blir tregere, minimale scratch- eller distroless-containerbilder krever tilsvarende C-kjøretidsbiblioteker, krysskompilering til et annet operativsystem eller en annen CPU-arkitektur krever en fullstendig C-kryssverktøykjede, og eventuell minnekorrupsjon i C deler adresserommet med Go-prosessen.
På den andre siden finnes rene Go-portinger av SQLite som er generert ved mekanisk oversettelse av C-amalgamasjonen til Go-kildekode over et emulert C-kjøretidsmiljø (libc). De gjenoppretter CGO_ENABLED=0, noe som gjør byggene trivielle. Under vedvarende arbeidslast øker imidlertid den oversatte C-heapen og de per-rad-allokeringene i Go belastningen på søppelinnsamlingen, og subtile tilfeller knyttet til låsing, busy-timeouts eller avbrytelse av setninger kan avvike fra native C-bygg.
Vi bygde gosuda.org/sqlite3 for at Go-applikasjoner ikke skal måtte velge mellom CGO_ENABLED=0 og den reelle C-SQLite-motoren. Ved å kompilere den offisielle SQLite 3.54.1 C-amalgamasjonen til en dedikert server med delt minne (hqq-sqlite-server) og kommunisere over gosuda.org/hqq, kompileres din Go-binærfil med CGO_ENABLED=0, samtidig som den kjører uendret, -O3-optimalisert C-SQLite på tvers av en prosessgrense.
Konfigurasjonsfri kjøring: Fra go get til sql.Open
Å erstatte github.com/mattn/go-sqlite3 i en eksisterende Go-kodebase krever kun endring av den blanke importstien. Driveren registreres som "sqlite3" (og "hqq-sqlite" for eksplisitt valg) og godtar de samme DSN-spørrestrengene:
1package main
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "log"
8
9 _ "gosuda.org/sqlite3"
10)
11
12func main() {
13 dsn := "file:app.db?_journal_mode=WAL&_busy_timeout=5000&_txlock=immediate&_foreign_keys=on"
14 db, err := sql.Open("sqlite3", dsn)
15 if err != nil {
16 log.Fatal(err)
17 }
18 defer db.Close()
19
20 ctx := context.Background()
21 if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`); err != nil {
22 log.Fatal(err)
23 }
24 if _, err := db.ExecContext(ctx, `INSERT INTO users (name) VALUES (?)`, "Ada Lovelace"); err != nil {
25 log.Fatal(err)
26 }
27
28 var id int64
29 var name string
30 if err := db.QueryRowContext(ctx, `SELECT id, name FROM users WHERE name = ?`, "Ada Lovelace").Scan(&id, &name); err != nil {
31 log.Fatal(err)
32 }
33 fmt.Printf("user #%d: %s\n", id, name)
34}
Innebygde SLSA-attesterte arbeidsbinærfiler
Et naturlig spørsmål når man hører at et bibliotek bruker en C-arbeidsprosess, er: "Må jeg installere eller distribuere en separat C-binærfil sammen med Go-programmet mitt?"
Nei. gosuda.org/sqlite3 inkluderer forhåndskompilerte hqq-sqlite-server-binærfiler for alle seks store målplattformer direkte i plattformmerkede Go-filer (internal/embedded/embed_<goos>_<goarch>.go). Fordi Gos byggebegrensninger kun kompilerer filen som samsvarer med målet ditt GOOS og GOARCH, vil krysskompilering med GOOS=linux GOARCH=arm64 go build automatisk kun inkludere den ca. 1,4 MiB store linux/arm64-serverbinærfilen.
| Målsystem | Målarkitektur (GOARCH) | Innebygd binærfil | Byggeverktøy og proveniens |
|---|---|---|---|
| Linux | amd64, arm64 | hqq-sqlite-server_linux_<arch> | GCC -O3, SLSA Build L3-proveniens |
| macOS | arm64, amd64 | hqq-sqlite-server_darwin_<arch> | AppleClang -O3, SLSA Build L3-proveniens |
| Windows | amd64 | hqq-sqlite-server_windows_amd64.exe | MSVC -O2, SLSA Build L3-proveniens |
| Windows | arm64 | hqq-sqlite-server_windows_arm64.exe | zig cc -target aarch64-windows-gnu -O3, SLSA L3 |
Når sql.Open etablerer sin første forbindelse, materialiserer autolaunch.go den innebygde binærfilen i brukerens cachekatalog ($XDG_CACHE_HOME/hqq/bin/<sha256>/), verifiserer dens kryptografiske SHA-256-sammendrag mot det fastlåste ReleaseHashes-kartet i hashes.go via gosuda.org/hqq/secureexec, og starter arbeidsprosessen med --parent-stdin slik at levetiden er strengt knyttet til Go-prosessen din.
Ren-rom-kompatibilitet med DSN for mattn/go-sqlite3
For å fungere som en reell drop-in-erstatning implementerer DSN-parseren (dsn.go) en ren-rom-replika av tilkoblings- og pragma-innstillingene som brukes på tvers av mattn/go-sqlite3-kodebaser:
| DSN-spørringsparameter | Godtatte verdier | SQLite-effekt |
|---|---|---|
_busy_timeout / _timeout | Heltall i millisekunder (f.eks. 5000) | Kaller sqlite3_busy_timeout(db, ms) ved åpning av økt. |
_journal_mode / _journal | DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF | Utfører PRAGMA journal_mode = <mode>. |
_txlock | immediate, exclusive, deferred | Velger BEGIN IMMEDIATE, BEGIN EXCLUSIVE eller BEGIN DEFERRED for db.BeginTx. |
_foreign_keys / _fk | on, true, 1, off, false, 0 | Utfører PRAGMA foreign_keys = ON/OFF. |
_synchronous / _sync | 0/OFF, 1/NORMAL, 2/FULL, 3/EXTRA | Utfører PRAGMA synchronous = <level>. |
_cache_size | Heltall i sider eller -KiB (f.eks. -20000) | Utfører PRAGMA cache_size = <val>. |
_loc | auto eller IANA-tidssone (f.eks. UTC, Asia/Seoul) | Parser DATETIME/TIMESTAMP-tekstkolonner til *time.Time i målplasseringen. |
mode | ro, rw, rwc, memory | Setter SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE eller SQLITE_OPEN_MEMORY. |
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuum | Boolske verdier eller modusheltall | Anvender tilsvarende økt-PRAGMA ved initialisering av forbindelsen. |
Å gjøre prosessisolasjon til en gjennomstrømningsfordel
Ved første øyekast kan det virke som om kjøring av SQLite i en egen prosess må være tregere enn å kalle sqlite3_step in-process via cgo. I praksis skjer det motsatte så snart en spørring returnerer mer enn én rad.
De skjulte kostnadene ved rad-for-rad-kall i cgo
I en konvensjonell cgo-driver for SQLite driver database/sql iterasjonen én rad og én kolonne om gangen. Lesing av 1 000 rader med 4 kolonner utløser:
- 1 000
cgo-kall tilsqlite3_step - 4 000
cgo-kall tilsqlite3_column_typeogsqlite3_column_int64/sqlite3_column_text - Tusenvis av enkeltstående grensekryssinger mellom Go-skjedulereren og C-stakken
Forespørselspipelining og forhåndshenting av batcher på 64 KiB
gosuda.org/sqlite3 eliminerer grensekryssinger per rad ved å redesigne wire-protokollen rundt to mekanismer i stmt.go, rows.go og sqlite3/server/sqlite3_hooks.c:
1Go-klient (stmt.go / rows.go) C11-arbeider (sqlite3_hooks.c)
2----------------------------- ------------------------------
31. Pipeline i 1 flyt:
4 ResetAsync + BindValuesAsync
5 + StmtInfoAsync + FetchRowsAsync(128) =======> reset -> bind -> stmt_info ->
6 løkke over sqlite3_step() opptil 128 rader
7 <======= Batch 1 (rad 1..128 pakket i 64 KiB-slot)
82. Utløs forhåndshenting umiddelbart:
9 FetchRowsAsync(128) (Batch 2) =======> løkke over sqlite3_step() for rad 129..256
10 Mens C steger Batch 2, skanner Go-kalleren
11 rad 1..128 fra Batch 1! <======= Batch 2 klar i delt minne
- Pipelining av spørringer i én flyt (
stmt.go): NårQueryContextellerStmt.QueryContextkjører en skrivebeskyttet setning, senders.query()reset,bind_values,stmt_info(ved første kjøring) ogfetch_rowsasynkront i én enkelt flyt i delt minne. Alle fire operasjonene kjøres etter hverandre på C-arbeideren og returneres i én rundereise. - Stramme C-batchløkker og asynkron forhåndshenting (
rows.go): Inne ifetch_rows(sqlite3_hooks.c) kjører C-arbeiderensqlite3_step(stmt)i en nativ C-løkke og koder opptilFetchSizerader (standard 128) direkte inn i responsslotet i delt minne på 64 KiB, inntil batchgrensen er nådd, slotet er fullt ellerSQLITE_DONEinntreffer. Så snartrows.ingest()har dekodet Batch 1, utløser denFetchRowsAsyncfor Batch 2 før den returnerer første rad til kalleren. Mens Go-koden din skanner Batch 1, steger C-arbeideren allerede Batch 2 på en annen CPU-kjerne.
På Apple M4 (darwin/arm64) viser sammenligningen mellom gosuda.org/sqlite3 (CGO_ENABLED=0) og github.com/mattn/go-sqlite3 (CGO_ENABLED=1) på samme SQLite-database effekten av batching og pipelining:
| Arbeidslast | gosuda.org/sqlite3 (CGO_ENABLED=0) | mattn/go-sqlite3 (CGO_ENABLED=1) | Relativ hastighet |
|---|---|---|---|
Punktvalg (QueryRow på PK) | 3.52 µs/op (9 B, 1 alloc) | 3.56 µs/op (232 B, 11 allok.) | 1,01x (paritet, 11x færre allok.) |
Skanning av 100 rader (rows.Next) | 20.41 µs/op (30.5 KB, 211 allok.) | 36.34 µs/op (21.8 KB, 309 allok.) | 1,78x raskere |
Skanning av 1 000 rader (rows.Next) | 125.92 µs/op (296 KB, 2 030 allok.) | 349.10 µs/op (216 KB, 3 009 allok.) | 2,77x raskere |
| Forberedt innsetting (autocommit) | 5.33 µs/op (0 B, 0 allok.) | 5.88 µs/op (120 B, 5 allok.) | 1,10x raskere (0 allok.) |
| Transaksjonsbatch med 100 rader | 189.99 µs/op (16 B, 1 alloc) | 191.50 µs/op (12.1 KB, 503 allok.) | 1,01x (500x færre allok.) |
Kansellering, stisandboxing og gjenoppretting av tilkoblingspool
Å kjøre SQLite i en isolert arbeider styrker også måten driveren håndterer tidsavbrudd, upålitelig SQL og filsystemgrenser på:
- Deterministisk kansellering av kontekst (
conn.googerrors.go): Hvert driverkall aktiverer en lettvektscontext.AfterFunc-vakt (cn.guard). Dersom kallerenscontext.Contextkanselleres, eller fristen utløper, mens en treg rekursiv spørring eller låsevent kjører på serveren, markerer vakten tilkoblingen som ugyldig, dreper den lokale sesjonsforbindelsen og sender enCancel()-ramme utenfor båndet. C-arbeideren kallersqlite3_interrupt(db)umiddelbart, slik atsqlite3_stepfrigjøres, mens Go-driveren oversetter den resulterendeSQLITE_INTERRUPT-statusen tilcontext.Canceledellercontext.DeadlineExceededog instruererdatabase/sqlom å erstatte tilkoblingen i poolen. - Katalogsandboxing (
sqlsrv_pathrules.c): C-arbeideren håndhever--allow-dir-stiregler ved hvertopen_v2-kall, løser symbolske lenker og avviser relativ..-traversering utenfor det tillatte katalogen.sqlite3_load_extensionogSQLITE_LIMIT_ATTACHEDer deaktivert som standard, slik at SQL-injeksjon ikke kan unnslippe databasekatalogen eller laste inn vilkårlige delte objekter.
Repositoriets struktur
Repositoriet er strukturert slik at pakken på toppnivå leverer den drop-in database/sql-driveren, mens launcher/ og sqlite3/ forblir tilgjengelige for avanserte distribusjoner med egendefinert server:
1gosuda.org/sqlite3
2├── driver.go / api.go # Registrering av database/sql-driver ("sqlite3") og Connector-API
3├── dsn.go # Ren-rom DSN-parser for mattn/go-sqlite3 og pragma-konfigurasjon
4├── conn.go / tx.go # Livssyklus for økt, kontekstvakt og tilstandsmaskin for transaksjoner
5├── stmt.go / rows.go # Pipelining av forespørsler i én flyt og forhåndshenting av batcher på 64 KiB
6├── errors.go # Typede primære og utvidede SQLite-feilkoder og matching av sentinelverdier
7├── autolaunch.go / hashes.go # Konfigurasjonsfri materialisering av innebygd server og SHA-256-fastlåsing
8├── internal/embedded/ # Plattformmerkede //go:embed-binærfiler (linux, darwin, windows)
9├── launcher/ # Konfigurerbar prosessovervåker for egendefinerte serverdistribusjoner
10└── sqlite3/ # Generert hqq-bindgen Go-klient og C11-kildekode for SQLite-server
11 └── server/ # C11-serverimplementasjon (sqlite3_hooks.c, sqlsrv_policy.c)
Kom i gang
Installer modulen med go get og åpne databasen din med sql.Open("sqlite3", dsn):
1go get gosuda.org/sqlite3@v0.3.3540100
- GitHub-repositorium: https://github.com/gosuda/hqq-sqlite3
- DSN- og pragma-referanse: docs/DSN.md
- Arkitektur og wire-protokoll: docs/ARCHITECTURE.md