gosuda.org/zstd: Drop-In DataDog/zstd Compression with Zero CGO and Crash Isolation
gosuda.org/zstd è una libreria di compressione Go con CGO_ENABLED=0 che esegue il motore C ufficiale di Zstandard (libzstd 1.5.7) all'interno di un processo lavoratore isolato tramite memoria condivisa HQQ. Essa fornisce un'API di sostituzione immediata (drop-in replacement) per github.com/DataDog/zstd (inclusi Compress/Decompress istantanei, io.Writer/io.Reader in streaming, dizionari pre-digeriti e contesti riutilizzabili a zero allocazioni), mantenendo al contempo l'esecuzione C completamente al di fuori dello spazio di indirizzamento del runtime di Go.
1go get gosuda.org/zstd@v0.3.10507
Perché la Decompressione Appartiene all'Esterno del Vostro Heap di Go
nei backend di produzione, la compressione e la decompressione si trovano direttamente sul confine degli input non attendibili. I collettori di log, i broker di messaggi, i gateway RPC e i motori di archiviazione decomprimono gigabyte di frame forniti dall'esterno ogni minuto.
Molti team di Go scelgono github.com/DataDog/zstd perché si collega direttamente al libzstd C upstream: l'implementazione in C offre un elevato throughput su singolo core, stretti rapporti di compressione del dizionario su record piccoli e aree di memoria C esterne all'heap (ZSTD_CCtx e ZSTD_DCtx) che non aumentano i tempi di scansione della garbage collection di Go.
Collegare libzstd a un servizio Go tramite cgo, tuttavia, comporta due problemi operativi:
- Attrito di Compilazione e Distribuzione: Proprio come
go-sqlite3,DataDog/zstdrichiedeCGO_ENABLED=1e una toolchain di compilazione C di destinazione. La compilazione incrociata (cross-compiling) dei servizi o la costruzione di binari statici perlinux/arm64owindows/arm64richiede sysroot C esterne. - Spazio di Indirizzamento Condiviso su Input Non Attendibili: Decomprimere payload corrotti o ostili all'interno dello stesso spazio di indirizzamento virtuale del vostro server Go significa che qualsiasi bug nel codice nativo C o nella gestione personalizzata del dizionario può terminare l'intero processo Go.
gosuda.org/zstd mantiene l'esatto motore C upstream libzstd 1.5.7 e la familiare API di github.com/DataDog/zstd, ma sposta l'esecuzione C in un processo figlio verificato (zstd_server) connesso al vostro programma Go tramite memoria condivisa a livello di riga di cache (cache-line).
Sostituzione Immediata per github.com/DataDog/zstd
La migrazione da github.com/DataDog/zstd richiede la modifica del solo percorso di importazione in "gosuda.org/zstd". Non è necessaria alcuna installazione di binari esterni o codice di configurazione: alla prima chiamata, gosuda.org/zstd materializza un binario zstd_server incorporato e attestato SLSA per la vostra piattaforma, ne verifica il digest SHA-256 tramite gosuda.org/hqq/secureexec e apre un pool di sessioni in memoria condivisa.
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log"
8
9 "gosuda.org/zstd"
10)
11
12func main() {
13 // 1. Compressione / Decompressione istantanea (rispecchia github.com/DataDog/zstd)
14 src := []byte("shared-memory zstd payload without cgo")
15 compressed, err := zstd.CompressLevel(nil, src, zstd.BestSpeed)
16 if err != nil {
17 log.Fatal(err)
18 }
19 plain, err := zstd.Decompress(nil, compressed)
20 if err != nil {
21 log.Fatal(err)
22 }
23 fmt.Printf("one-shot: %d bytes -> %d bytes (%s)\n", len(src), len(compressed), plain)
24
25 // 2. Writer e Reader in streaming con supporto per dizionari
26 var buf bytes.Buffer
27 w := zstd.NewWriterLevel(&buf, zstd.DefaultCompression)
28 if _, err := w.Write(src); err != nil {
29 log.Fatal(err)
30 }
31 if err := w.Close(); err != nil {
32 log.Fatal(err)
33 }
34
35 r := zstd.NewReader(&buf)
36 defer r.Close()
37 streamed, err := io.ReadAll(r)
38 if err != nil {
39 log.Fatal(err)
40 }
41 fmt.Printf("streamed: %s\n", streamed)
42}
Piattaforme Supportate e Binari Incorporati
Tutte e sei le piattaforme di destinazione vengono fornite con binari di lavoro precompilati e associati a SHA-256 incorporati tramite tag di compilazione specifici per la piattaforma (internal/embedded/embed_<goos>_<goarch>.go):
| SO di Destinazione | Architettura di Destinazione (GOARCH) | Binario Incorporato | Dimensione del Binario | Toolchain di Compilazione e Provenienza |
|---|---|---|---|---|
| Linux | amd64, arm64 | zstd_server_linux_<arch> | ~770 - 880 KiB | GCC -O3, Provenienza SLSA Build L3 |
| macOS | arm64, amd64 | zstd_server_darwin_<arch> | ~660 - 795 KiB | AppleClang -O3, Provenienza SLSA Build L3 |
| Windows | amd64 | zstd_server_windows_amd64.exe | ~818 KiB | MSVC -O2, Provenienza SLSA Build L3 |
| Windows | arm64 | zstd_server_windows_arm64.exe | ~551 KiB | zig cc -target aarch64-windows-gnu -O3, SLSA L3 |
All'Interno del Percorso Dati in Memoria Condivisa
Per mantenere il throughput competitivo con cgo in-process, gosuda.org/zstd evita la serializzazione tramite socket e le allocazioni nell'heap sul percorso critico (hot path).
1. Codifica degli Slot a Zero Allocazioni (zstd_ctx.go e c/zstd_hooks.c)
Quando utilizzate un zstd.Ctx riutilizzabile (o quando le funzioni a livello di pacchetto Compress e Decompress prendono in prestito un Ctx dal pool di sessioni interno), i dati fluiscono direttamente attraverso lo slot mappato StandardLink:
1Go Caller (zstd.Ctx) Shared-Memory Slot C11 Worker (zstd_hooks.c)
2-------------------- ------------------ -------------------------
3src []byte -------------------------> Request Slot Payload -------------> ZSTD_compressCCtx(
4 cctx, resp_slot, src, level)
5dst []byte <------------------------- Response Slot Payload <------------- Writes compressed frame directly
6 into shared response slot
- Il client generato da
hqq-bindgencopiasrcdirettamente nello slot di richiesta mappato in memoria e inverte la parola di sequenzaSeqLock. - Il lavoratore C legge
srcdirettamente dallo slot di richiesta e passa il puntatore al payload dello slot di risposta direttamente come buffer di destinazione aZSTD_compressCCtxoZSTD_decompressDCtx. - Il client Go copia la slice risultante dallo slot di risposta nella slice
dstfornita dal chiamante. A regime,ctx.CompressTo(dst, src, level)ectx.DecompressInto(dst, src)eseguono0 allocs/op.
I dizionari pre-digeriti (CDict e DDict) risiedono sul lavoratore C dietro handle a 64 bit con controllo di generazione (handle CDict, handle DDict in zstd.idl). La creazione di un dizionario una sola volta tramite ctx.NewCDict(rawDict, level) analizza le tabelle del dizionario all'interno del lavoratore C e restituisce un handle leggero che può essere riutilizzato attraverso migliaia di chiamate senza dover inviare nuovamente i byte del dizionario.
2. Ammortamento dell'Overhead IPC con il Framing in Blocco (zstd_bulk.go)
Per payload di grandi dimensioni (16 KiB e superiori), il ciclo di andata e ritorno (round trip) in memoria condivisa di circa 200 ns è trascurabile rispetto al tempo di CPU impiegato all'interno di libzstd. Per carichi di lavoro che comprimono migliaia di piccoli messaggi da 100 byte a 1 KiB contemporaneamente, tuttavia, pagare anche solo 200 ns per elemento si fa sentire.
gosuda.org/zstd fornisce CompressBulk e DecompressBulk su BulkCtx (zstd_bulk.go) per eliminare i cicli di andata e ritorno per singolo elemento:
1bctx, err := client.NewBulkCtx(1 << 20)
2if err != nil {
3 log.Fatal(err)
4}
5defer bctx.Close()
6
7items := [][]byte{
8 []byte(`{"event":"login","user":101}`),
9 []byte(`{"event":"purchase","user":101,"amount":4200}`),
10 []byte(`{"event":"logout","user":101}`),
11}
12compressedItems, err := bctx.CompressBulk(nil, items, zstd.DefaultCompression)
CompressBulk impacchetta l'intero batch [][]byte in un singolo frame di richiesta con prefisso di lunghezza nella memoria condivisa. Il lavoratore C esegue un ciclo su ogni elemento in C (compress_bulk in c/zstd_hooks.c), scrive tutti i frame compressi in modo contiguo nello slot di risposta e restituisce l'intero batch in un unico ciclo di andata e ritorno in memoria condivisa.
3. Sopravvivere alla Terminazione del Lavoratore Senza Arrestare in Modo Anomalo il Chiamante
Poiché zstd_server viene eseguito in un processo del sistema operativo separato, un errore irreversibile all'interno del lavoratore C non blocca mai la vostra applicazione Go:
- Se
zstd_serverviene terminato dall'esterno (ad esempio, daSIGKILLo da un evento OOM), il livello di trasporto HQQ rileva immediatamente il peer interrotto e restituisce un*rt.TransportErrortipizzato dalla chiamataCompressoDecompressin corso. - Le funzioni a livello di pacchetto (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) rilevano automaticamente che il lavoratore predefinito condiviso è scomparso, generano una nuova istanza verificata dizstd_serveralla chiamata successiva e riprendono il normale funzionamento.
Struttura del Repository
La codebase separa la facciata DataDog/zstd integrata dalle primitive di sessione e in blocco a basso livello di hqq-bindgen:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # API di Compress/Decompress integrata, pool di recupero automatico e hash SHA-256
3├── zstd_stream.go # Writer/Reader in streaming (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Wrapper Ctx, CDict e DDict a zero allocazioni sopra hqq-bindgen
5├── zstd_bulk.go # Framing CompressBulk e DecompressBulk a singolo round-trip
6├── zstd.go # Client Go generato da hqq-bindgen per il servizio IDL di zstd
7├── zstd.idl # Schema IDL HQQ che definisce Context, CDict, DDict e metodi RPC
8├── internal/embedded/ # Binari //go:embed zstd_server con tag di piattaforma (6 destinazioni SO/Arch)
9└── c/ # Implementazione del server C11 (zstd_server.c, zstd_hooks.c, main.c)
Per Iniziare
Installate gosuda.org/zstd con go get e usatelo con CGO_ENABLED=0:
1go get gosuda.org/zstd@v0.3.10507
- Repository GitHub: https://github.com/gosuda/hqq-zstd
- Architettura e Benchmark: docs/ARCHITECTURE.md
- Runtime Principale HQQ: https://gosuda.org/hqq