gosuda.org/sqlite3: Driver database/sql dengan CGO_ENABLED=0 melalui IPC Shared-Memory

gosuda.org/sqlite3 adalah driver database/sql untuk Go dengan CGO_ENABLED=0 yang menjalankan amalgamasi C SQLite 3.54.1 hulu di dalam proses pekerja terisolasi melalui ring memori bersama HQQ. Driver ini terdaftar dengan nama driver standar "sqlite3", mendukung parameter query DSN dari github.com/mattn/go-sqlite3, menyertakan binary server prebuilt yang telah diattest SLSA untuk enam target sistem operasi dan arsitektur, serta menggunakan request pipelining dengan prefetch batch 64 KiB sehingga performanya melampaui cgo in-process pada query multi-baris.
1go get gosuda.org/sqlite3@v0.3.3540100
Mengapa Driver SQLite Go Lain?
SQLite merupakan salah satu perangkat lunak yang paling luas digunakan di dunia, namun penggunaannya dari Go secara historis memaksa para insinyur memilih antara kesederhanaan build dan fidelitas engine.
Di satu sisi terdapat github.com/mattn/go-sqlite3, standar komunitas yang telah lama digunakan. Driver ini membungkus amalgamasi resmi C SQLite secara langsung melalui cgo. Anda memperoleh query planner C yang sesungguhnya, semantik konkurensi WAL yang lengkap, dan manajemen memori C yang dapat diprediksi. Konsekuensinya adalah CGO_ENABLED=1: build lokal dan pipeline CI menjadi lebih lambat, container image scratch atau distroless minimal memerlukan pustaka runtime C yang sesuai, kompilasi silang ke sistem operasi atau arsitektur CPU lain memerlukan toolchain C lintas platform yang lengkap, dan setiap korupsi memori di sisi C berbagi ruang alamat proses Go.
Di sisi lain terdapat port SQLite murni Go yang dihasilkan dengan menerjemahkan amalgamasi C secara mekanis menjadi kode sumber Go di atas runtime C yang diemulasi (libc). Driver tersebut memulihkan CGO_ENABLED=0, sehingga build menjadi sederhana. Namun, pada beban kerja yang berkelanjutan, heap C hasil terjemahan dan alokasi Go per baris meningkatkan tekanan garbage collection, serta kasus-kasus tepi yang halus dalam penguncian, busy timeout, atau interupsi statement dapat berbeda dari build C native.
Kami membangun gosuda.org/sqlite3 agar aplikasi Go tidak harus memilih antara CGO_ENABLED=0 dan engine C SQLite yang sesungguhnya. Dengan mengompilasi amalgamasi C SQLite 3.54.1 resmi ke dalam server memori bersama khusus (hqq-sqlite-server) dan berkomunikasi melalui gosuda.org/hqq, binary Go Anda dikompilasi dengan CGO_ENABLED=0 sambil tetap menjalankan C SQLite yang tidak dimodifikasi dan dioptimasi -O3 melintasi batas proses.
Eksekusi Tanpa Konfigurasi: Dari go get ke sql.Open
Mengganti github.com/mattn/go-sqlite3 dalam basis kode Go yang ada hanya memerlukan perubahan pada path blank import. Driver terdaftar sebagai "sqlite3" (dan "hqq-sqlite" untuk pemilihan eksplisit) serta menerima string query DSN yang sama:
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}
Binary Pekerja Tersemat yang Diattest SLSA
Pertanyaan alami ketika mendengar bahwa sebuah library menggunakan proses pekerja C adalah: "Apakah saya harus menginstal atau mengirimkan binary C terpisah bersama program Go saya?"
Tidak. gosuda.org/sqlite3 menyematkan binary hqq-sqlite-server prebuilt untuk keenam target utama secara langsung di dalam file Go bertag platform (internal/embedded/embed_<goos>_<goarch>.go). Karena build constraint Go hanya mengompilasi file yang sesuai dengan GOOS dan GOARCH target Anda, kompilasi silang GOOS=linux GOARCH=arm64 go build secara otomatis hanya menyematkan binary server linux/arm64 berukuran sekitar 1,4 MiB.
| Sistem Operasi Target | Arsitektur Target (GOARCH) | Binary Tersemat | Toolchain Build & 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 |
Ketika sql.Open membuka koneksi pertamanya, autolaunch.go mengekstrak binary tersemat ke direktori cache pengguna ($XDG_CACHE_HOME/hqq/bin/<sha256>/), memverifikasi digest SHA-256 kriptografisnya terhadap map ReleaseHashes yang dipin di hashes.go melalui gosuda.org/hqq/secureexec, lalu menjalankan pekerja dengan --parent-stdin sehingga masa hidupnya terikat secara ketat pada proses Go Anda.
Kompatibilitas DSN mattn/go-sqlite3 dengan Implementasi Ulang Bersih
Agar dapat berfungsi sebagai pengganti langsung yang sesungguhnya, parser DSN (dsn.go) mengimplementasikan replika bersih dari knob koneksi dan pragma yang digunakan di berbagai basis kode mattn/go-sqlite3:
| Parameter Query DSN | Nilai yang Diterima | Efek SQLite |
|---|---|---|
_busy_timeout / _timeout | Integer milidetik (misalnya 5000) | Memanggil sqlite3_busy_timeout(db, ms) saat sesi dibuka. |
_journal_mode / _journal | DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF | Mengeksekusi PRAGMA journal_mode = <mode>. |
_txlock | immediate, exclusive, deferred | Memilih BEGIN IMMEDIATE, BEGIN EXCLUSIVE, atau BEGIN DEFERRED untuk db.BeginTx. |
_foreign_keys / _fk | on, true, 1, off, false, 0 | Mengeksekusi PRAGMA foreign_keys = ON/OFF. |
_synchronous / _sync | 0/OFF, 1/NORMAL, 2/FULL, 3/EXTRA | Mengeksekusi PRAGMA synchronous = <level>. |
_cache_size | Integer halaman atau -KiB (misalnya -20000) | Mengeksekusi PRAGMA cache_size = <val>. |
_loc | auto atau zona waktu IANA (misalnya UTC, Asia/Seoul) | Mengurai kolom teks DATETIME/TIMESTAMP menjadi *time.Time pada lokasi target. |
mode | ro, rw, rwc, memory | Mengatur SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, atau SQLITE_OPEN_MEMORY. |
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuum | Boolean atau integer mode | Menerapkan PRAGMA sesi yang sesuai saat inisialisasi koneksi. |
Mengubah Isolasi Proses Menjadi Keunggulan Throughput
Sekilas, menjalankan SQLite dalam proses terpisah tampak pasti lebih lambat daripada memanggil sqlite3_step secara in-process melalui cgo. Dalam praktiknya, justru yang terjadi sebaliknya begitu sebuah query mengembalikan lebih dari satu baris.
Biaya Tersembunyi Pemanggilan cgo Baris demi Baris
Dalam driver SQLite cgo konvensional, database/sql melakukan iterasi satu baris dan satu kolom pada satu waktu. Membaca 1.000 baris dengan 4 kolom memicu:
- 1.000 pemanggilan
cgokesqlite3_step - 4.000 pemanggilan
cgokesqlite3_column_typedansqlite3_column_int64/sqlite3_column_text - Ribuan lintasan batas individual antara scheduler Go dan stack C
Request Pipelining dan Prefetch Batch 64 KiB
gosuda.org/sqlite3 menghilangkan lintasan batas per baris dengan merancang ulang wire protocol di sekitar dua mekanisme dalam stmt.go, rows.go, dan sqlite3/server/sqlite3_hooks.c:
1Go Client (stmt.go / rows.go) C11 Worker (sqlite3_hooks.c)
2----------------------------- ----------------------------
31. Pipeline dalam 1 flight:
4 ResetAsync + BindValuesAsync
5 + StmtInfoAsync + FetchRowsAsync(128) =======> reset -> bind -> stmt_info ->
6 loop sqlite3_step() hingga 128 baris
7 <======= Batch 1 (baris 1..128 dikemas dalam slot 64 KiB)
82. Segera jalankan prefetch:
9 FetchRowsAsync(128) (Batch 2) =======> loop sqlite3_step() untuk baris 129..256
10 Selagi C melakukan step Batch 2, pemanggil Go
11 memindai baris 1..128 dari Batch 1! <======= Batch 2 siap di shared memory
- Single-Flight Query Pipelining (
stmt.go): KetikaQueryContextatauStmt.QueryContextmenjalankan statement read-only,s.query()mengirimkanreset,bind_values,stmt_info(pada eksekusi pertama), danfetch_rowssecara asinkron dalam satu flight shared memory. Keempat operasi tersebut dieksekusi secara berurutan pada worker C dan dikembalikan dalam satu round trip. - Loop Batch C yang Ketat dan Prefetch Asinkron (
rows.go): Di dalamfetch_rows(sqlite3_hooks.c), worker C menjalankansqlite3_step(stmt)dalam loop C native, mengodekan hinggaFetchSizebaris (default 128) langsung ke dalam slot respons shared memory 64 KiB hingga batas batch tercapai, slot penuh, atauSQLITE_DONEterjadi. Segera setelahrows.ingest()men-decode Batch 1, ia menjalankanFetchRowsAsyncuntuk Batch 2 sebelum mengembalikan baris pertama kepada pemanggil. Selagi kode Go Anda memindai Batch 1, worker C sudah melakukan step Batch 2 pada core CPU lain.
Pada Apple M4 (darwin/arm64), perbandingan gosuda.org/sqlite3 (CGO_ENABLED=0) dengan github.com/mattn/go-sqlite3 (CGO_ENABLED=1) pada basis data SQLite yang sama memperlihatkan dampak batching dan pipelining:
| Beban Kerja | gosuda.org/sqlite3 (CGO_ENABLED=0) | mattn/go-sqlite3 (CGO_ENABLED=1) | Kecepatan Relatif |
|---|---|---|---|
Point Select (QueryRow berdasarkan PK) | 3,52 µs/op (9 B, 1 alokasi) | 3,56 µs/op (232 B, 11 alokasi) | 1,01x (setara, 11x lebih sedikit alokasi) |
Pemindaian 100 Baris (rows.Next) | 20,41 µs/op (30,5 KB, 211 alokasi) | 36,34 µs/op (21,8 KB, 309 alokasi) | 1,78x lebih cepat |
Pemindaian 1.000 Baris (rows.Next) | 125,92 µs/op (296 KB, 2.030 alokasi) | 349,10 µs/op (216 KB, 3.009 alokasi) | 2,77x lebih cepat |
| Prepared Insert (Autocommit) | 5,33 µs/op (0 B, 0 alokasi) | 5,88 µs/op (120 B, 5 alokasi) | 1,10x lebih cepat (0 alokasi) |
| Batch Transaksi 100 Baris | 189,99 µs/op (16 B, 1 alokasi) | 191,50 µs/op (12,1 KB, 503 alokasi) | 1,01x (500x lebih sedikit alokasi) |
Pembatalan, Sandboxing Path, dan Pemulihan Connection Pool
Menjalankan SQLite dalam worker terisolasi juga memperkuat cara driver menangani timeout, SQL yang tidak tepercaya, dan batas sistem berkas:
- Pembatalan Context yang Deterministik (
conn.godanerrors.go): Setiap pemanggilan driver memasang guardcontext.AfterFuncyang ringan (cn.guard). Jikacontext.Contextpemanggil dibatalkan atau deadline-nya habis selagi query rekursif yang lambat atau lock wait sedang berjalan di server, guard menandai koneksi sebagai rusak, mematikan tautan sesi lokal, dan mengirimkan frameCancel()out-of-band. Worker C segera memanggilsqlite3_interrupt(db), membebaskansqlite3_step, sementara driver Go memetakan statusSQLITE_INTERRUPTyang dihasilkan menjadicontext.Canceledataucontext.DeadlineExceededdan menginstruksikandatabase/sqluntuk mengganti koneksi tersebut dalam pool. - Sandboxing Direktori (
sqlsrv_pathrules.c): Worker C menerapkan aturan path--allow-dirpada setiap pemanggilanopen_v2, me-resolve symlink dan menolak traversal..relatif ke luar direktori yang diizinkan.sqlite3_load_extensiondanSQLITE_LIMIT_ATTACHEDdinonaktifkan secara default sehingga SQL injection tidak dapat keluar dari direktori basis data atau memuat shared object sembarang.
Tata Letak Repositori
Repositori disusun sedemikian rupa sehingga paket tingkat atas menyediakan driver database/sql yang dapat langsung digunakan, sementara launcher/ dan sqlite3/ tetap dapat diakses untuk deployment server kustom tingkat lanjut:
1gosuda.org/sqlite3
2├── driver.go / api.go # Registrasi driver database/sql ("sqlite3") dan API Connector
3├── dsn.go # Parser DSN mattn/go-sqlite3 hasil implementasi ulang bersih dan konfigurasi pragma
4├── conn.go / tx.go # Siklus hidup sesi, context guard, dan state machine transaksi
5├── stmt.go / rows.go # Request pipelining single-flight dan prefetch batch 64 KiB
6├── errors.go # Kode error primer/extended SQLite bertipe dan pencocokan sentinel
7├── autolaunch.go / hashes.go # Materialisasi server tersemat tanpa konfigurasi dan pinning SHA-256
8├── internal/embedded/ # Binary //go:embed bertag platform (linux, darwin, windows)
9├── launcher/ # Supervisor proses yang dapat dikonfigurasi untuk deployment server kustom
10└── sqlite3/ # Klien Go hqq-bindgen yang dihasilkan dan source server C11 SQLite
11 └── server/ # Implementasi server C11 (sqlite3_hooks.c, sqlsrv_policy.c)
Memulai
Pasang modul dengan go get dan buka basis data Anda dengan sql.Open("sqlite3", dsn):
1go get gosuda.org/sqlite3@v0.3.3540100
- Repositori GitHub: https://github.com/gosuda/hqq-sqlite3
- Referensi DSN & Pragma: docs/DSN.md
- Arsitektur & Wire Protocol: docs/ARCHITECTURE.md