GoSuda

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

By Lemon Mint views ...

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 TargetArsitektur Target (GOARCH)Binary TersematToolchain Build & Provenance
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

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 DSNNilai yang DiterimaEfek SQLite
_busy_timeout / _timeoutInteger milidetik (misalnya 5000)Memanggil sqlite3_busy_timeout(db, ms) saat sesi dibuka.
_journal_mode / _journalDELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFFMengeksekusi PRAGMA journal_mode = <mode>.
_txlockimmediate, exclusive, deferredMemilih BEGIN IMMEDIATE, BEGIN EXCLUSIVE, atau BEGIN DEFERRED untuk db.BeginTx.
_foreign_keys / _fkon, true, 1, off, false, 0Mengeksekusi PRAGMA foreign_keys = ON/OFF.
_synchronous / _sync0/OFF, 1/NORMAL, 2/FULL, 3/EXTRAMengeksekusi PRAGMA synchronous = <level>.
_cache_sizeInteger halaman atau -KiB (misalnya -20000)Mengeksekusi PRAGMA cache_size = <val>.
_locauto atau zona waktu IANA (misalnya UTC, Asia/Seoul)Mengurai kolom teks DATETIME/TIMESTAMP menjadi *time.Time pada lokasi target.
modero, rw, rwc, memoryMengatur SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, atau SQLITE_OPEN_MEMORY.
_case_sensitive_like, _query_only, _recursive_triggers, _secure_delete, _AutoVacuumBoolean atau integer modeMenerapkan 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 cgo ke sqlite3_step
  • 4.000 pemanggilan cgo ke sqlite3_column_type dan sqlite3_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
  1. Single-Flight Query Pipelining (stmt.go): Ketika QueryContext atau Stmt.QueryContext menjalankan statement read-only, s.query() mengirimkan reset, bind_values, stmt_info (pada eksekusi pertama), dan fetch_rows secara asinkron dalam satu flight shared memory. Keempat operasi tersebut dieksekusi secara berurutan pada worker C dan dikembalikan dalam satu round trip.
  2. Loop Batch C yang Ketat dan Prefetch Asinkron (rows.go): Di dalam fetch_rows (sqlite3_hooks.c), worker C menjalankan sqlite3_step(stmt) dalam loop C native, mengodekan hingga FetchSize baris (default 128) langsung ke dalam slot respons shared memory 64 KiB hingga batas batch tercapai, slot penuh, atau SQLITE_DONE terjadi. Segera setelah rows.ingest() men-decode Batch 1, ia menjalankan FetchRowsAsync untuk 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 Kerjagosuda.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 Baris189,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.go dan errors.go): Setiap pemanggilan driver memasang guard context.AfterFunc yang ringan (cn.guard). Jika context.Context pemanggil 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 frame Cancel() out-of-band. Worker C segera memanggil sqlite3_interrupt(db), membebaskan sqlite3_step, sementara driver Go memetakan status SQLITE_INTERRUPT yang dihasilkan menjadi context.Canceled atau context.DeadlineExceeded dan menginstruksikan database/sql untuk mengganti koneksi tersebut dalam pool.
  • Sandboxing Direktori (sqlsrv_pathrules.c): Worker C menerapkan aturan path --allow-dir pada setiap pemanggilan open_v2, me-resolve symlink dan menolak traversal .. relatif ke luar direktori yang diizinkan. sqlite3_load_extension dan SQLITE_LIMIT_ATTACHED dinonaktifkan 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