gosuda.org/zstd: Drop-In DataDog/zstd-compressie zonder CGO en met crashisolatie

gosuda.org/zstd is een CGO_ENABLED=0 Go-compressiebibliotheek die de officiële C Zstandard-engine (libzstd 1.5.7) uitvoert binnen een geïsoleerd workerproces via gedeeld geheugen van HQQ. Het biedt een drop-in vervangende API voor github.com/DataDog/zstd (inclusief eenmalige Compress/Decompress, streaming io.Writer/io.Reader, vooraf verwerkte woordenboeken en herbruikbare contexten zonder allocaties), terwijl de C-uitvoering volledig buiten de adresruimte van de Go-runtime blijft.
1go get gosuda.org/zstd@v0.3.10507
Waarom decompressie buiten je Go-heap hoort
In productiebackends bevinden compressie en decompressie zich direct op de grens met onbetrouwbare invoer. Log-collectors, berichtenbrokers, RPC-gateways en opslagengines decomprimeren elke minuut gigabytes aan extern aangeleverde frames.
Veel Go-teams kiezen voor github.com/DataDog/zstd omdat het rechtstreeks koppelt aan de upstream C-bibliotheek libzstd: de C-implementatie levert een hoge doorvoer per core, sterke dictionary-compressieratio's op kleine records, en off-heap C-arena's (ZSTD_CCtx en ZSTD_DCtx) die de scantijd van de Go-garbagecollector niet verhogen.
Het koppelen van libzstd aan een Go-service via cgo brengt echter twee operationele knelpunten met zich mee:
- Frictie bij build en deployment: Net als
go-sqlite3vereistDataDog/zstdCGO_ENABLED=1en een doel-C-compilertoolchain. Cross-compilatie van services of het bouwen van statische binaries voorlinux/arm64ofwindows/arm64vereist externe C-sysroots. - Gedeelde adresruimte bij onbetrouwbare invoer: Het decomprimeren van beschadigde of kwaadwillige payloads binnen dezelfde virtuele adresruimte als je Go-server betekent dat elke bug in native C-code of in de afhandeling van aangepaste woordenboeken het volledige Go-proces kan doen eindigen.
gosuda.org/zstd behoudt de exacte upstream C-engine libzstd 1.5.7 en de vertrouwde API van github.com/DataDog/zstd, maar verplaatst de C-uitvoering naar een geverifieerd childproces (zstd_server) dat via cache-line-gedeeld geheugen verbonden is met je Go-programma.
Drop-in vervanging voor github.com/DataDog/zstd
Migreren vanuit github.com/DataDog/zstd vereist alleen het wijzigen van het importpad naar "gosuda.org/zstd". Er is geen externe binaire installatie of installatiecode nodig: bij de eerste aanroep materialiseert gosuda.org/zstd een ingebedde SLSA-geattesteerde zstd_server-binary voor jouw platform, verifieert de SHA-256-digest ervan via gosuda.org/hqq/secureexec, en opent een pool van gedeelde-geheugensessies.
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log"
8
9 "gosuda.org/zstd"
10)
11
12func main() {
13 // 1. One-shot Compress / Decompress (mirrors 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. Streaming Writer and Reader with Dictionary Support
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}
Ondersteunde platformen en ingebedde binaries
Alle zes doelplatformen worden geleverd met vooraf gebouwde, op SHA-256 vastgepinde workerbinaries, ingebed via platformspecifieke build-tags (internal/embedded/embed_<goos>_<goarch>.go):
| Doel-OS | Doel-architectuur (GOARCH) | Ingebedde binary | Binarygrootte | Buildtoolchain en herkomst |
|---|---|---|---|---|
| Linux | amd64, arm64 | zstd_server_linux_<arch> | ~770 - 880 KiB | GCC -O3, SLSA Build L3 Provenance |
| macOS | arm64, amd64 | zstd_server_darwin_<arch> | ~660 - 795 KiB | AppleClang -O3, SLSA Build L3 Provenance |
| Windows | amd64 | zstd_server_windows_amd64.exe | ~818 KiB | MSVC -O2, SLSA Build L3 Provenance |
| Windows | arm64 | zstd_server_windows_arm64.exe | ~551 KiB | zig cc -target aarch64-windows-gnu -O3, SLSA L3 |
Binnenin het gedeeld-geheugen-datapad
Om de doorvoer concurrerend te houden met in-process cgo, vermijdt gosuda.org/zstd socketserialisatie en heap-allocaties op het hot path.
1. Codering in slots zonder allocaties (zstd_ctx.go en c/zstd_hooks.c)
Wanneer je een herbruikbare zstd.Ctx gebruikt (of wanneer de pakketniveau-functies Compress en Decompress een Ctx lenen uit de interne sessiepool), stroomt data rechtstreeks via de gemapte StandardLink-slot:
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
- De gegenereerde
hqq-bindgen-client kopieertsrcrechtstreeks naar de geheugengemapte requestslot en wisselt hetSeqLock-sequentiewoord om. - De C-worker leest
srcter plaatse uit de requestslot en geeft de payloadpointer van de responseslot rechtstreeks door als doelbuffer aanZSTD_compressCCtxofZSTD_decompressDCtx. - De Go-client kopieert het resulterende slice uit de responseslot naar het door de aanroeper aangeleverde
dst-slice. In de steady state voerenctx.CompressTo(dst, src, level)enctx.DecompressInto(dst, src)0 allocs/opuit.
Vooraf verwerkte woordenboeken (CDict en DDict) leven op de C-worker achter generatiegecontroleerde 64-bits handles (handle CDict, handle DDict in zstd.idl). Het eenmalig aanmaken van een woordenboek via ctx.NewCDict(rawDict, level) parseert de woordenboektabellen binnen de C-worker en retourneert een lichtgewicht handle die over duizenden aanroepen kan worden hergebruikt zonder de woordenboekbytes opnieuw te verzenden.
2. IPC-overhead amortiseren met bulk-framing (zstd_bulk.go)
Voor grote payloads (16 KiB en groter) is de ~200 ns durende round trip via gedeeld geheugen verwaarloosbaar ten opzichte van de CPU-tijd die binnen libzstd wordt besteed. Voor workloads die duizenden kleine berichten van 100 bytes tot 1 KiB in één keer comprimeren, telt het betalen van zelfs 200 ns per item echter op.
gosuda.org/zstd biedt CompressBulk en DecompressBulk op BulkCtx (zstd_bulk.go) om round trips per item te elimineren:
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 verpakt de volledige [][]byte-batch in één frame met lengteprefixen in het gedeelde geheugen. De C-worker doorloopt elk item in C (compress_bulk in c/zstd_hooks.c), schrijft alle gecomprimeerde frames aaneengesloten naar de responseslot en retourneert de volledige batch in één enkele round trip via gedeeld geheugen.
3. Overleven van workerbeëindiging zonder de aanroeper te laten crashen
Omdat zstd_server in een afzonderlijk OS-proces draait, brengt een fatale fout in de C-worker nooit je Go-applicatie tot stilstand:
- Als
zstd_serverextern wordt beëindigd (bijvoorbeeld doorSIGKILLof een OOM-gebeurtenis), detecteert de HQQ-transportlaag de verbroken peer onmiddellijk en retourneert een getypeerde*rt.TransportErroruit de lopendeCompress- ofDecompress-aanroep. - Pakketniveau-functies (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) detecteren automatisch dat de gedeelde standaardworker verdwenen is, starten bij de volgende aanroep een nieuwe geverifieerdezstd_server-instantie en hervatten de normale werking.
Repositorystructuur
De codebase scheidt de drop-in DataDog/zstd-facade van de laagniveau-primitieven voor hqq-bindgen-sessies en bulkverwerking:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # Drop-in Compress/Decompress API, auto-recovery pool, and SHA-256 hashes
3├── zstd_stream.go # Streaming Writer/Reader (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Zero-allocation Ctx, CDict, and DDict wrappers over hqq-bindgen
5├── zstd_bulk.go # Single-round-trip CompressBulk and DecompressBulk framing
6├── zstd.go # Generated hqq-bindgen Go client for the zstd IDL service
7├── zstd.idl # HQQ IDL schema defining Context, CDict, DDict, and RPC methods
8├── internal/embedded/ # Platform-tagged //go:embed zstd_server binaries (6 OS/Arch targets)
9└── c/ # C11 server implementation (zstd_server.c, zstd_hooks.c, main.c)
Aan de slag
Installeer gosuda.org/zstd met go get en gebruik het met CGO_ENABLED=0:
1go get gosuda.org/zstd@v0.3.10507
- GitHub-repository: https://github.com/gosuda/hqq-zstd
- Architectuur en benchmarks: docs/ARCHITECTURE.md
- HQQ Core Runtime: https://gosuda.org/hqq