GoSuda

gosuda.org/sqlite3: Paylaşımlı Bellek Üzerinde Yerel C SQLite Çalıştıran Doğrudan Uyumlu (Drop-In) bir database/sql Sürücüsü

By Lemon Mint
views ...

gosuda.org/sqlite3, Go için yalıtılmış bir işçi (worker) süreci üzerinde HQQ paylaşımlı bellek halkaları aracılığıyla üst akım C SQLite 3.54.1 birleştirilmiş (amalgamation) sürümünü çalıştıran bir CGO_ENABLED=0 database/sql sürücüsüdür. Standart "sqlite3" sürücü adı altında kayıt olur, github.com/mattn/go-sqlite3 DSN sorgu parametrelerini destekler, altı işletim sistemi ve mimari hedefi için SLSA onaylı önceden derlenmiş sunucu ikililerini (binaries) gömer ve çok satırlı sorgularda süreç içi cgo başarımını geride bırakmak için 64 KiB toplu (batch) ön getirme (prefetching) ile istek ardışık düzeni (request pipelining) kullanır.

1go get gosuda.org/sqlite3@v0.3.3540100

Neden Başka Bir Go SQLite Sürücüsü?

SQLite, dünyada en çok dağıtımı yapılan yazılımlardan biridir; ancak onu Go içinden kullanmak, tarihsel olarak mühendisleri derleme basitliği ile motor sadakati arasında rahatsız edici bir seçim yapmaya zorlamıştır.

Bir tarafta uzun süredir topluluk standardı olan github.com/mattn/go-sqlite3 yer alır. Resmi C SQLite birleşimini doğrudan cgo aracılığıyla sarar. Gerçek C sorgu planlayıcısına, tam WAL eşzamanlılık semantiğine ve öngörülebilir C bellek yönetimine sahip olursunuz. Bunun bedeli CGO_ENABLED=1 değeridir: yerel derlemeler ve CI boru hatları yavaşlar, minimal scratch veya distroless konteyner imajları eşleşen C çalışma zamanı kütüphanelerini gerektirir, başka bir işletim sistemine veya CPU mimarisine çapraz derleme (cross-compile) yapmak eksiksiz bir C çapraz araç zinciri gerektirir ve C içerisindeki herhangi bir bellek bozulması Go süreç adres alanını paylaşır.

Diğer tarafta ise C birleşimini öykünülmüş bir C çalışma zamanı (libc) üzerinde Go kaynak koduna mekanik olarak çevirerek oluşturulan saf Go (pure-Go) SQLite portları yer alır. Bunlar, derlemeleri önemsiz hale getiren CGO_ENABLED=0 özelliğini geri getirir. Bununla birlikte, sürdürülebilir iş yükleri altında, çevrilen C yığını (heap) ve satır başına Go atıkları çöp toplayıcı (garbage collection) baskısını artırır ve kilitleme, meşgul zaman aşımı (busy timeouts) veya ifade kesintisi (statement interruption) gibi ince uç durumlar yerel C derlemelerinden sapabilir.

Go uygulamalarının CGO_ENABLED=0 ile gerçek C SQLite motoru arasında seçim yapmak zorunda kalmaması için gosuda.org/sqlite3 kütüphanesini geliştirdik. Resmi SQLite 3.54.1 C birleşimini ö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 optimize edilmiş C SQLite'ı bir süreç sınırının ötesinde yürütür.

Sıfır Yapılandırmalı Yürütme: go get komutundan sql.Open komutuna

Mevcut bir Go kod tabanında github.com/mattn/go-sqlite3 kütüphanesini değiştirmek yalnızca boş içe aktarma (blank import) yolunun değiştirilmesini gerektirir. Sürücü "sqlite3" (ve açık seçim için "hqq-sqlite") olarak kaydolur ve aynı DSN sorgu dizelerini 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 Onaylı İşçi İkilileri

Bir kütüphanenin C işçi süreci kullandığı duyulduğunda akla gelen doğal bir soru şudur: "Go programımın yanında ayrı bir C ikili dosyası kurmak veya dağıtmak zorunda mıyım?"

Hayır. gosuda.org/sqlite3, platform etiketli Go dosyalarının (internal/embedded/embed_<goos>_<goarch>.go) içine doğrudan altı ana hedef için önceden derlenmiş hqq-sqlite-server ikililerini 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 ile çapraz derleme yapmak otomatik olarak yalnızca ~1.4 MiB boyutundaki linux/arm64 sunucu ikilisini gömer.

Hedef İşletim SistemiHedef Mimari (GOARCH)Gömülü İkili DosyaDerleme Araç Zinciri ve Menşei (Provenance)
Linuxamd64, arm64hqq-sqlite-server_linux_<arch>GCC -O3, SLSA Derleme L3 Menşei
macOSarm64, amd64hqq-sqlite-server_darwin_<arch>AppleClang -O3, SLSA Derleme L3 Menşei
Windowsamd64hqq-sqlite-server_windows_amd64.exeMSVC -O2, SLSA Derleme L3 Menşei
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>/) çıkarır, kriptografik SHA-256 özetini gosuda.org/hqq/secureexec aracılığıyla hashes.go içindeki sabitlenmiş ReleaseHashes haritasına göre doğrular ve ömrü kesin olarak Go sürecinize bağlı olacak şekilde işçiyi --parent-stdin ile başlatır.

Temiz Oda (Clean-Room) mattn/go-sqlite3 DSN Uyumluluğu

Gerçek bir doğrudan değiştirme (drop-in replacement) olarak çalışmak 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 / _timeoutTamsayı milisaniye (örn. 5000)Oturum açıldığında sqlite3_busy_timeout(db, ms) fonksiyonunu çağırır.
_journal_mode / _journalDELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFFPRAGMA journal_mode = <mode> komutunu yürütü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 komutunu yürütür.
_synchronous / _sync0/OFF, 1/NORMAL, 2/FULL, 3/EXTRAPRAGMA synchronous = <level> komutunu yürütür.
_cache_sizeTamsayı sayfa veya -KiB (örn. -20000)PRAGMA cache_size = <val> komutunu yürütür.
_locauto veya IANA saat dilimi (örn. UTC, Asia/Seoul)DATETIME/TIMESTAMP metin sütunlarını hedef konumda *time.Time değerine ayrıştırır.
modero, rw, rwc, memorySQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE veya SQLITE_OPEN_MEMORY ayarlarını yapar.
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuumBoole veya mod tamsayılarıBağlantı başlatıldığında ilgili oturum PRAGMA ayarını 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 aracılığıyla süreç içinde sqlite3_step çağrısı yapmaktan daha yavaş olmalı gibi görünür. Uygulamada, bir sorgu tek bir satırdan fazlasını 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 her seferinde bir satır ve bir sütun olmak üzere yürütür. 4 sütunlu 1,000 satırı okumak şunları tetikler:

  • sqlite3_step için 1,000 cgo çağrısı
  • sqlite3_column_type ve 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 bireysel sınır geçişi

İstek Ardışık Düzeni ve 64 KiB Toplu Ön Getirme

gosuda.org/sqlite3, stmt.go, rows.go ve sqlite3/server/sqlite3_hooks.c içerisindeki 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 İşçisi (sqlite3_hooks.c)
 2-----------------------------                       ----------------------------
 31. Tek uçuşta (1 flight) ardışık düzen:
 4   ResetAsync + BindValuesAsync
 5   + StmtInfoAsync + FetchRowsAsync(128)  =======>  reset -> bind -> stmt_info ->
 6                                                    128 satıra kadar sqlite3_step() döngüsü
 7                                          <=======  Toplu 1 (64 KiB yuvada paketlenmiş 1..128. satırlar)
 82. Hemen ön getirmeyi tetikle:
 9   FetchRowsAsync(128) (Toplu 2)          =======>  129..256. satırlar için sqlite3_step() döngüsü
10   C, Toplu 2'yi işlerken Go çağıranı
11   Toplu 1'den 1..128. satırları tarar!   <=======  Toplu 2 paylaşımlı bellekte hazır
  1. Tek Uçuşlu Sorgu Ardışık Düzeni (stmt.go): QueryContext veya Stmt.QueryContext salt okunur bir ifade çalıştırdığında, s.query() işlevini reset, bind_values, stmt_info (ilk yürütmede) ve fetch_rows işlemlerini tek bir paylaşımlı bellek uçuşunda eşzamansız (asynchronously) olarak gönderir. Dört işlem de arka arkaya C işçisinde yürütülür ve tek bir gidiş-dönüşte (round trip) 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 işçisi, toplu sınırına ulaşılana, yuva dolana veya SQLITE_DONE oluşana kadar yerel bir C döngüsünde sqlite3_step(stmt) çalıştırarak FetchSize (varsayılan 128) kadar satırı doğrudan 64 KiB paylaşımlı bellek yanıt yuvasına kodlar. rows.ingest() Toplu 1'i kodunu çözer çözmez, ilk satırı çağırana döndürmeden önce Toplu 2 için FetchRowsAsync çağrısını tetikler. Go kodunuz Toplu 1'i tararken, C işçisi başka bir CPU çekirdeğinde halihazırda Toplu 2'yi işlemektedir.

Apple M4 (darwin/arm64) üzerinde, gosuda.org/sqlite3 (CGO_ENABLED=0) ile github.com/mattn/go-sqlite3 (CGO_ENABLED=1) kütüphanelerini aynı SQLite veritabanı üzerinde karşılaştırmak, toplu işleme ve ardışık düzen etkisini gösterir:

İş Yükügosuda.org/sqlite3 (CGO_ENABLED=0)mattn/go-sqlite3 (CGO_ENABLED=1)Göreceli Hız
Nokta Seçimi (PK ile QueryRow)3.52 µs/op (9 B, 1 tahsis)3.56 µs/op (232 B, 11 tahsis)1.01x (eşdeğer, 11 kat daha az tahsis)
100 Satırlık Tarama (rows.Next)20.41 µs/op (30.5 KB, 211 tahsis)36.34 µs/op (21.8 KB, 309 tahsis)1.78x daha hızlı
1,000 Satırlık Tarama (rows.Next)125.92 µs/op (296 KB, 2,030 tahsis)349.10 µs/op (216 KB, 3,009 tahsis)2.77x daha hızlı
Hazırlanmış Ekleme (Otomatik İşlem Onayı)5.33 µs/op (0 B, 0 tahsis)5.88 µs/op (120 B, 5 tahsis)1.10x daha hızlı (0 tahsis)
100 Satırlık İşlem Grubu (Transaction Batch)189.99 µs/op (16 B, 1 tahsis)191.50 µs/op (12.1 KB, 503 tahsis)1.01x (500 kat daha az tahsis)

İptal, Yol Korumalı Alanı (Sandboxing) ve Bağlantı Havuzu Kurtarma

SQLite'ı yalıtılmış bir işçide çalıştırmak ayrıca sürücünün zaman aşımcılarını, güvenilmeyen SQL'i ve dosya sistemi sınırlarını ele alma biçimini güçlendirir:

  • Deterministik Bağlam İptali (conn.go ve errors.go): Her sürücü çağrısı hafif bir context.AfterFunc koruması (cn.guard) devreye sokar. Sunucuda yavaş bir özyinelemeli (recursive) sorgu veya kilit bekletmesi çalışırken çağıranın context.Context değeri iptal edilirse veya zaman aşımı süresi dolarsa, koruma bağlantıyı sorunlu olarak işaretler, yerel oturum bağlantısını sonlandırır ve bant dışı (out-of-band) bir Cancel() çerçevesi gönderir. C işçisi derhal sqlite3_interrupt(db) çağrısı yaparak sqlite3_step kilidini çözerken, Go sürücüsü ortaya çıkan SQLITE_INTERRUPT durumunu context.Canceled veya context.DeadlineExceeded hatasıyla eşler ve database/sql kütüphanesine havuzdaki bağlantıyı değiştirmesini söyler.
  • Dizin Korumalı Alanı (sqlsrv_pathrules.c): C işçisi her open_v2 çağrısında --allow-dir yol kurallarını zorunlu kılar; sembolik bağlantıları (symlinks) çözümler ve izin verilen dizinin dışındaki göreceli .. geçişlerini reddeder. sqlite3_load_extension ve SQLITE_LIMIT_ATTACHED varsayılan olarak devre dışı bırakılmıştır, böylece SQL enjeksiyonu veritabanı dizininden kaçamaz veya rastgele paylaşımlı nesneler yükleyemez.

Depo Düzeni (Repository Layout)

Depo, üst düzey paket doğrudan database/sql sürücüsünü sağlarken, launcher/ ve sqlite3/ dizinlerinin gelişmiş özel sunucu dağıtımları için erişilebilir kalacağı şekilde yapılandırılmıştır:

 1gosuda.org/sqlite3
 2├── driver.go / api.go         # database/sql sürücü kaydı ("sqlite3") ve Connector API
 3├── dsn.go                     # Clean-room 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 koruması ve işlem durum makinesi
 5├── stmt.go / rows.go          # Tek uçuşlu istek ardışık düzeni ve 64 KiB toplu ön getirme
 6├── errors.go                  # Tiplendirilmiş SQLite birincil/genişletilmiş hata kodları ve sinyal eşleme
 7├── autolaunch.go / hashes.go  # Sıfır yapılandırmalı gömülü sunucu oluşturma ve SHA-256 sabitleme
 8├── internal/embedded/         # Platform etiketli //go:embed ikilileri (linux, darwin, windows)
 9├── launcher/                  # Özel sunucu dağıtımları için yapılandırılabilir süreç denetleyicisi
10└── sqlite3/                   # Üretilen 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