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

gosuda.org/sqlite3 je ovladač database/sql pro Go s CGO_ENABLED=0, který spouští upstream C SQLite 3.54.1 amalgamaci uvnitř izolovaného pracovního procesu přes sdílené paměťové kruhové fronty HQQ. Registruje se pod standardním názvem ovladače "sqlite3", podporuje parametry DSN dotazu z github.com/mattn/go-sqlite3, obsahuje vložené předkompilované serverové binárky s SLSA atestací pro šest cílových operačních systémů a architektur a využívá pipelining požadavků s předběžným načítáním dávek o velikosti 64 KiB, díky čemuž překonává in-process cgo u dotazů vracejících více řádků.
1go get gosuda.org/sqlite3@v0.3.3540100
Proč další ovladač SQLite pro Go?
SQLite patří mezi nejrozšířenější software na světě, přesto se její používání v Go historicky vynucovalo volbu mezi jednoduchostí sestavení a věrností enginu, která engineery stavěla do nepohodlné situace.
Na jedné straně stojí github.com/mattn/go-sqlite3, dlouhodobý komunitní standard. Přímo obaluje oficiální C amalgamaci SQLite prostřednictvím cgo. Získáváte skutečný C plánovač dotazů, plnou sémantiku souběžnosti WAL a předvídatelnou správu paměti v C. Cenou je CGO_ENABLED=1: lokální sestavení a CI pipeline se zpomalují, minimální scratch nebo distroless kontejnerové obrazy vyžadují odpovídající C běhové knihovny, křížová kompilace pro jiný operační systém nebo architekturu CPU vyžaduje kompletní C křížový toolchain a jakákoli poškozená paměť v C sdílí adresový prostor procesu Go.
Na druhé straně stojí čistě Go porty SQLite, vytvořené mechanickým překladem C amalgamace do zdrojového kódu Go nad emulovaným C runtime (libc). Obnovují CGO_ENABLED=0, což činí sestavení triviálním. Při dlouhodobé zátěži však přeložená C halda a alokace Go pro každý řádek zvyšují tlak na garbage collector a jemné okrajové případy při zamykání, časových limitech busy nebo přerušování příkazů se mohou odchylovat od nativních C sestavení.
Vytvořili jsme gosuda.org/sqlite3, aby Go aplikace nemusely volit mezi CGO_ENABLED=0 a skutečným C enginem SQLite. Kompilací oficiální C amalgamace SQLite 3.54.1 do vyhrazeného serveru se sdílenou pamětí (hqq-sqlite-server) a komunikací přes gosuda.org/hqq se vaše Go binárka sestavuje s CGO_ENABLED=0, zatímco vykonává nezměněnou C SQLite optimalizovanou pomocí -O3 napříč hranicí procesu.
Konfigurace bez nastavení: od go get k sql.Open
Nahrazení github.com/mattn/go-sqlite3 v existující Go kódové základně vyžaduje změnit pouze cestu k prázdnému importu. Ovladač se registruje jako "sqlite3" (a "hqq-sqlite" pro explicitní výběr) a přijímá stejné řetězce DSN:
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}
Vložené pracovní binárky s SLSA atestací
Přirozenou otázkou, když se dozvíte, že knihovna používá pracovní proces v C, je: „Musím vedle svého Go programu instalovat nebo distribuovat samostatnou C binárku?“
Ne. gosuda.org/sqlite3 vkládá předkompilované binárky hqq-sqlite-server pro všech šest hlavních cílů přímo do souborů Go označených podle platformy (internal/embedded/embed_<goos>_<goarch>.go). Protože build constraints Go kompilují pouze soubor odpovídající vašemu cílovému GOOS a GOARCH, křížová kompilace příkazem GOOS=linux GOARCH=arm64 go build automaticky vloží pouze binárku serveru linux/arm64 o velikosti přibližně 1,4 MiB.
| Cílový OS | Cílová architektura (GOARCH) | Vložená binárka | Build toolchain a provenance |
|---|---|---|---|
| Linux | amd64, arm64 | hqq-sqlite-server_linux_<arch> | GCC -O3, SLSA Build L3 Provenance |
| macOS | arm64, amd64 | hqq-sqlite-server_darwin_<arch> | AppleClang -O3, SLSA Build L3 Provenance |
| Windows | amd64 | hqq-sqlite-server_windows_amd64.exe | MSVC -O2, SLSA Build L3 Provenance |
| Windows | arm64 | hqq-sqlite-server_windows_arm64.exe | zig cc -target aarch64-windows-gnu -O3, SLSA L3 |
Když sql.Open naváže své první spojení, autolaunch.go materializuje vloženou binárku do uživatelského cache adresáře ($XDG_CACHE_HOME/hqq/bin/<sha256>/), ověří její kryptografický digest SHA-256 proti připnuté mapě ReleaseHashes v hashes.go prostřednictvím gosuda.org/hqq/secureexec a spustí pracovní proces s parametrem --parent-stdin, takže jeho životnost je přísně svázána s vaším Go procesem.
Kompatibilita DSN s mattn/go-sqlite3 v čistém provedení
Aby mohl fungovat jako skutečná náhrada bez nutnosti úprav, parser DSN (dsn.go) implementuje čistě navržený (clean-room) model parametrů připojení a pragma nastavení používaných napříč kódovými základnami mattn/go-sqlite3:
| Parametr DSN dotazu | Přijímané hodnoty | Účinek v SQLite |
|---|---|---|
_busy_timeout / _timeout | Celé číslo v milisekundách (např. 5000) | Volá sqlite3_busy_timeout(db, ms) při otevření relace. |
_journal_mode / _journal | DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF | Provede PRAGMA journal_mode = <mode>. |
_txlock | immediate, exclusive, deferred | Vybírá BEGIN IMMEDIATE, BEGIN EXCLUSIVE nebo BEGIN DEFERRED pro db.BeginTx. |
_foreign_keys / _fk | on, true, 1, off, false, 0 | Provede PRAGMA foreign_keys = ON/OFF. |
_synchronous / _sync | 0/OFF, 1/NORMAL, 2/FULL, 3/EXTRA | Provede PRAGMA synchronous = <level>. |
_cache_size | Celý počet stránek nebo -KiB (např. -20000) | Provede PRAGMA cache_size = <val>. |
_loc | auto nebo IANA časové pásmo (např. UTC, Asia/Seoul) | Převádí textové sloupce DATETIME/TIMESTAMP na *time.Time v cílové lokalitě. |
mode | ro, rw, rwc, memory | Nastavuje SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE nebo SQLITE_OPEN_MEMORY. |
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuum | Booleovská hodnota nebo celočíselný režim | Aplikuje odpovídající relační PRAGMA při inicializaci připojení. |
Proměna izolace procesů ve výhodu propustnosti
Na první pohled se může zdát, že spouštění SQLite v odděleném procesu musí být pomalejší než volání sqlite3_step in-process přes cgo. V praxi se však jako první ukáže opak, jakmile dotaz vrátí více než jeden řádek.
Skrytá cena volání cgo řádek po řádku
V konvenčním ovladači SQLite založeném na cgo database/sql řídí iteraci po jednom řádku a jednom sloupci. Čtení 1 000 řádků se čtyřmi sloupci vyvolá:
- 1 000 volání
cgonasqlite3_step - 4 000 volání
cgonasqlite3_column_typeasqlite3_column_int64/sqlite3_column_text - Tisíce jednotlivých přechodů hranic mezi plánovačem Go a zásobníkem C
Pipelining požadavků a předběžné načítání dávek po 64 KiB
gosuda.org/sqlite3 odstraňuje přechody hranic u každého řádku tím, že přepracovává protokol na úrovni drátu kolem dvou mechanismů v stmt.go, rows.go a sqlite3/server/sqlite3_hooks.c:
1Go klient (stmt.go / rows.go) C11 worker (sqlite3_hooks.c)
2----------------------------- ----------------------------
31. Pipeline v 1 letu:
4 ResetAsync + BindValuesAsync
5 + StmtInfoAsync + FetchRowsAsync(128) =======> reset -> bind -> stmt_info ->
6 smyčka sqlite3_step() až 128 řádků
7 <======= Dávka 1 (řádky 1..128 zabalené ve slotu 64 KiB)
82. Okamžitě spustit předběžné načtení:
9 FetchRowsAsync(128) (Dávka 2) =======> smyčka sqlite3_step() pro řádky 129..256
10 Zatímco C krokuje Dávku 2, Go volající
11 čte řádky 1..128 z Dávky 1! <======= Dávka 2 připravena ve sdílené paměti
- Pipelining dotazu v jednom letu (
stmt.go): KdyžQueryContextneboStmt.QueryContextspustí příkaz pouze pro čtení,s.query()odešlereset,bind_values,stmt_info(při prvním spuštění) afetch_rowsasynchronně v jediném letu přes sdílenou paměť. Všechny čtyři operace se vykonávají za sebou na pracovním procesu C a vrátí se v jednom round tripu. - Úzké dávkové smyčky v C a asynchronní předběžné načítání (
rows.go): Uvnitřfetch_rows(sqlite3_hooks.c) pracovní proces C krokujesqlite3_step(stmt)v nativní smyčce v C a kóduje ažFetchSizeřádků (výchozí 128) přímo do odpovědního slotu sdílené paměti o velikosti 64 KiB, dokud není dosažen limit dávky, slot se nezaplní nebo nenastaneSQLITE_DONE. Jakmilerows.ingest()dekóduje Dávku 1, spustíFetchRowsAsyncpro Dávku 2 ještě předtím, než vrátí první řádek volajícímu. Zatímco váš Go kód prochází Dávku 1, pracovní proces C již krokuje Dávku 2 na jiném jádru CPU.
Na Apple M4 (darwin/arm64) ukazuje srovnání gosuda.org/sqlite3 (CGO_ENABLED=0) s github.com/mattn/go-sqlite3 (CGO_ENABLED=1) nad touže databází SQLite efekt dávkování a pipeliningu:
| Zátěž | gosuda.org/sqlite3 (CGO_ENABLED=0) | mattn/go-sqlite3 (CGO_ENABLED=1) | Relativní rychlost |
|---|---|---|---|
Bodový výběr (QueryRow podle PK) | 3.52 µs/op (9 B, 1 alokace) | 3.56 µs/op (232 B, 11 alokací) | 1.01x (parita, 11x méně alokací) |
Čtení 100 řádků (rows.Next) | 20.41 µs/op (30.5 KB, 211 alokací) | 36.34 µs/op (21.8 KB, 309 alokací) | 1.78x rychlejší |
Čtení 1 000 řádků (rows.Next) | 125.92 µs/op (296 KB, 2 030 alokací) | 349.10 µs/op (216 KB, 3 009 alokací) | 2.77x rychlejší |
| Připravený insert (autocommit) | 5.33 µs/op (0 B, 0 alokací) | 5.88 µs/op (120 B, 5 alokací) | 1.10x rychlejší (0 alokací) |
| Dávka 100 řádků v transakci | 189.99 µs/op (16 B, 1 alokace) | 191.50 µs/op (12.1 KB, 503 alokací) | 1.01x (500x méně alokací) |
Zrušení, sandboxing cest a obnova poolu připojení
Spouštění SQLite v izolovaném pracovním procesu také posiluje způsob, jakým ovladač zpracovává časové limity, nedůvěryhodné SQL a hranice souborového systému:
- Deterministické zrušení kontextu (
conn.goaerrors.go): Každé volání ovladače aktivuje lehkou ochranucontext.AfterFunc(cn.guard). Pokud jecontext.Contextvolajícího zrušen nebo jeho termín vyprší, zatímco na serveru běží pomalý rekurzivní dotaz nebo čekání na zámek, ochrana označí připojení jako chybné, ukončí lokální odkaz relace a odešle mimopásmový rámecCancel(). Pracovní proces C okamžitě zavolásqlite3_interrupt(db), čímž uvolnísqlite3_step, zatímco ovladač Go mapuje výsledný stavSQLITE_INTERRUPTnacontext.Cancelednebocontext.DeadlineExceededa pokynedatabase/sql, aby nahradil připojení v poolu. - Sandboxing adresářů (
sqlsrv_pathrules.c): Pracovní proces C vynucuje pravidla cest--allow-dirpři každém voláníopen_v2, rozlišuje symbolické odkazy a odmítá relativní průchod..mimo povolený adresář.sqlite3_load_extensionaSQLITE_LIMIT_ATTACHEDjsou ve výchozím nastavení zakázány, aby SQL injection nemohla uniknout z adresáře databáze nebo načíst libovolné sdílené objekty.
Struktura repozitáře
Repozitář je uspořádán tak, že hlavní balíček poskytuje ovladač database/sql pro přímou náhradu, zatímco launcher/ a sqlite3/ zůstávají přístupné pro pokročilá nasazení s vlastním serverem:
1gosuda.org/sqlite3
2├── driver.go / api.go # Registrace ovladače database/sql ("sqlite3") a Connector API
3├── dsn.go # Parser DSN mattn/go-sqlite3 v čistém provedení a konfigurace pragma
4├── conn.go / tx.go # Životní cyklus relace, ochrana kontextu a stavový automat transakcí
5├── stmt.go / rows.go # Pipelining požadavků v jednom letu a předběžné načítání dávek po 64 KiB
6├── errors.go # Typované primární a rozšířené chybové kódy SQLite a shoda sentinelů
7├── autolaunch.go / hashes.go # Zero-config materializace vloženého serveru a připnutí SHA-256
8├── internal/embedded/ # Binárky //go:embed označené platformou (linux, darwin, windows)
9├── launcher/ # Konfigurovatelný supervisor procesů pro vlastní nasazení serveru
10└── sqlite3/ # Generovaný hqq-bindgen Go klient a zdrojový kód serveru C11 SQLite
11 └── server/ # Implementace serveru C11 (sqlite3_hooks.c, sqlsrv_policy.c)
Začínáme
Nainstalujte modul příkazem go get a otevřete databázi pomocí sql.Open("sqlite3", dsn):
1go get gosuda.org/sqlite3@v0.3.3540100
- Repozitář na GitHubu: https://github.com/gosuda/hqq-sqlite3
- Reference DSN a pragma: docs/DSN.md
- Architektura a protokol na úrovni drátu: docs/ARCHITECTURE.md