GoSuda

gosuda.org/sqlite3: En drop-in database/sql-driver som kjører innebygd C SQLite over delt minne

By Lemon Mint
views ...

gosuda.org/sqlite3 er en CGO_ENABLED=0 database/sql-driver for Go som kjører den overordnede C SQLite 3.54.1-amalgameringen inne i en isolert arbeiderprosess over HQQ delt-minne-ringer. Den registreres under standarddrivernavnet "sqlite3", støtter DSN-spørringsparametere for github.com/mattn/go-sqlite3, innebygger SLSA-attesterte forhåndsbygde serverbinærfiler for seks operativsystemer og arkitekturmål, og bruker forespørsels-pipelining med 64 KiB satsvis forhåndshenting for å overgå in-prosess cgo på spørringer med flere rader.

1go get gosuda.org/sqlite3@v0.3.3540100

Hvorfor en ny Go SQLite-driver?

SQLite er en av de mest distribuerte programvarekomponentene i verden, men å bruke den fra Go har historisk sett tvunget ingeniører inn i et ukomfortabelt valg mellom byggeenkelhet og motorlojalitet.

På den ene siden står github.com/mattn/go-sqlite3, den langvarige standarden i fellesskapet. Den pakker inn den offisielle C SQLite-amalgameringen direkte via cgo. Du får den ekte C-spørringsplanleggeren, full WAL-sameksistenssemantikk og forutsigbar C-minnehåndtering. Kostnaden er CGO_ENABLED=1: lokale bygg og CI-pipeliner sakter ned, minimale scratch- eller distroless-beholderavbildninger krever matchende C-kjøretidsbiblioteker, krysskompilering til et annet operativsystem eller CPU-arkitektur krever en fullstendig C-krysseverktøykjede, og enhver minnekorrupsjon i C deler Go-prosessens adresseområde.

På den andre siden finnes ren-Go SQLite-porter generert ved mekanisk å oversette C-amalgameringen til Go-kildekode over en emulert C-kjøretid (libc). De gjenoppretter CGO_ENABLED=0, noe som gjør bygging trivielt. Under vedvarende arbeidsmengder øker imidlertid den oversatte C-hopen og de radvise Go-allokeringene søppelinnsamlingstrykket, og subtile grensetilfeller innen låsing, opptatt-tidsavbrudd eller setningsavbrudd kan avvike fra native C-bygg.

Vi bygget gosuda.org/sqlite3 slik at Go-applikasjoner ikke behøver å velge mellom CGO_ENABLED=0 og den ekte C SQLite-motoren. Ved å kompilere den offisielle SQLite 3.54.1 C-amalgameringen til en dedikert delt-minne-server (hqq-sqlite-server) og kommunisere over gosuda.org/sqlite3, kompilerer din Go-binærfil med CGO_ENABLED=0 samtidig som den utfører umodifisert, -O3-optimalisert C SQLite på tvers av en prosessgrense.

Null-konfigurasjonsutførelse: Fra go get til sql.Open

Å erstatte github.com/mattn/go-sqlite3 i en eksisterende Go-kildebase krever at man kun endrer den blanke importeringsstien. Driveren registreres som "sqlite3" (og "hqq-sqlite" for eksplisitt valg) og aksepterer de samme DSN-spørringsstrengene:

 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 arbeiderbinærfiler

Et naturlig spørsmål når man hører at et bibliotek bruker en C-arbeiderprosess, er: "Må jeg installere eller distribuere en separat C-binærfil sammen med mitt Go-program?"

Nei. gosuda.org/sqlite3 innebygger forhåndsbygde hqq-sqlite-server-binærfiler for alle seks store mål direkte inne i plattformtaggede Go-filer (internal/embedded/embed_<goos>_<goarch>.go). Fordi Gos byggerestriksjoner kun kompilerer filen som samsvarer med din målsatte GOOS og GOARCH, vil krysskompilering av GOOS=linux GOARCH=arm64 go build automatisk bygge inn kun den omtrent 1,4 MiB store linux/arm64-serverbinærfilen.

Mål-operativsystemMålarkitektur (GOARCH)Innebygd binærfilByggeverktøykjede og proveniens
Linuxamd64, arm64hqq-sqlite-server_linux_<arch>GCC -O3, SLSA Build L3 Provenance
macOSarm64, amd64hqq-sqlite-server_darwin_<arch>AppleClang -O3, SLSA Build L3 Provenance
Windowsamd64hqq-sqlite-server_windows_amd64.exeMSVC -O2, SLSA Build L3 Provenance
Windowsarm64hqq-sqlite-server_windows_arm64.exezig cc -target aarch64-windows-gnu -O3, SLSA L3

Når sql.Open etablerer sin første tilkobling, materialiserer autolaunch.go den innebygde binærfilen til brukerens buffermappe ($XDG_CACHE_HOME/hqq/bin/<sha256>/), verifiserer dens kryptografiske SHA-256-sammendrag mot det festede ReleaseHashes-kartet i hashes.go via gosuda.org/hqq/secureexec, og starter arbeideren med --parent-stdin slik at levetiden er strengt bundet til din Go-prosess.

Rentroms mattn/go-sqlite3-DSN-kompatibilitet

For å fungere som en ekte erstatning rett ut av esken implementerer DSN-parseren (dsn.go) en rentromsreplika av tilkoblings- og pragma-bryterne som brukes på tvers av mattn/go-sqlite3-kildebaser:

DSN-spørringsparameterAksepterte verdierSQLite-effekt
_busy_timeout / _timeoutHeltalls millisekunder (f.eks. 5000)Kaller sqlite3_busy_timeout(db, ms) ved øktåpning.
_journal_mode / _journalDELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFFUtfører PRAGMA journal_mode = <mode>.
_txlockimmediate, exclusive, deferredVelger BEGIN IMMEDIATE, BEGIN EXCLUSIVE eller BEGIN DEFERRED for db.BeginTx.
_foreign_keys / _fkon, true, 1, off, false, 0Utfører PRAGMA foreign_keys = ON/OFF.
_synchronous / _sync0/OFF, 1/NORMAL, 2/FULL, 3/EXTRAUtfører PRAGMA synchronous = <level>.
_cache_sizeHeltallssider eller -KiB (f.eks. -20000)Utfører PRAGMA cache_size = <val>.
_locauto eller IANA-tidssone (f.eks. UTC, Asia/Seoul)Parser DATETIME/TIMESTAMP-tekstkolonner til *time.Time i målokasjonen.
modero, rw, rwc, memorySetter SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE eller SQLITE_OPEN_MEMORY.
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuumBooleanske verdier eller heltallsmoduserBruker den tilhørende øktens PRAGMA ved tilkoblingsinitialisering.

Gjøre prosessisoleringsfordeler om til gjennomgyningsgevinst

Ved første øyekast virker det som om det å kjøre SQLite i en separat prosess må være tregere enn å kalle sqlite3_step i-prosess via cgo. I praksis skjer det motsatte så snart en spørring returnerer mer enn en enkelt rad.

Den skjulte kostnaden ved radvise cgo-kall

I en konvensjonell cgo SQLite-driver driver database/sql iterering én rad og én kolonne av gangen. Å lese 1 000 rader med 4 kolonner utløser:

  • 1 000 cgo-kall til sqlite3_step
  • 4 000 cgo-kall til sqlite3_column_type og sqlite3_column_int64 / sqlite3_column_text
  • Tusenvis av individuelle grensekryssinger mellom Go-planleggeren og C-stakken

Forespørsels-pipelining og 64 KiB satsvis forhåndshenting

gosuda.org/sqlite3 eliminerer radvise grensekryssinger ved å omdesigne trådløsprotokollen 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. Pipeliner i 1 operasjon:
 4   ResetAsync + BindValuesAsync
 5   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
 6                                                    løkke sqlite3_step() opp til 128 rader
 7                                          <=======  Sats 1 (rader 1..128 pakket i 64 KiB-plass)
 82. Utløs forhåndshenting umiddelbart:
 9   FetchRowsAsync(128) (Sats 2)           =======>  løkke sqlite3_step() for rader 129..256
10   Mens C utfører trinn for Sats 2, skanner
11   Go-kalleren rader 1..128 fra Sats 1!   <=======  Sats 2 klar i delt minne
  1. Enkeltoperasjons-spørrings-pipelining (stmt.go): Når QueryContext eller Stmt.QueryContext kjører en skrivebeskyttet setning, sender s.query() inn reset, bind_values, stmt_info (ved første utførelse) og fetch_rows asynkront i én enkelt delt-minne-operasjon. Alle fire operasjonene utføres etter hverandre på C-arbeideren og returnerer i én rundtur.
  2. Tette C-satsløkker og asynkron forhåndshenting (rows.go): Inne i fetch_rows (sqlite3_hooks.c) utfører C-arbeideren trinnvise sqlite3_step(stmt) i en native C-løkke, og koder opp til FetchSize-rader (standard 128) direkte inn i svarsplassen for delt minne på 64 KiB til satsgrensen er nådd, plassen er full, eller SQLITE_DONE oppstår. Så snart rows.ingest() dekoder Sats 1, utløser den FetchRowsAsync for Sats 2 før den returnerer den første raden til kalleren. Mens Go-koden din skanner Sats 1, utfører C-arbeideren allerede trinn for Sats 2 på en annen CPU-kjernen.

På Apple M4 (darwin/arm64) viser en sammenligning av gosuda.org/sqlite3 (CGO_ENABLED=0) mot github.com/mattn/go-sqlite3 (CGO_ENABLED=1) på den samme SQLite-databasen effekten av satsvis behandling og pipelining:

Arbeidsmengdegosuda.org/sqlite3 (CGO_ENABLED=0)mattn/go-sqlite3 (CGO_ENABLED=1)Relativ hastighet
Punktutvalg (QueryRow med primærnøkkel)3.52 µs/op (9 B, 1 allokering)3.56 µs/op (232 B, 11 allokeringer)1,01x (paritet, 11x færre allokeringer)
100-raders skanning (rows.Next)20.41 µs/op (30.5 KB, 211 allokeringer)36.34 µs/op (21.8 KB, 309 allokeringer)1,78x raskere
1 000-raders skanning (rows.Next)125.92 µs/op (296 KB, 2 030 allokeringer)349.10 µs/op (216 KB, 3 009 allokeringer)2,77x raskere
Forberedt innsetting (Autocommit)5.33 µs/op (0 B, 0 allokeringer)5.88 µs/op (120 B, 5 allokeringer)1,10x raskere (0 allokeringer)
100-raders transaksjonssats189.99 µs/op (16 B, 1 allokering)191.50 µs/op (12.1 KB, 503 allokeringer)1,01x (500x færre allokeringer)

Avbrudd, stisandkasse og gjenoppretting av tilkoblingsbasseng

Å kjøre SQLite i en isolert arbeider styrker også hvordan driveren håndterer tidsavbrudd, ubetrodd SQL og filsystemgrenser:

  • Deterministisk kontekstavbrudd (conn.go og errors.go): Hvert driverkall aktiverer en lettvekts context.AfterFunc-vakt (cn.guard). Hvis kallerens context.Context avbrytes eller tidsfristen utløper mens en langsom rekursiv spørring eller låsevente kjører på serveren, markerer vakten tilkoblingen som feilaktig, avslutter den lokale øktkoblingen og sender en utenom-båndet Cancel()-ramme. C-arbeideren anroper sqlite3_interrupt(db) umiddelbart, og frigjør sqlite3_step, mens Go-driveren tilordner den resulterende SQLITE_INTERRUPT-statusen til context.Canceled eller context.DeadlineExceeded og instruerer database/sql til å erstatte tilkoblingen i bassenget.
  • Katalogsandkasse (sqlsrv_pathrules.c): C-arbeideren håndhever --allow-dir-stiregler ved hvert open_v2-kall, oppløser symbolsk lenker og avviser relativ ..-navigasjon utenfor den tillatte katalogen. sqlite3_load_extension og SQLITE_LIMIT_ATTACHED er deaktivert som standard slik at SQL-injeksjon ikke kan unnslippe databankatalogen eller laste inn vilkårlige delte objekter.

Arkivstruktur

Arkivet er strukturert slik at toppnivåpakken tilbyr database/sql-driveren rett ut av esken, mens launcher/ og sqlite3/ forblir tilgjengelige for avanserte tilpassede serverdistribusjoner:

 1gosuda.org/sqlite3
 2├── driver.go / api.go         # database/sql-driverregistrering ("sqlite3") og Connector-API
 3├── dsn.go                     # Rentroms mattn/go-sqlite3 DSN-parser og pragma-konfigurasjon
 4├── conn.go / tx.go            # Øktlevetid, kontekstvakt og transaksjonstilstandsmaskin
 5├── stmt.go / rows.go          # Enkeltoperasjons-spørrings-pipelining og 64 KiB satsvis forhåndshenting
 6├── errors.go                  # Typede SQLite primære/utvidede feilkoder og sentinel-samsvar
 7├── autolaunch.go / hashes.go  # Null-konfigurasjons innebygd servermaterialisering og SHA-256-festing
 8├── internal/embedded/         # Plattformtaggede //go:embed-binærfiler (linux, darwin, windows)
 9├── launcher/                  # Konfigurerbar prosessovervåker for tilpassede serverdistribusjoner
10└── sqlite3/                   # Generert hqq-bindgen Go-klient og C11 SQLite-serverkilde
11    └── server/                # C11-serverimplementering (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