GoSuda

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

By Lemon Mint views ...

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:

  1. Frictie bij build en deployment: Net als go-sqlite3 vereist DataDog/zstd CGO_ENABLED=1 en een doel-C-compilertoolchain. Cross-compilatie van services of het bouwen van statische binaries voor linux/arm64 of windows/arm64 vereist externe C-sysroots.
  2. 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-OSDoel-architectuur (GOARCH)Ingebedde binaryBinarygrootteBuildtoolchain en herkomst
Linuxamd64, arm64zstd_server_linux_<arch>~770 - 880 KiBGCC -O3, SLSA Build L3 Provenance
macOSarm64, amd64zstd_server_darwin_<arch>~660 - 795 KiBAppleClang -O3, SLSA Build L3 Provenance
Windowsamd64zstd_server_windows_amd64.exe~818 KiBMSVC -O2, SLSA Build L3 Provenance
Windowsarm64zstd_server_windows_arm64.exe~551 KiBzig 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
  1. De gegenereerde hqq-bindgen-client kopieert src rechtstreeks naar de geheugengemapte requestslot en wisselt het SeqLock-sequentiewoord om.
  2. De C-worker leest src ter plaatse uit de requestslot en geeft de payloadpointer van de responseslot rechtstreeks door als doelbuffer aan ZSTD_compressCCtx of ZSTD_decompressDCtx.
  3. De Go-client kopieert het resulterende slice uit de responseslot naar het door de aanroeper aangeleverde dst-slice. In de steady state voeren ctx.CompressTo(dst, src, level) en ctx.DecompressInto(dst, src) 0 allocs/op uit.

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_server extern wordt beëindigd (bijvoorbeeld door SIGKILL of een OOM-gebeurtenis), detecteert de HQQ-transportlaag de verbroken peer onmiddellijk en retourneert een getypeerde *rt.TransportError uit de lopende Compress- of Decompress-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 geverifieerde zstd_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