gosuda.org/sqlite3: A Drop-In database/sql Driver Running Native C SQLite Over Shared Memory
[62b05454ed1bdd82]gosuda.org/sqlite3 je ovládač database/sql s nastavením CGO_ENABLED=0 pre jazyk Go, ktorý spúšťa pôvodnú C SQLite amalgamáciu 3.54.1 vo vnútri izolovaného pracovného procesu (worker process) prostredníctvom kruhových vyrovnávacích pamätí so zdieľanou pamäťou HQQ. Registruje sa pod štandardným názvom ovládača "sqlite3", podporuje parametre dopytu DSN pre github.com/mattn/go-sqlite3, vkladá predkompilované binárne súbory servera overené prostredníctvom SLSA pre šesť cieľových operačných systémov a architektúr a využíva pipelining požiadaviek s 64 KiB dávkovým predbežným načítaním (prefetching), čím prekonáva in-process cgo pri dopytoch s viacerými riadkami.
1go get gosuda.org/sqlite3@v0.3.3540100
Prečo ďalší ovládač Go SQLite?
SQLite je jedným z najrozšírenejších softvérových dielov na svete, avšak jeho používanie v jazyku Go historicky nútilo inžinierov k nepríjemnej voľbe medzi jednoduchosťou zostavovania (build simplicity) a vernosťou enginu.
Na jednej strane stojí github.com/mattn/go-sqlite3, dlhoročný komunitný štandard. Priamo obaľuje oficiálnu C SQLite amalgamáciu prostredníctvom cgo. Získate skutočný C plánovač dopytov, úplnú sémantiku súbežnosti WAL a predvídateľnú správu C pamäte. Daňou za to je CGO_ENABLED=1: lokálne zostavenia a CI kanály (pipelines) sa spomaľujú, minimálne obrazové kontajnery typu scratch alebo distroless vyžadujú zodpovedajúce knižnice C runtime, cross-kompilácia na iný operačný systém alebo architektúru CPU vyžaduje kompletný C cross-toolchain a akákoľvek korupcia pamäte v jazyku C zdieľa adresný priestor procesu Go.
Na druhej strane sú čisté Go porty SQLite generované mechanickým prekladom C amalgamácie do zdrojového kódu Go nad emulovaným C runtime (libc). Obnovujú CGO_ENABLED=0, vďaka čomu je zostavovanie triviálne. Pri dlhodobom zaťažení však preložená C halda (heap) a alokácie v Go na jeden riadok zvyšujú záťaž na správcu pamäte (garbage collection), pričom jemné okrajové prípady pri zamykaní, časových limitoch zaneprázdnenia (busy timeouts) alebo prerušení príkazov sa môžu odchyľovať od natívnych C zostavení.
Ovládač gosuda.org/sqlite3 sme vytvorili preto, aby aplikácie v jazyku Go nemuseli voliť medzi CGO_ENABLED=0 a skutočným C SQLite enginom. Kompiláciou oficiálnej C amalgamácie SQLite 3.54.1 do vyhradeného servera so zdieľanou pamäťou (hqq-sqlite-server) a komunikáciou cez gosuda.org/hqq sa váš binárny súbor Go zostavuje s nastavením CGO_ENABLED=0, pričom vykonáva nezmenenú C SQLite optimalizovanú pomocou -O3 naprieč hranicou procesov.
Spustenie s nulovou konfiguráciou: Od go get po sql.Open
Náhrada knižnice github.com/mattn/go-sqlite3 v existujúcej kódovej základni Go vyžaduje zmenu iba prázdnej cesty importu. Ovládač sa registruje ako "sqlite3" (a "hqq-sqlite" pre explicitný výber) a akceptuje rovnaké reťazce dopytu 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árne súbory overené prostredníctvom SLSA
Pri správe, že knižnica využíva C pracovný proces, sa prirodzene vynára otázka: „Musím vedľa svojho programu v jazyku Go inštalovať alebo distribuovať samostatný C binárny súbor?“
Nie. gosuda.org/sqlite3 priamo vnútri súborov Go označených pre konkrétnu platformu (internal/embedded/embed_<goos>_<goarch>.go) vkladá predkompilované binárne súbory hqq-sqlite-server pre všetkých šesť hlavných cieľových platforiem. Keďže obmedzenia zostavenia v jazyku Go kompilujú iba súbor zodpovedajúci vášmu cieľovému GOOS a GOARCH, cross-kompilácia príkazom GOOS=linux GOARCH=arm64 go build automaticky vnesie iba binárny súbor servera linux/arm64 s veľkosťou približne 1,4 MiB.
| Cieľový OS | Cieľová architektúra (GOARCH) | Vložený binárny súbor | Zostavovací nástroj & Pôvod (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 |
Keď sql.Open nadviaže svoje prvé pripojenie, autolaunch.go materializuje vložený binárny súbor do adresára vyrovnávacej pamäte používateľa ($XDG_CACHE_HOME/hqq/bin/<sha256>/), overí jeho kryptografický súhrn SHA-256 oproti pripnutej mape ReleaseHashes v súbore hashes.go prostredníctvom gosuda.org/hqq/secureexec a spustí pracovný proces s parametrom --parent-stdin, takže jeho životnosť je prísne viazaná na váš proces Go.
Kompatibilita DSN v čistej miestnosti pre mattn/go-sqlite3
Aby fungoval ako skutočná náhrada typu drop-in, analyzátor DSN (dsn.go) implementuje repliku v čistej miestnosti (clean-room replica) pre pripojenia a konfiguračné prepínače pragma používané v kódových základniach mattn/go-sqlite3:
| Parameter dopytu DSN | Akceptované hodnoty | Účinok v SQLite |
|---|---|---|
_busy_timeout / _timeout | Celé číslo v milisekundách (napr. 5000) | Pri otváraní relácie zavolá funkciu sqlite3_busy_timeout(db, ms). |
_journal_mode / _journal | DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF | Vykoná príkaz PRAGMA journal_mode = <mode>. |
_txlock | immediate, exclusive, deferred | Vyberie BEGIN IMMEDIATE, BEGIN EXCLUSIVE alebo BEGIN DEFERRED pre db.BeginTx. |
_foreign_keys / _fk | on, true, 1, off, false, 0 | Vykoná príkaz PRAGMA foreign_keys = ON/OFF. |
_synchronous / _sync | 0/OFF, 1/NORMAL, 2/FULL, 3/EXTRA | Vykoná príkaz PRAGMA synchronous = <level>. |
_cache_size | Celé číslo stránok alebo -KiB (napr. -20000) | Vykoná príkaz PRAGMA cache_size = <val>. |
_loc | auto alebo IANA časové pásmo (napr. UTC, Asia/Seoul) | Analyzuje textové stĺpce DATETIME/TIMESTAMP na *time.Time v cieľovom umiestnení. |
mode | ro, rw, rwc, memory | Nastaví SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE alebo SQLITE_OPEN_MEMORY. |
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuum | Booleovské hodnoty alebo celé čísla režimov | Aplikuje zodpovedajúcu reláciu PRAGMA pri inicializácii pripojenia. |
Premena procesnej izolácie na výhodu priepustnosti
Na prvý pohľad sa zdá, že spúšťanie SQLite v samostatnom procese musí byť pomalšie ako volanie sqlite3_step v rámci procesu cez cgo. V praxi sa stane pravý opak akonáhle dopyt vráti viac než jeden riadok.
Skryté náklady na cgo volania riadok po riadku
V konvenčnom SQLite ovládači cez cgo riadi database/sql iteráciu po jednom riadku a jednom stĺpci. Prečítanie 1 000 riadkov so 4 stĺpcami spúšťa:
- 1 000
cgovolaní funkciesqlite3_step - 4 000
cgovolaní funkciísqlite3_column_typeasqlite3_column_int64/sqlite3_column_text - Tisíce individuálnych prechodov hraníc medzi plánovačom Go a zásobníkom C
Pipelining požiadaviek a 64 KiB dávkové predbežné načítanie
Ovládač gosuda.org/sqlite3 eliminuje prekračovanie hraníc pre jednotlivé riadky prepracovaním sieťového protokolu okolo dvoch mechanizmov v súboroch stmt.go, rows.go a sqlite3/server/sqlite3_hooks.c:
1Go Client (stmt.go / rows.go) C11 Worker (sqlite3_hooks.c)
2----------------------------- ----------------------------
31. Pipeline in 1 flight:
4 ResetAsync + BindValuesAsync
5 + StmtInfoAsync + FetchRowsAsync(128) =======> reset -> bind -> stmt_info ->
6 loop sqlite3_step() up to 128 rows
7 <======= Batch 1 (rows 1..128 packed in 64 KiB slot)
82. Immediately fire prefetch:
9 FetchRowsAsync(128) (Batch 2) =======> loop sqlite3_step() for rows 129..256
10 While C steps Batch 2, Go caller
11 scans rows 1..128 from Batch 1! <======= Batch 2 ready in shared memory
- Jednocyklové pipeliningové dopyty (
stmt.go): KeďQueryContextaleboStmt.QueryContextspustí príkaz len na čítanie,s.query()odošle príkazyreset,bind_values,stmt_info(pri prvom spustení) afetch_rowsasynchrónne v rámci jedného prenosu v zdieľanej pamäti. Všetky štyri operácie sa vykonajú bezprostredne za sebou na C worker procese a vrátia sa v jednej spiatočnej ceste. - Tesné C dávkové cykly a asynchrónne predbežné načítanie (
rows.go): Vo vnútri funkciefetch_rows(sqlite3_hooks.c) prechádza C worker proces cezsqlite3_step(stmt)v natívnom C cykle a kóduje ažFetchSizeriadkov (predvolene 128) priamo do 64 KiB slotu odpovede so zdieľanou pamäťou, kým sa nedosiahne limit dávky, kým nie je slot plný alebo kým nenastaneSQLITE_DONE. Hneď akorows.ingest()dekóduje dávku Batch 1, odošle požiadavkuFetchRowsAsyncpre dávku Batch 2 predtým, ako vráti prvý riadok volajúcemu. Kým váš kód v Go prechádza dávku Batch 1, C worker už spracováva dávku Batch 2 na inom jadre CPU.
Na čipe Apple M4 (darwin/arm64) ukazuje porovnanie gosuda.org/sqlite3 (CGO_ENABLED=0) s github.com/mattn/go-sqlite3 (CGO_ENABLED=1) na tej istej databáze SQLite vplyv dávkovania a pipeliningu:
| Pracovná záťaž | gosuda.org/sqlite3 (CGO_ENABLED=0) | mattn/go-sqlite3 (CGO_ENABLED=1) | Relatívna rýchlosť |
|---|---|---|---|
Bodový výber (QueryRow podľa primárneho kľúča) | 3.52 µs/op (9 B, 1 alokácia) | 3.56 µs/op (232 B, 11 alokácií) | 1,01x (parita, 11x menej alokácií) |
Prehľadanie 100 riadkov (rows.Next) | 20.41 µs/op (30,5 KB, 211 alokácií) | 36.34 µs/op (21,8 KB, 309 alokácií) | 1,78x rýchlejšie |
Prehľadanie 1 000 riadkov (rows.Next) | 125.92 µs/op (296 KB, 2 030 alokácií) | 349.10 µs/op (216 KB, 3 009 alokácií) | 2,77x rýchlejšie |
| Pripravený vklad (Autocommit) | 5.33 µs/op (0 B, 0 alokácií) | 5.88 µs/op (120 B, 5 alokácií) | 1,10x rýchlejšie (0 alokácií) |
| Dávka 100 transakčných riadkov | 189.99 µs/op (16 B, 1 alokácia) | 191.50 µs/op (12,1 KB, 503 alokácií) | 1,01x (500x menej alokácií) |
Zrušenie, izolácia ciest (sandboxing) a obnova skupiny pripojení (connection pool)
Spúšťanie SQLite v izolovanom pracovnom procese taktiež posilňuje spôsob, akým ovládač spracováva časové limity, nedôveryhodný jazyk SQL a hranice súborového systému:
- Determinantné zrušenie kontextu (
conn.goaerrors.go): Každé volanie ovládača aktivuje ľahkú ochranucontext.AfterFunc(cn.guard). Ak sa kontext volajúceho (context.Context) zruší alebo vyprší jeho časový limit počas spúšťania pomalého rekurzívneho dopytu alebo čakania na zámok na serveri, ochrana označí pripojenie za chybné, ukončí prepojenie lokálnej relácie a odošle mimopásmový (out-of-band) rámecCancel(). C worker vyvolá funkciusqlite3_interrupt(db)okamžite, čím odblokujesqlite3_step, zatiaľ čo Go ovládač mapuje výsledný stavSQLITE_INTERRUPTnacontext.Canceledalebocontext.DeadlineExceededa nariadidatabase/sqlnahradiť pripojenie v skupine (pool). - Izolácia adresárov (
sqlsrv_pathrules.c): C worker presadzuje pravidlá ciest--allow-dirpri každom volaníopen_v2, pričom rozlišuje symbolické odkazy a odmieta relatívny prechod..mimo povoleného adresára. Funkciesqlite3_load_extensionaSQLITE_LIMIT_ATTACHEDsú predvolene zakázané, aby injektáž SQL nemohla uniknúť z databázového adresára ani načítať ľubovoľné zdieľané objekty.
Štruktúra úložiska (Repository Layout)
Úložisko je štruktúrované tak, že balík najvyššej úrovne poskytuje ovládač database/sql typu drop-in, zatiaľ čo priečinky launcher/ a sqlite3/ zostávajú prístupné pre pokročilé nasadenia vlastných serverov:
1gosuda.org/sqlite3
2├── driver.go / api.go # database/sql driver registration ("sqlite3") and Connector API
3├── dsn.go # Clean-room mattn/go-sqlite3 DSN parser and pragma configuration
4├── conn.go / tx.go # Session lifecycle, context guard, and transaction state machine
5├── stmt.go / rows.go # Single-flight request pipelining and 64 KiB batch prefetching
6├── errors.go # Typed SQLite primary/extended error codes and sentinel matching
7├── autolaunch.go / hashes.go # Zero-config embedded server materialization and SHA-256 pinning
8├── internal/embedded/ # Platform-tagged //go:embed binaries (linux, darwin, windows)
9├── launcher/ # Configurable process supervisor for custom server deployments
10└── sqlite3/ # Generated hqq-bindgen Go client and C11 SQLite server source
11 └── server/ # C11 server implementation (sqlite3_hooks.c, sqlsrv_policy.c)
Začínam
Nainštalujte modul pomocou príkazu go get a otvorte databázu pomocou sql.Open("sqlite3", dsn):
1go get gosuda.org/sqlite3@v0.3.3540100
- Úložisko GitHub: https://github.com/gosuda/hqq-sqlite3
- Referencia DSN & Pragma: docs/DSN.md
- Architektúra a sieťový protokol: docs/ARCHITECTURE.md