gosuda.org/sqlite3: CGO_ENABLED=0 database/sql Sürücüsü, Paylaşımlı Bellek IPC Üzerinden

gosuda.org/sqlite3, CGO_ENABLED=0 ile çalışan ve Go için bir database/sql sürücüsüdür; upstream C SQLite 3.54.1 amalgamation kodunu, HQQ paylaşımlı bellek halkaları üzerinden izole bir worker süreci içinde çalıştırır. Standart "sqlite3" sürücü adıyla kayıt olur, github.com/mattn/go-sqlite3 DSN sorgu parametrelerini destekler, altı işletim sistemi ve mimari hedefi için SLSA ile doğrulanmış önceden derlenmiş sunucu ikili dosyalarını gömülü olarak içerir ve 64 KiB toplu ön getirme (batch prefetching) ile istek boru hattı (request pipelining) kullanarak çok satırlı sorgularda süreç içi cgo çözümünden daha yüksek performans sağlar.
1go get gosuda.org/sqlite3@v0.3.3540100
Neden Bir Go SQLite Sürücüsü Daha?
SQLite, dünyada en yaygın kullanılan yazılımlardan biridir; ancak Go'dan kullanımı tarihsel olarak mühendisleri derleme basitliği ile motor sadakati arasında rahatsız edici bir seçim yapmaya zorlamıştır.
Bir yanda, uzun süredir topluluğun standardı olan github.com/mattn/go-sqlite3 bulunmaktadır. Resmi C SQLite amalgamation kodunu doğrudan cgo aracılığıyla sarmalar. Gerçek C sorgu planlayıcısını, tam WAL eşzamanlılık semantiğini ve öngörülebilir C bellek yönetimini elde edersiniz. Bedeli ise CGO_ENABLED=1 gerekliliğidir: yerel derlemeler ve CI hatları yavaşlar, minimal scratch veya distroless konteyner imajları uyumlu C çalışma zamanı kütüphaneleri gerektirir, başka bir işletim sistemine veya CPU mimarisine çapraz derleme tam bir C çapraz araç zinciri gerektirir ve C içindeki herhangi bir bellek bozulması Go süreci adres alanını paylaşır.
Diğer yanda, C amalgamation kodunun bir öykünülmüş C çalışma zamanı (libc) üzerinden Go kaynak koduna mekanik olarak çevrilmesiyle üretilen saf Go SQLite portları vardır. Bunlar CGO_ENABLED=0 desteğini geri getirir ve derlemeyi basitleştirir. Ancak sürekli iş yüklerinde, çevrilmiş C yığını ve satır başına Go ayırmaları çöp toplama baskısını artırır ve kilitleme, meşgul zaman aşımları veya ifade kesintisi gibi ince uç durumlar yerel C derlemelerinden ayrışabilir.
gosuda.org/sqlite3 kütüphanesini, Go uygulamalarının CGO_ENABLED=0 ile gerçek C SQLite motoru arasında seçim yapmak zorunda kalmaması için oluşturduk. Resmi SQLite 3.54.1 C amalgamation kodunu, özel bir paylaşımlı bellek sunucusuna (hqq-sqlite-server) derleyerek ve gosuda.org/hqq üzerinden iletişim kurarak, Go ikili dosyanız CGO_ENABLED=0 ile derlenirken, değiştirilmemiş, -O3 ile optimize edilmiş C SQLite'ı bir süreç sınırının ötesinde çalıştırır.
Sıfır Yapılandırmalı Çalıştırma: go get Komutundan sql.Open Çağrısına
Mevcut bir Go kod tabanında github.com/mattn/go-sqlite3 kütüphanesini değiştirmek yalnızca boş (blank) içe aktarma yolunun değiştirilmesini gerektirir. Sürücü "sqlite3" adıyla (ve açık seçim için "hqq-sqlite" adıyla) kayıt olur ve aynı DSN sorgu dizgelerini kabul eder:
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}
Gömülü SLSA ile Doğrulanmış Worker İkili Dosyaları
Bir kütüphanenin C worker süreci kullandığını duyduğunuzda akla gelen doğal bir soru şudur: "Go programımın yanında ayrı bir C ikili dosyasını kurmam veya dağıtmam gerekir mi?"
Hayır. gosuda.org/sqlite3, altı ana hedefin tümü için önceden derlenmiş hqq-sqlite-server ikili dosyalarını, platforma göre etiketlenmiş Go dosyalarının (internal/embedded/embed_<goos>_<goarch>.go) içine doğrudan gömer. Go'nun derleme kısıtlamaları yalnızca hedef GOOS ve GOARCH değerinizle eşleşen dosyayı derlediğinden, GOOS=linux GOARCH=arm64 go build komutuyla çapraz derleme yapmak yalnızca yaklaşık 1,4 MiB boyutundaki linux/arm64 sunucu ikili dosyasını otomatik olarak gömer.
| Hedef İşletim Sistemi | Hedef Mimari (GOARCH) | Gömülü İkili Dosya | Derleme Araç Zinciri ve Köken |
|---|---|---|---|
| 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 |
sql.Open ilk bağlantısını kurduğunda, autolaunch.go gömülü ikili dosyayı kullanıcı önbellek dizinine ($XDG_CACHE_HOME/hqq/bin/<sha256>/) yazar, kriptografik SHA-256 özetini hashes.go içindeki sabitlenmiş ReleaseHashes haritasına karşı gosuda.org/hqq/secureexec aracılığıyla doğrular ve worker'ı --parent-stdin parametresiyle başlatır; böylece yaşam süresi kesin olarak Go süreciniz ile sınırlandırılır.
Temiz Oda (Clean-Room) mattn/go-sqlite3 DSN Uyumluluğu
Gerçek bir doğrudan yerine geçen (drop-in) çözüm olabilmek için DSN ayrıştırıcısı (dsn.go), mattn/go-sqlite3 kod tabanlarında kullanılan bağlantı ve pragma ayarlarının temiz oda bir kopyasını uygular:
| DSN Sorgu Parametresi | Kabul Edilen Değerler | SQLite Etkisi |
|---|---|---|
_busy_timeout / _timeout | Milisaniye cinsinden tamsayı (ör. 5000) | Oturum açılışında sqlite3_busy_timeout(db, ms) çağrısını yapar. |
_journal_mode / _journal | DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF | PRAGMA journal_mode = <mode> ifadesini çalıştırır. |
_txlock | immediate, exclusive, deferred | db.BeginTx için BEGIN IMMEDIATE, BEGIN EXCLUSIVE veya BEGIN DEFERRED seçer. |
_foreign_keys / _fk | on, true, 1, off, false, 0 | PRAGMA foreign_keys = ON/OFF ifadesini çalıştırır. |
_synchronous / _sync | 0/OFF, 1/NORMAL, 2/FULL, 3/EXTRA | PRAGMA synchronous = <level> ifadesini çalıştırır. |
_cache_size | Tamsayı sayfa veya -KiB (ör. -20000) | PRAGMA cache_size = <val> ifadesini çalıştırır. |
_loc | auto veya IANA saat dilimi (ör. UTC, Asia/Seoul) | DATETIME/TIMESTAMP metin sütunlarını hedef konumda *time.Time değerlerine ayrıştırır. |
mode | ro, rw, rwc, memory | SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE veya SQLITE_OPEN_MEMORY bayraklarını ayarlar. |
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuum | Boolean veya mod tamsayıları | Bağlantı başlatmada ilgili oturum PRAGMA ifadesini uygular. |
Süreç Yalıtımını Bir Verim Avantajına Dönüştürmek
İlk bakışta SQLite'ı ayrı bir süreçte çalıştırmak, cgo üzerinden süreç içinde sqlite3_step çağırmaktan daha yavaş gibi görünebilir. Pratikte, bir sorgu birden fazla satır döndürdüğü anda tam tersi gerçekleşir.
Satır Satır cgo Çağrılarının Gizli Maliyeti
Geleneksel bir cgo SQLite sürücüsünde database/sql, yinelemeyi bir seferde bir satır ve bir sütun olacak şekilde yönetir. 4 sütunlu 1.000 satırın okunması şunları tetikler:
sqlite3_stepiçin 1.000cgoçağrısısqlite3_column_typeilesqlite3_column_int64/sqlite3_column_textiçin 4.000cgoçağrısı- Go zamanlayıcısı ile C yığını arasında binlerce ayrı sınır geçişi
İstek Boru Hattı ve 64 KiB Toplu Ön Getirme
gosuda.org/sqlite3, stmt.go, rows.go ve sqlite3/server/sqlite3_hooks.c dosyalarındaki iki mekanizma etrafında kablo protokolünü yeniden tasarlayarak satır başına sınır geçişlerini ortadan kaldırır:
1Go İstemcisi (stmt.go / rows.go) C11 Worker (sqlite3_hooks.c)
2----------------------------- ----------------------------
31. Tek uçuşta boru hattı:
4 ResetAsync + BindValuesAsync
5 + StmtInfoAsync + FetchRowsAsync(128) =======> reset -> bind -> stmt_info ->
6 128 satıra kadar sqlite3_step() döngüsü
7 <======= Toplu 1 (satır 1..128, 64 KiB slotta paketlenmiş)
82. Hemen ön getirmeyi başlat:
9 FetchRowsAsync(128) (Toplu 2) =======> 129..256 satırları için sqlite3_step() döngüsü
10 C, Toplu 2'yi adımlarken Go çağıran
11 kod, Toplu 1'deki 1..128 satırlarını tarar! <======= Toplu 2 paylaşımlı bellekte hazır
- Tek Uçuşlu Sorgu Boru Hattı (
stmt.go):QueryContextveyaStmt.QueryContext, salt okunur bir ifadeyi çalıştırdığında,s.query()işlevireset,bind_values,stmt_info(ilk çalıştırmada) vefetch_rowsişlemlerini tek bir paylaşımlı bellek uçuşunda eşzamansız olarak gönderir. Dört işlemin tümü C worker üzerinde art arda çalışır ve tek bir gidiş-dönüşte geri döner. - Sıkı C Toplu Döngüleri ve Eşzamansız Ön Getirme (
rows.go):fetch_rows(sqlite3_hooks.c) içinde C worker, yerel bir C döngüsündesqlite3_step(stmt)çağırır ve toplu sınırına, slotun dolmasına veyaSQLITE_DONEdurumuna ulaşılana kadar en fazlaFetchSize(varsayılan 128) satırı doğrudan 64 KiB paylaşımlı bellek yanıt slotuna kodlar.rows.ingest()Toplu 1'i çözer çözmez, ilk satırı çağırana döndürmeden önce Toplu 2 içinFetchRowsAsyncçağrısını başlatır. Go kodunuz Toplu 1'i tararken, C worker Toplu 2'yi başka bir CPU çekirdeğinde zaten adımlamaktadır.
Apple M4 (darwin/arm64) üzerinde, aynı SQLite veritabanı kullanılarak gosuda.org/sqlite3 (CGO_ENABLED=0) ile github.com/mattn/go-sqlite3 (CGO_ENABLED=1) karşılaştırıldığında, toplu işlemenin ve boru hattının etkisi görülmektedir:
| İş Yükü | gosuda.org/sqlite3 (CGO_ENABLED=0) | mattn/go-sqlite3 (CGO_ENABLED=1) | Göreli Hız |
|---|---|---|---|
Nokta Seçimi (QueryRow, birincil anahtar ile) | 3.52 µs/op (9 B, 1 ayırma) | 3.56 µs/op (232 B, 11 ayırma) | 1.01x (eşit, 11 kat az ayırma) |
100 Satırlık Tarama (rows.Next) | 20.41 µs/op (30.5 KB, 211 ayırma) | 36.34 µs/op (21.8 KB, 309 ayırma) | 1.78x daha hızlı |
1.000 Satırlık Tarama (rows.Next) | 125.92 µs/op (296 KB, 2.030 ayırma) | 349.10 µs/op (216 KB, 3.009 ayırma) | 2.77x daha hızlı |
| Hazırlanmış Ekleme (Otomatik İşlem) | 5.33 µs/op (0 B, 0 ayırma) | 5.88 µs/op (120 B, 5 ayırma) | 1.10x daha hızlı (0 ayırma) |
| 100 Satırlık İşlem Toplu İşlemi | 189.99 µs/op (16 B, 1 ayırma) | 191.50 µs/op (12.1 KB, 503 ayırma) | 1.01x (500 kat az ayırma) |
İptal, Yol Kumlaması ve Bağlantı Havuzu Kurtarma
İzole bir worker içinde SQLite çalıştırmak, sürücünün zaman aşımlarını, güvenilmeyen SQL'i ve dosya sistemi sınırlarını ele alma biçimini de güçlendirir:
- Deterministik Bağlam İptali (
conn.goveerrors.go): Her sürücü çağrısı hafif bircontext.AfterFunckoruyucusu (cn.guard) kurar. Sunucuda yavaş bir özyinelemeli sorgu veya kilit beklemesi çalışırken çağıranıncontext.Contextdeğeri iptal edilir veya süresi dolarsa, koruyucu bağlantıyı bozuk olarak işaretler, yerel oturum bağlantısını sonlandırır ve bant dışı birCancel()çerçevesi gönderir. C worker,sqlite3_stepişlemini engelleyensqlite3_interrupt(db)çağrısını hemen yapar; Go sürücüsü ise ortaya çıkanSQLITE_INTERRUPTdurumunucontext.Canceledveyacontext.DeadlineExceededdeğerlerine eşler vedatabase/sqlpaketine bağlantıyı havuzda değiştirmesi talimatını verir. - Dizin Kumlaması (
sqlsrv_pathrules.c): C worker, heropen_v2çağrısında--allow-diryol kurallarını uygular; sembolik bağlantıları çözümler ve izin verilen dizin dışına yapılan göreli..geçişlerini reddeder.sqlite3_load_extensionveSQLITE_LIMIT_ATTACHEDvarsayılan olarak devre dışıdır; böylece SQL enjeksiyonu veritabanı dizininden kaçamaz veya rastgele paylaşımlı nesneleri yükleyemez.
Depo Yapısı
Depo, en üst düzey paketin doğrudan kullanılabilir database/sql sürücüsünü sağlayacak, launcher/ ve sqlite3/ ise gelişmiş özel sunucu dağıtımları için erişilebilir kalacak şekilde düzenlenmiştir:
1gosuda.org/sqlite3
2├── driver.go / api.go # database/sql sürücü kaydı ("sqlite3") ve Connector API
3├── dsn.go # Temiz oda mattn/go-sqlite3 DSN ayrıştırıcısı ve pragma yapılandırması
4├── conn.go / tx.go # Oturum yaşam döngüsü, bağlam koruyucusu ve işlem durum makinesi
5├── stmt.go / rows.go # Tek uçuşlu istek boru hattı ve 64 KiB toplu ön getirme
6├── errors.go # Tiplenmiş SQLite birincil/genişletilmiş hata kodları ve sentinel eşleştirme
7├── autolaunch.go / hashes.go # Sıfır yapılandırmalı gömülü sunucu açığa çıkarma ve SHA-256 sabitleme
8├── internal/embedded/ # Platforma göre etiketlenmiş //go:embed ikili dosyaları (linux, darwin, windows)
9├── launcher/ # Özel sunucu dağıtımları için yapılandırılabilir süreç denetleyicisi
10└── sqlite3/ # Üretilmiş hqq-bindgen Go istemcisi ve C11 SQLite sunucu kaynağı
11 └── server/ # C11 sunucu uygulaması (sqlite3_hooks.c, sqlsrv_policy.c)
Başlarken
Modülü go get ile kurun ve veritabanınızı sql.Open("sqlite3", dsn) ile açın:
1go get gosuda.org/sqlite3@v0.3.3540100
- GitHub Deposu: https://github.com/gosuda/hqq-sqlite3
- DSN ve Pragma Referansı: docs/DSN.md
- Mimari ve Kablo Protokolü: docs/ARCHITECTURE.md