---
id: e52d68870be3de9f644fcfc584bd8aec
author: Lemon Mint
title: 'gosuda.org/zstd : compression compatible avec DataDog/zstd sans CGO et avec isolation des pannes'
description: CGO_ENABLED=0 Bibliothèque Go Zstandard exécutant la bibliothèque C libzstd 1.5.7 en amont dans un worker à mémoire partagée, avec une API compatible DataDog/zstd, des contextes réutilisables et un traitement par lots en masse.
language: fr
date: 2026-10-18T14:07:11.281986997Z
path: /zstd
go_package: gosuda.org/zstd
go_repourl: https://github.com/gosuda/hqq-zstd.git
category: lowlevel
---

`gosuda.org/zstd` est une bibliothèque de compression Go avec `CGO_ENABLED=0` qui exécute le moteur C officiel Zstandard (`libzstd 1.5.7`) dans un processus de travail isolé via la mémoire partagée [HQQ](https://gosuda.org/hqq). Elle fournit une API de remplacement direct pour `github.com/DataDog/zstd` (incluant `Compress`/`Decompress` en un seul appel, `io.Writer`/`io.Reader` en flux, dictionnaires pré-traités et contextes réutilisables sans allocation) tout en maintenant l'exécution du code C entièrement en dehors de l'espace d'adressage du runtime Go.

```bash
go get gosuda.org/zstd@v0.3.10507
```

## Pourquoi la décompression doit se situer en dehors du tas Go

Dans les backends 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 chaque minute des gigaoctets de trames fournies de l'extérieur.

De nombreuses équipes Go choisissent `github.com/DataDog/zstd` parce qu'il se lie directement à la bibliothèque C `libzstd` amont : l'implémentation C offre un haut débit sur un seul cœur, des taux de compression par dictionnaire élevés sur de petits enregistrements, et des arènes C hors tas (`ZSTD_CCtx` et `ZSTD_DCtx`) qui n'augmentent pas la durée des analyses du ramasse-miettes Go.

Toutefois, lier `libzstd` à un service Go via `cgo` entraîne deux difficultés opérationnelles :

1. **Frictions de compilation et de déploiement** : à l'instar de `go-sqlite3`, `DataDog/zstd` exige `CGO_ENABLED=1` ainsi qu'une chaîne d'outils de compilation C cible. La compilation croisée des services ou la production de binaires statiques pour `linux/arm64` ou `windows/arm64` nécessitent des sysroots C externes.
2. **Espace d'adressage partagé pour les entrées non fiables** : la décompression de 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 de dictionnaires personnalisés peut interrompre l'intégralité du processus Go.

`gosuda.org/zstd` conserve l'implémentation C amont exacte `libzstd 1.5.7` et l'API familière de `github.com/DataDog/zstd`, mais déplace l'exécution du code C dans un processus enfant vérifié (`zstd_server`) relié à votre programme Go par une mémoire partagée alignée sur les lignes de cache.

## Remplacement direct de `github.com/DataDog/zstd`

La migration depuis `github.com/DataDog/zstd` nécessite uniquement de modifier le chemin d'importation vers `"gosuda.org/zstd"`. Aucune installation de binaire externe ni code de configuration n'est requis : au premier appel, `gosuda.org/zstd` matérialise un binaire `zstd_server` embarqué et attesté SLSA pour votre plateforme, vérifie son empreinte SHA-256 via `gosuda.org/hqq/secureexec`, puis ouvre un pool de sessions en mémoire partagée.

```go
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"

	"gosuda.org/zstd"
)

func main() {
	// 1. Compress / Decompress en un seul appel (reproduit github.com/DataDog/zstd)
	src := []byte("shared-memory zstd payload without cgo")
	compressed, err := zstd.CompressLevel(nil, src, zstd.BestSpeed)
	if err != nil {
		log.Fatal(err)
	}
	plain, err := zstd.Decompress(nil, compressed)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("one-shot: %d bytes -> %d bytes (%s)\n", len(src), len(compressed), plain)

	// 2. Writer et Reader en flux avec prise en charge des dictionnaires
	var buf bytes.Buffer
	w := zstd.NewWriterLevel(&buf, zstd.DefaultCompression)
	if _, err := w.Write(src); err != nil {
		log.Fatal(err)
	}
	if err := w.Close(); err != nil {
		log.Fatal(err)
	}

	r := zstd.NewReader(&buf)
	defer r.Close()
	streamed, err := io.ReadAll(r)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("streamed: %s\n", streamed)
}
```

### Plateformes prises en charge et binaires embarqués

Les six plateformes cibles sont livrées avec des binaires de travail précompilés et épinglés par SHA-256, intégrés via des balises de compilation propres à chaque plateforme (`internal/embedded/embed_<goos>_<goarch>.go`) :

| Système cible | Architecture cible (`GOARCH`) | Binaire embarqué                | Taille du binaire | Chaîne de compilation et provenance               |
| :------------ | :---------------------------- | :------------------------------ | ----------------: | :------------------------------------------------ |
| **Linux**     | `amd64`, `arm64`              | `zstd_server_linux_<arch>`      |  ~770 - 880 KiB   | GCC `-O3`, provenance SLSA Build L3               |
| **macOS**     | `arm64`, `amd64`              | `zstd_server_darwin_<arch>`     |  ~660 - 795 KiB   | AppleClang `-O3`, provenance SLSA Build L3        |
| **Windows**   | `amd64`                       | `zstd_server_windows_amd64.exe` |       ~818 KiB    | MSVC `-O2`, provenance SLSA Build L3              |
| **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

Pour maintenir un débit compétitif face au `cgo` en processus, `gosuda.org/zstd` évite la sérialisation par sockets et les allocations de tas sur le chemin critique.

### 1. Encodage de slots sans allocation (`zstd_ctx.go` et `c/zstd_hooks.c`)

Lorsque vous utilisez un `zstd.Ctx` réutilisable (ou lorsque les fonctions `Compress` et `Decompress` au niveau du paquet empruntent un `Ctx` au pool de sessions interne), les données circulent directement par le slot `StandardLink` mappé en mémoire :

```text
Appelant Go (zstd.Ctx)                  Slot de mémoire partagée              Worker C11 (zstd_hooks.c)
----------------------                  ------------------------              -------------------------
src []byte  ------------------------->  Charge utile du slot requête  ------>  ZSTD_compressCCtx(
                                                                                cctx, resp_slot, src, level)
dst []byte  <-------------------------  Charge utile du slot réponse  <-----  Écrit la trame compressée directement
                                                                              dans le slot de réponse partagé
```

1. Le client `hqq-bindgen` généré copie `src` directement dans le slot de requête mappé en mémoire et inverse le mot de séquence `SeqLock`.
2. Le worker C lit `src` sur place depuis le slot de requête et transmet le pointeur de charge utile du slot de réponse directement comme tampon de destination à `ZSTD_compressCCtx` ou `ZSTD_decompressDCtx`.
3. Le client Go copie la tranche résultante du slot de réponse vers la tranche `dst` fournie par l'appelant. En régime permanent, `ctx.CompressTo(dst, src, level)` et `ctx.DecompressInto(dst, src)` réalisent `0 allocs/op`.

Les dictionnaires pré-traités (`CDict` et `DDict`) résident sur le worker C derrière des handles de 64 bits à contrôle de 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 au sein du worker C et renvoie un handle léger, réutilisable pour des milliers d'appels sans renvoyer à nouveau les octets du dictionnaire.

### 2. Amortir le surcoût IPC grâce à l'encapsulation en lot (`zstd_bulk.go`)

Pour les charges utiles volumineuses (16 KiB et plus), l'aller-retour en mémoire partagée d'environ 200 ns est négligeable comparé au temps CPU consommé à l'intérieur de `libzstd`. En revanche, pour les charges de travail qui compressent des milliers de petits messages de 100 octets à 1 KiB en une seule fois, payer ne serait-ce que 200 ns par élément s'accumule.

`gosuda.org/zstd` fournit `CompressBulk` et `DecompressBulk` sur `BulkCtx` (`zstd_bulk.go`) pour éliminer les allers-retours par élément :

```go
bctx, err := client.NewBulkCtx(1 << 20)
if err != nil {
	log.Fatal(err)
}
defer bctx.Close()

items := [][]byte{
	[]byte(`{"event":"login","user":101}`),
	[]byte(`{"event":"purchase","user":101,"amount":4200}`),
	[]byte(`{"event":"logout","user":101}`),
}
compressedItems, err := bctx.CompressBulk(nil, items, zstd.DefaultCompression)
```

`CompressBulk` empaquette l'intégralité du lot `[][]byte` dans une unique trame de requête préfixée par la longueur, en mémoire partagée. Le worker C parcourt chaque élément en C (`compress_bulk` dans `c/zstd_hooks.c`), écrit toutes les trames compressées de manière contiguë dans le slot de réponse, et renvoie le lot entier en un seul aller-retour en mémoire partagée.

### 3. Survivre à l'arrêt du worker sans faire planter l'appelant

Comme `zstd_server` s'exécute dans un processus système distinct, une défaillance fatale à l'intérieur du worker C ne met jamais en péril votre application Go :

- Si `zstd_server` est tué de l'extérieur (par exemple par `SIGKILL` ou un événement OOM), la couche de transport HQQ détecte immédiatement le pair défaillant et renvoie un `*rt.TransportError` typé depuis l'appel `Compress` ou `Decompress` en cours.
- Les fonctions au niveau du paquet (`zstd.Compress`, `zstd.Decompress`, `zstd.NewWriter`, `zstd.NewReader`) détectent automatiquement que le worker par défaut partagé a disparu, lancent une nouvelle instance vérifiée de `zstd_server` lors de l'appel suivant, et reprennent un fonctionnement normal.

## Organisation du dépôt

La base de code sépare la façade `DataDog/zstd` de remplacement direct des primitives de session et de lot de `hqq-bindgen` de bas niveau :

```text
gosuda.org/zstd
├── autolaunch.go / hashes.go  # API Compress/Decompress de remplacement direct, pool à récupération automatique et empreintes SHA-256
├── zstd_stream.go             # Writer/Reader en flux (NewWriterLevelDict, NewReaderDict)
├── zstd_ctx.go                # Wrappers Ctx, CDict et DDict sans allocation au-dessus de hqq-bindgen
├── zstd_bulk.go               # Encapsulation CompressBulk et DecompressBulk en un seul aller-retour
├── zstd.go                    # Client Go hqq-bindgen généré pour le service IDL zstd
├── zstd.idl                   # Schéma IDL HQQ définissant Context, CDict, DDict et les méthodes RPC
├── internal/embedded/         # Binaires zstd_server intégrés via //go:embed, balisés par plateforme (6 cibles OS/Arch)
└── c/                         # Implémentation du serveur C11 (zstd_server.c, zstd_hooks.c, main.c)
```

## Premiers pas

Installez `gosuda.org/zstd` avec `go get` et utilisez-le avec `CGO_ENABLED=0` :

```bash
go get gosuda.org/zstd@v0.3.10507
```

- **Dépôt GitHub** : [https://github.com/gosuda/hqq-zstd](https://github.com/gosuda/hqq-zstd)
- **Architecture et benchmarks** : [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-zstd/blob/main/docs/ARCHITECTURE.md)
- **Runtime central HQQ** : [https://gosuda.org/hqq](https://gosuda.org/hqq)
