gosuda.org/sqlite3: CGO_ENABLED=0 database/sql Driver Shared-Memory IPC:n välityksellä

gosuda.org/sqlite3 on Go-ohjelmointikielen database/sql -ajuri, joka on CGO_ENABLED=0-yhteensopiva ja ajaa alkuperäisen C-kielisen SQLite 3.54.1 -amalgaman eristetyssä työprosessissa HQQ:n jaetun muistin renkaiden kautta. Ajuri rekisteröityy vakionimellä "sqlite3", tukee github.com/mattn/go-sqlite3 -DSN-kyselyparametreja, sisältää upotetut SLSA-todennetut esikäännetyt palvelinbinäärit kuudelle käyttöjärjestelmän ja arkkitehtuurin kohteelle sekä käyttää kyselyiden putkitusta ja 64 KiB:n erien esihakua, minkä ansiosta se suoriutuu monirivisistä kyselyistä paremmin kuin prosessinsisäinen cgo.
1go get gosuda.org/sqlite3@v0.3.3540100
Miksi Toinen Go-SQLite-ajuri?
SQLite on yksi maailman laajimmin käytetyistä ohjelmistoista, mutta sen käyttäminen Go-kielestä on historiallisesti pakottanut kehittäjät epämukavaan valintaan rakennuksen yksinkertaisuuden ja moottorin uskollisuuden välillä.
Toisaalta on github.com/mattn/go-sqlite3, pitkäaikainen yhteisön vakiovalinta. Se kääri viralliset C-kielisen SQLite-amalgaman suoraan cgo:n kautta. Saat todellisen C-kielisen kyselyoptimoijan, täydet WAL-samanaikaisuussemantiikat ja ennustettavan C-muistinhallinnan. Hinta on CGO_ENABLED=1: paikalliset käännökset ja CI-putkistot hidastuvat, minimaaliset scratch- tai distroless-kontti-imagot vaativat vastaavat C-ajonaikaiset kirjastot, ristiinkääntäminen toiselle käyttöjärjestelmälle tai suoritinarkkitehtuurille vaatii täyden C-ristiinkääntötyökaluketjun, ja mikä tahansa C-koodin muistivaurio jakaa Go-prosessin osoiteavaruuden.
Toisaalta ovat puhtaan Go-kielen SQLite-portit, jotka on tuotettu mekaanisesti kääntämällä C-amalgama Go-lähdekoodiksi emuloidun C-ajonaikaisen ympäristön (libc) päälle. Ne palauttavat CGO_ENABLED=0:n, mikä tekee käännöksistä triviaaleja. Pitkäkestoisissa kuormissa käännetty C-keko ja rivikohtaiset Go-allokaatiot kuitenkin lisäävät roskienkeräyksen painetta, ja hienovaraiset reunatapaukset lukituksessa, varausaikakatkaisuissa tai lauseiden keskeytyksessä voivat poiketa natiiveista C-käännöksistä.
Rakensimme gosuda.org/sqlite3:n, jotta Go-sovelluksien ei tarvitse valita CGO_ENABLED=0:n ja todellisen C-kielisen SQLite-moottorin välillä. Kääntämällä virallisen SQLite 3.54.1 C-amalgaman omistettuun jaetun muistin palvelimeen (hqq-sqlite-server) ja kommunikoimalla gosuda.org/hqq:n kautta, Go-binääri käännetään CGO_ENABLED=0-tilassa ja suorittaa muokkaamatonta, -O3-optimoitua C-kielistä SQLite-koodia prosessirajan yli.
Konfiguroinniton Suoritus: go get:stä sql.Open:iin
Olemassa olevan Go-koodikannan github.com/mattn/go-sqlite3 -kirjaston korvaaminen vaatii ainoastaan tyhjän tuontipolun muuttamisen. Ajuri rekisteröityy nimellä "sqlite3" (ja "hqq-sqlite" eksplisiittistä valintaa varten) ja hyväksyy samat DSN-kyselymerkkijonot:
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}
Upotetut SLSA-todennetut työbinäärit
Luonnollinen kysymys, kun kuulee kirjaston käyttävän C-työprosessia, on: "Pitääkö minun asentaa tai toimittaa erillinen C-binääri Go-ohjelmani rinnalle?"
Ei. gosuda.org/sqlite3 upottaa esikäännetyt hqq-sqlite-server-binäärit kaikille kuudelle pääkohteelle suoraan alustakohtaisiin Go-tiedostoihin (internal/embedded/embed_<goos>_<goarch>.go). Koska Go:n käännösrajoitteet kääntävät vain kohdetta GOOS:ia ja GOARCH:ia vastaavan tiedoston, GOOS=linux GOARCH=arm64 go build -ristiinkäännös upottaa automaattisesti vain noin 1,4 MiB:n kokoisen linux/arm64-palvelinbinäärin.
| Kohde-käyttöjärjestelmä | Kohdearkkitehtuuri (GOARCH) | Upotettu binääri | Käännöstyökalut ja alkuperä |
|---|---|---|---|
| Linux | amd64, arm64 | hqq-sqlite-server_linux_<arch> | GCC -O3, SLSA Build L3 -alkuperä |
| macOS | arm64, amd64 | hqq-sqlite-server_darwin_<arch> | AppleClang -O3, SLSA Build L3 -alkuperä |
| Windows | amd64 | hqq-sqlite-server_windows_amd64.exe | MSVC -O2, SLSA Build L3 -alkuperä |
| Windows | arm64 | hqq-sqlite-server_windows_arm64.exe | zig cc -target aarch64-windows-gnu -O3, SLSA L3 |
Kun sql.Open muodostaa ensimmäisen yhteyden, autolaunch.go purkaa upotetun binäärin käyttäjän välimuistihakemistoon ($XDG_CACHE_HOME/hqq/bin/<sha256>/), varmistaa sen kryptografisen SHA-256-tiivisteen vasten hashes.go-tiedoston kiinnitettyä ReleaseHashes-karttaa gosuda.org/hqq/secureexec-paketin avulla ja käynnistää työprosessin --parent-stdin-lipulla, jotta sen elinikä on tiukasti sidottu Go-prosessiisi.
mattn/go-sqlite3 DSN -yhteensopivuus puhtaasta lähteestä
Jotta ajuri toimisi todellisena suorana korvaajana, DSN-jäsennin (dsn.go) toteuttaa puhtaasta lähteestä rakennetun kopion yhteys- ja pragma-asetuksista, joita käytetään mattn/go-sqlite3-koodikannoissa:
| DSN-kyselyparametri | Hyväksytyt arvot | SQLite-vaikutus |
|---|---|---|
_busy_timeout / _timeout | Kokonaisluku millisekunteina (esim. 5000) | Kutsuu sqlite3_busy_timeout(db, ms) istunnon avautuessa. |
_journal_mode / _journal | DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF | Suorittaa PRAGMA journal_mode = <mode>. |
_txlock | immediate, exclusive, deferred | Valitsee db.BeginTx:lle joko BEGIN IMMEDIATE, BEGIN EXCLUSIVE tai BEGIN DEFERRED. |
_foreign_keys / _fk | on, true, 1, off, false, 0 | Suorittaa PRAGMA foreign_keys = ON/OFF. |
_synchronous / _sync | 0/OFF, 1/NORMAL, 2/FULL, 3/EXTRA | Suorittaa PRAGMA synchronous = <level>. |
_cache_size | Kokonaisluku sivuina tai -KiB (esim. -20000) | Suorittaa PRAGMA cache_size = <val>. |
_loc | auto tai IANA-aikavyöhyke (esim. UTC, Asia/Seoul) | Jäsentää DATETIME/TIMESTAMP tekstisarakkeet arvoiksi *time.Time kohdesijainnissa. |
mode | ro, rw, rwc, memory | Asettaa SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE tai SQLITE_OPEN_MEMORY. |
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuum | Totuusarvo tai tilakokonaisluku | Soveltaa vastaavan istunnon PRAGMA-asetuksen yhteyden alustuksessa. |
Prosessieristyksen Muuttaminen Läpäisykyvyn Eduksi
Ensi silmäyksellä SQLiten ajaminen erillisessä prosessissa vaikuttaa väistämättä hitaammalta kuin sqlite3_step-kutsujen tekeminen prosessinsisäisesti cgo:n kautta. Käytännössä päinvastoin tapahtuu heti, kun kysely palauttaa enemmän kuin yhden rivin.
Rivikohtaisten cgo-kutsujen piilokustannus
Tavanomaisessa cgo-SQLite-ajurissa database/sql ohjaa iterointia yksi rivi ja yksi sarake kerrallaan. 1 000 rivin lukeminen 4 sarakkeella aiheuttaa:
- 1 000
cgo-kutsuasqlite3_step:lle - 4 000
cgo-kutsuasqlite3_column_type:lle sekäsqlite3_column_int64:lle /sqlite3_column_text:lle - Tuhansia yksittäisiä rajanylityksiä Go-ajastimen ja C-pinon välillä
Kyselyiden putkitus ja 64 KiB:n erien esihaku
gosuda.org/sqlite3 poistaa rivikohtaiset rajanylitykset uudistamalla siirtoprotokollan kahden mekanismin ympärille, jotka sijaitsevat tiedostoissa stmt.go, rows.go ja sqlite3/server/sqlite3_hooks.c:
1Go-asiakas (stmt.go / rows.go) C11-työprosessi (sqlite3_hooks.c)
2------------------------------ ---------------------------------
31. Putkita yhdessä lennossa:
4 ResetAsync + BindValuesAsync
5 + StmtInfoAsync + FetchRowsAsync(128) =======> reset -> bind -> stmt_info ->
6 silmukka sqlite3_step() enintään 128 riville
7 <======= Erä 1 (rivit 1..128 pakattuna 64 KiB:n slotiin)
82. Laukaise välittömästi esihaku:
9 FetchRowsAsync(128) (Erä 2) =======> silmukka sqlite3_step() riveille 129..256
10 Kun C askeltaa Erää 2, Go-kutsuja
11 lukee rivit 1..128 Erästä 1! <======= Erä 2 valmis jaetussa muistissa
- Yksittäislennon kyselyputkitus (
stmt.go): KunQueryContexttaiStmt.QueryContextsuorittaa vain luku -lauseen,s.query()lähettääreset-,bind_values-,stmt_info- (ensimmäisellä suorituskerralla) jafetch_rows-operaatiot asynkronisesti yhdessä jaetun muistin lennossa. Kaikki neljä operaatiota suoritetaan peräkkäin C-työprosessissa ja palautetaan yhdellä kierroksella. - Tiiviit C-erisilmukat ja asynkroninen esihaku (
rows.go):fetch_rows-funktion sisällä (sqlite3_hooks.c) C-työprosessi askeltaasqlite3_step(stmt):ia natiivissa C-silmukassa ja koodaa enintäänFetchSize-riviä (oletus 128) suoraan 64 KiB:n jaetun muistin vastausslottiin, kunnes erän raja, slotti täyttyy taiSQLITE_DONEtapahtuu. Heti kunrows.ingest()on purkanut Erän 1, se laukaiseeFetchRowsAsync:n Erälle 2 ennen ensimmäisen rivin palauttamista kutsujalle. Kun Go-koodisi lukee Erää 1, C-työprosessi askeltaa jo Erää 2 toisella suoritinytimellä.
Apple M4:llä (darwin/arm64) vertailu gosuda.org/sqlite3:n (CGO_ENABLED=0) ja github.com/mattn/go-sqlite3:n (CGO_ENABLED=1) välillä samalla SQLite-tietokannalla osoittaa erien käsittelyn ja putkituksen vaikutuksen:
| Kuorma | gosuda.org/sqlite3 (CGO_ENABLED=0) | mattn/go-sqlite3 (CGO_ENABLED=1) | Suhteellinen nopeus |
|---|---|---|---|
Pistehaku (QueryRow PK:lla) | 3.52 µs/op (9 B, 1 allokaatio) | 3.56 µs/op (232 B, 11 allokaatiota) | 1.01x (tasavertainen, 11x vähemmän allokaatioita) |
100 rivin skannaus (rows.Next) | 20.41 µs/op (30.5 KB, 211 allokaatiota) | 36.34 µs/op (21.8 KB, 309 allokaatiota) | 1.78x nopeampi |
1 000 rivin skannaus (rows.Next) | 125.92 µs/op (296 KB, 2 030 allokaatiota) | 349.10 µs/op (216 KB, 3 009 allokaatiota) | 2.77x nopeampi |
| Valmisteltu lisäys (autocommit) | 5.33 µs/op (0 B, 0 allokaatiota) | 5.88 µs/op (120 B, 5 allokaatiota) | 1.10x nopeampi (0 allokaatiota) |
| 100 rivin transaktioerä | 189.99 µs/op (16 B, 1 allokaatio) | 191.50 µs/op (12.1 KB, 503 allokaatiota) | 1.01x (500x vähemmän allokaatioita) |
Peruutus, polkuhiekkalaatikointi ja yhteysaltaan palautuminen
SQLiten ajaminen eristetyssä työprosessissa vahvistaa myös tapaa, jolla ajuri käsittelee aikakatkaisuja, luotettavia SQL-lauseita ja tiedostojärjestelmän rajoja:
- Deterministinen kontekstin peruutus (
conn.gojaerrors.go): Jokainen ajurikutsu asettaa kevyencontext.AfterFunc-vartijan (cn.guard). Jos kutsujancontext.Contextperuutetaan tai sen määräaika umpeutuu, kun hidas rekursiivinen kysely tai lukitusodotus on käynnissä palvelimella, vartija merkitsee yhteyden virheelliseksi, katkaisee paikallisen istuntoyhteyden ja lähettää kaistan ulkopuolisenCancel()-kehyksen. C-työprosessi kutsuu välittömästisqlite3_interrupt(db):tä, mikä vapauttaasqlite3_step:n, kun taas Go-ajuri kuvaa tuloksena olevanSQLITE_INTERRUPT-tilan arvoksicontext.Canceledtaicontext.DeadlineExceededja ohjaadatabase/sql:n korvaamaan yhteyden altaassa. - Hakemistohiekkalaatikointi (
sqlsrv_pathrules.c): C-työprosessi valvoo--allow-dir-polkusääntöjä jokaisellaopen_v2-kutsulla, selvittää symbolisia linkkejä ja hylkää sallitun hakemiston ulkopuolelle johtavat relatiiviset..-siirtymät.sqlite3_load_extensionjaSQLITE_LIMIT_ATTACHEDovat oletuksena poissa käytöstä, jotta SQL-injektio ei voi poistua tietokantahakemistosta tai ladata mielivaltaisia jaettuja objekteja.
Repositorion rakenne
Repositorio on jäsennelty siten, että ylätason paketti tarjoaa suoran database/sql-ajurin, kun taas launcher/ ja sqlite3/ ovat saatavilla edistyneitä mukautettuja palvelinkäyttöönottoja varten:
1gosuda.org/sqlite3
2├── driver.go / api.go # database/sql -ajurin rekisteröinti ("sqlite3") ja Connector-API
3├── dsn.go # Puhtaasta lähteestä rakennettu mattn/go-sqlite3 DSN-jäsennin ja pragma-asetukset
4├── conn.go / tx.go # Istunnon elinkaari, kontekstivartija ja transaktiotilakone
5├── stmt.go / rows.go # Yksittäislennon pyyntöputkitus ja 64 KiB:n erien esihaku
6├── errors.go # Tyypitetyt SQLiten ensisijaiset/laajennetut virhekoodit ja vartijavastaavuus
7├── autolaunch.go / hashes.go # Konfiguroinniton upotetun palvelimen purkaminen ja SHA-256-kiinnitys
8├── internal/embedded/ # Alustakohtaiset //go:embed-binäärit (linux, darwin, windows)
9├── launcher/ # Konfiguroitava prosessivalvoja mukautettuja palvelinkäyttöönottoja varten
10└── sqlite3/ # Generoitu hqq-bindgen Go-asiakas ja C11 SQLite-palvelinlähdekoodi
11 └── server/ # C11-palvelintoteutus (sqlite3_hooks.c, sqlsrv_policy.c)
Aloittaminen
Asenna moduuli go get-komennolla ja avaa tietokantasi komennolla sql.Open("sqlite3", dsn):
1go get gosuda.org/sqlite3@v0.3.3540100
- GitHub-repositorio: https://github.com/gosuda/hqq-sqlite3
- DSN- ja pragma-viite: docs/DSN.md
- Arkkitehtuuri ja siirtoprotokolla: docs/ARCHITECTURE.md