gosuda.org/zstd : Compression DataDog/zstd intégrée sans modification (Drop-In) avec zéro CGO et isolation des plantages
gosuda.org/zstd est une bibliothèque de compression Go CGO_ENABLED=0 qui exécute le moteur C officiel de Zstandard (libzstd 1.5.7) au sein d'un processus ouvrier isolé via la mémoire partagée HQQ. Elle fournit une API de remplacement direct pour github.com/DataDog/zstd (comprenant Compress/Decompress en un seul appel, io.Writer/io.Reader en flux, dictionnaires pré-analysés et contextes réutilisables sans allocation) tout en maintenant l'exécution du code C totalement en dehors de l'espace d'adressage du runtime Go.
1go get gosuda.org/zstd@v0.3.10507
Pourquoi la décompression doit résider en dehors de votre tas Go
Dans les environnements de production, la compression et la décompression se situent directement à la frontière des entrées non fiables. Les collecteurs de journaux, les courtiers de messages, les passerelles RPC et les moteurs de stockage décompressent des gigaoctets de trames fournies de l'extérieur à chaque minute.
De nombreuses équipes Go choisissent github.com/DataDog/zstd parce qu'elle s'lie directement à la bibliothèque amont en C libzstd : l'implémentation en C offre un débit unicœur élevé, des taux de compression par dictionnaire serrés sur les petits enregistrements, ainsi que des zones mémoire C hors tas (ZSTD_CCtx et ZSTD_DCtx) qui n'augmentent pas les temps de balayage du ramasse-miettes de Go.
L'liaison de libzstd dans un service Go via cgo, cependant, entraîne deux difficultés opérationnelles majeures :
- Friction de compilation et de déploiement : Tout comme
go-sqlite3,DataDog/zstdnécessiteCGO_ENABLED=1et une chaîne d'outils de compilation C cible. La compilation croisée de services ou la construction de binaires statiques pourlinux/arm64ouwindows/arm64requiert des environnements système C externes. - Espace d'adressage partagé sur des charges utiles non fiables : Décompresser des charges utiles corrompues ou malveillantes dans le même espace d'adressage virtuel que votre serveur Go signifie que tout bogue dans le code C natif ou dans la gestion personnalisée des dictionnaires peut interrompre l'ensemble du processus Go.
gosuda.org/zstd conserve le moteur C amont exact libzstd 1.5.7 ainsi que l'API familière de github.com/DataDog/zstd, mais déplace l'exécution du code C vers un processus enfant vérifié (zstd_server) connecté à votre programme Go via une mémoire partagée alignée sur les lignes de cache.
Remplacement direct pour github.com/DataDog/zstd
La migration depuis github.com/DataDog/zstd ne nécessite que le changement du chemin d'importation vers "gosuda.org/zstd". Aucune installation de binaire externe ou code de configuration n'est nécessaire : lors du premier appel, gosuda.org/zstd matérialise un binaire embarqué zstd_server attesté SLSA pour votre plateforme, vérifie son condensé SHA-256 via gosuda.org/hqq/secureexec, et ouvre un pool de sessions en mémoire partagée.
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}
Plateformes prises en charge et binaires embarqués
Les six plateformes cibles intègrent des binaires ouvriers précompilés et épinglés par SHA-256 via des balises de compilation spécifiques à la plateforme (internal/embedded/embed_<goos>_<goarch>.go) :
| Target OS | Target Arch (GOARCH) | Embedded Binary | Binary Size | Build Toolchain & Provenance |
|---|---|---|---|---|
| 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 |
Au cœur du chemin de données en mémoire partagée
Afin de maintenir un débit compétitif par rapport au cgo intraprocessus, gosuda.org/zstd évite la sérialisation par socket et les allocations sur le tas dans le chemin critique.
1. Encodage d'emplacements sans allocation (zstd_ctx.go et c/zstd_hooks.c)
Lorsque vous utilisez un objet zstd.Ctx réutilisable (ou lorsque les fonctions de niveau paquet Compress et Decompress empruntent un Ctx du pool de sessions interne), les données transitent directement par l'emplacement mappé 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
- Le client généré par
hqq-bindgencopiesrcdirectement dans l'emplacement de requête mappé en mémoire et bascule le mot de séquenceSeqLock. - L'ouvrier C lit
srcdirectement depuis l'emplacement de requête et transmet le pointeur de charge utile de l'emplacement de réponse directement en tant que tampon de destination àZSTD_compressCCtxouZSTD_decompressDCtx. - Le client Go copie la tranche résultante de l'emplacement de réponse vers la tranche
dstfournie par l'appelant. En régime permanent,ctx.CompressTo(dst, src, level)etctx.DecompressInto(dst, src)n'effectuent0 allocs/op.
Les dictionnaires pré-analysés (CDict et DDict) résident sur l'ouvrier C derrière des descripteurs 64 bits vérifiés par génération (handle CDict, handle DDict dans zstd.idl). La création d'un dictionnaire une seule fois via ctx.NewCDict(rawDict, level) analyse les tables du dictionnaire à l'intérieur de l'ouvrier C et renvoie un descripteur léger qui peut être réutilisé sur des milliers d'appels sans retransmettre les octets du dictionnaire.
2. Amortissement du coût de communication inter-processus (IPC) avec le tramage par lots (zstd_bulk.go)
Pour les charges utiles volumineuses (16 Ko et plus), l'aller-retour en mémoire partagée d'environ 200 ns est négligeable par rapport au temps processeur passé au sein de libzstd. Pour les charges de travail qui compressent simultanément des milliers de petits messages de 100 octets à 1 Ko, cependant, payer ne serait-ce que 200 ns par élément s'accumule rapidement.
gosuda.org/zstd fournit CompressBulk et DecompressBulk sur BulkCtx (zstd_bulk.go) pour éliminer les allers-retours par élément :
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 regroupe l'ensemble du lot [][]byte en une seule trame de requête préfixée par la longueur dans la mémoire partagée. L'ouvrier C boucle sur chaque élément en C (compress_bulk dans c/zstd_hooks.c), écrit toutes les trames compressées de manière contiguë dans l'emplacement de réponse, et renvoie le lot entier en un seul aller-retour en mémoire partagée.
3. Survie à la fermeture de l'ouvrier sans faire planter l'appelant
Puisque zstd_server s'exécute dans un processus système séparé, une défaillance fatale au sein de l'ouvrier C ne provoque jamais la chute de votre application Go :
- Si
zstd_serverest arrêté de l'extérieur (par exemple, par un signalSIGKILLou un événement OOM), la couche de transport HQQ détecte immédiatement le pair rompu et renvoie une erreur typée*rt.TransportErrorde l'appelCompressouDecompressen cours. - Les fonctions de niveau paquet (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) détectent automatiquement que l'ouvrier par défaut partagé a disparu, lancent une nouvelle instance vérifiée dezstd_serverlors du prochain appel, et reprennent un fonctionnement normal.
Organisation du dépôt
La base de code sépare la façade de remplacement direct DataDog/zstd des primitives de session et de traitement par lots de bas niveau hqq-bindgen :
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)
Prise en main
Installez gosuda.org/zstd avec go get et utilisez-le avec CGO_ENABLED=0 :
1go get gosuda.org/zstd@v0.3.10507
- Dépôt GitHub : https://github.com/gosuda/hqq-zstd
- Architecture et benchmarks : docs/ARCHITECTURE.md
- Runtime principal HQQ : https://gosuda.org/hqq