GoSuda

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

By Lemon Mint views ...

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 SistemiHedef Mimari (GOARCH)Gömülü İkili DosyaDerleme Araç Zinciri ve Köken
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

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 ParametresiKabul Edilen DeğerlerSQLite Etkisi
_busy_timeout / _timeoutMilisaniye cinsinden tamsayı (ör. 5000)Oturum açılışında sqlite3_busy_timeout(db, ms) çağrısını yapar.
_journal_mode / _journalDELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFFPRAGMA journal_mode = <mode> ifadesini çalıştırır.
_txlockimmediate, exclusive, deferreddb.BeginTx için BEGIN IMMEDIATE, BEGIN EXCLUSIVE veya BEGIN DEFERRED seçer.
_foreign_keys / _fkon, true, 1, off, false, 0PRAGMA foreign_keys = ON/OFF ifadesini çalıştırır.
_synchronous / _sync0/OFF, 1/NORMAL, 2/FULL, 3/EXTRAPRAGMA synchronous = <level> ifadesini çalıştırır.
_cache_sizeTamsayı sayfa veya -KiB (ör. -20000)PRAGMA cache_size = <val> ifadesini çalıştırır.
_locauto veya IANA saat dilimi (ör. UTC, Asia/Seoul)DATETIME/TIMESTAMP metin sütunlarını hedef konumda *time.Time değerlerine ayrıştırır.
modero, rw, rwc, memorySQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE veya SQLITE_OPEN_MEMORY bayraklarını ayarlar.
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuumBoolean 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_step için 1.000 cgo çağrısı
  • sqlite3_column_type ile sqlite3_column_int64 / sqlite3_column_text için 4.000 cgo ç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
  1. Tek Uçuşlu Sorgu Boru Hattı (stmt.go): QueryContext veya Stmt.QueryContext, salt okunur bir ifadeyi çalıştırdığında, s.query() işlevi reset, bind_values, stmt_info (ilk çalıştırmada) ve fetch_rows iş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.
  2. 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ünde sqlite3_step(stmt) çağırır ve toplu sınırına, slotun dolmasına veya SQLITE_DONE durumuna ulaşılana kadar en fazla FetchSize (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çin FetchRowsAsync ç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 İşlemi189.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.go ve errors.go): Her sürücü çağrısı hafif bir context.AfterFunc koruyucusu (cn.guard) kurar. Sunucuda yavaş bir özyinelemeli sorgu veya kilit beklemesi çalışırken çağıranın context.Context değ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ışı bir Cancel() çerçevesi gönderir. C worker, sqlite3_step işlemini engelleyen sqlite3_interrupt(db) çağrısını hemen yapar; Go sürücüsü ise ortaya çıkan SQLITE_INTERRUPT durumunu context.Canceled veya context.DeadlineExceeded değerlerine eşler ve database/sql paketine bağlantıyı havuzda değiştirmesi talimatını verir.
  • Dizin Kumlaması (sqlsrv_pathrules.c): C worker, her open_v2 çağrısında --allow-dir yol 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_extension ve SQLITE_LIMIT_ATTACHED varsayı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