---
id: e52d68870be3de9f644fcfc584bd8aec
author: Lemon Mint
title: 'gosuda.org/zstd: Compressione compatibile con DataDog/zstd come drop-in replacement, senza CGO e con isolamento dei crash'
description: CGO_ENABLED=0 Libreria Go per Zstandard che esegue la libreria C upstream libzstd 1.5.7 in un worker con memoria condivisa, con un'API compatibile con DataDog/zstd, contesti riutilizzabili e elaborazione in batch.
language: it
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` è una libreria di compressione Go con `CGO_ENABLED=0` che esegue il motore C Zstandard ufficiale (`libzstd 1.5.7`) all'interno di un processo worker isolato su memoria condivisa [HQQ](https://gosuda.org/hqq). Essa fornisce un'API di sostituzione diretta per `github.com/DataDog/zstd` (includendo `Compress`/`Decompress` one-shot, `io.Writer`/`io.Reader` in streaming, dizionari pre-elaborati e contesti riutilizzabili a allocazione zero), mantenendo l'esecuzione del codice C completamente al di fuori dello spazio di indirizzamento del runtime Go.

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

## Perché la decompressione deve trovarsi al di fuori dell'heap Go

Nei backend di produzione, la compressione e la decompressione si collocano direttamente sul confine di input non attendibile. Log collector, message broker, gateway RPC e storage engine decomprimono ogni minuto gigabyte di frame forniti dall'esterno.

Molti team Go scelgono `github.com/DataDog/zstd` perché si collega direttamente alla libreria C `libzstd` upstream: l'implementazione C offre un'elevata throughput su singolo core, rapporti di compressione dei dizionari precisi su record di piccole dimensioni e arene C off-heap (`ZSTD_CCtx` e `ZSTD_DCtx`) che non aumentano i tempi di scansione del garbage collector di Go.

Il collegamento di `libzstd` a un servizio Go tramite `cgo`, tuttavia, comporta due criticità operative:

1. **Attrito in fase di build e deployment**: proprio come `go-sqlite3`, `DataDog/zstd` richiede `CGO_ENABLED=1` e una toolchain del compilatore C per la piattaforma di destinazione. La cross-compilazione dei servizi o la creazione di binari statici per `linux/arm64` o `windows/arm64` richiede sysroot C esterni.
2. **Spazio di indirizzamento condiviso con input non attendibili**: la decompressione di payload corrotti o ostili all'interno dello stesso spazio di indirizzamento virtuale del proprio server Go implica che qualsiasi bug nel codice C nativo o nella gestione di dizionari personalizzati possa terminare l'intero processo Go.

`gosuda.org/zstd` mantiene l'esatto motore upstream `libzstd 1.5.7` in C e la familiare API di `github.com/DataDog/zstd`, ma sposta l'esecuzione del C in un processo figlio verificato (`zstd_server`) collegato al programma Go tramite memoria condivisa allineata alle linee di cache.

## Sostituzione diretta per `github.com/DataDog/zstd`

La migrazione da `github.com/DataDog/zstd` richiede di modificare esclusivamente il percorso di importazione in `"gosuda.org/zstd"`. Non sono necessari l'installazione di binari esterni né codice di configurazione: alla prima chiamata, `gosuda.org/zstd` materializza un binario `zstd_server` incorporato, certificato SLSA, per la propria piattaforma, ne verifica il digest SHA-256 tramite `gosuda.org/hqq/secureexec` e apre un pool di sessioni su memoria condivisa.

```go
package main

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

	"gosuda.org/zstd"
)

func main() {
	// 1. Compress / Decompress one-shot (equivalente a 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 e Reader in streaming con supporto ai dizionari
	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)
}
```

### Piattaforme supportate e binari incorporati

Tutte e sei le piattaforme di destinazione includono binari worker precompilati con digest SHA-256 fissato, incorporati tramite tag di build specifici per piattaforma (`internal/embedded/embed_<goos>_<goarch>.go`):

| Sistema operativo | Architettura di destinazione (`GOARCH`) | Binario incorporato | Dimensione del binario | Toolchain di build e provenienza |
| :---------------- | :-------------------------------------- | :------------------ | ---------------------: | :------------------------------- |
| **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 |

## All'interno del percorso dati su memoria condivisa

Per mantenere una throughput competitiva rispetto al `cgo` in-process, `gosuda.org/zstd` evita la serializzazione su socket e le allocazioni heap nel percorso critico.

### 1. Codifica degli slot a allocazione zero (`zstd_ctx.go` e `c/zstd_hooks.c`)

Quando si utilizza un `zstd.Ctx` riutilizzabile (oppure quando le funzioni `Compress` e `Decompress` a livello di package prendono in prestito un `Ctx` dal pool di sessioni interno), i dati fluiscono direttamente attraverso lo slot `StandardLink` mappato in memoria:

```text
Caller Go (zstd.Ctx)                    Slot in memoria condivisa             Worker C11 (zstd_hooks.c)
--------------------                    -------------------------             -------------------------
src []byte  ------------------------->  Payload dello slot di richiesta  ---> ZSTD_compressCCtx(
                                                                                cctx, resp_slot, src, level)
dst []byte  <-------------------------  Payload dello slot di risposta  <---  Scrive il frame compresso direttamente
                                                                              nello slot di risposta condiviso
```

1. Il client `hqq-bindgen` generato copia `src` direttamente nello slot di richiesta mappato in memoria e modifica la parola di sequenza `SeqLock`.
2. Il worker C legge `src` in place dallo slot di richiesta e passa il puntatore al payload dello slot di risposta direttamente come buffer di destinazione a `ZSTD_compressCCtx` o `ZSTD_decompressDCtx`.
3. Il client Go copia la slice risultante dallo slot di risposta nella slice `dst` fornita dal chiamante. In regime stazionario, `ctx.CompressTo(dst, src, level)` e `ctx.DecompressInto(dst, src)` eseguono `0 allocs/op`.

I dizionari pre-elaborati (`CDict` e `DDict`) risiedono nel worker 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 worker C e restituisce un handle leggero riutilizzabile per migliaia di chiamate senza reinviare i byte del dizionario.

### 2. Ammortizzare il sovraccarico IPC con il framing in blocco (`zstd_bulk.go`)

Per payload di grandi dimensioni (16 KiB e oltre), il round trip su memoria condivisa di circa 200 ns è trascurabile rispetto al tempo di CPU impiegato all'interno di `libzstd`. Per i carichi di lavoro che comprimono migliaia di messaggi piccoli, da 100 byte a 1 KiB, in un'unica operazione, tuttavia, pagare anche solo 200 ns per elemento si accumula.

`gosuda.org/zstd` fornisce `CompressBulk` e `DecompressBulk` su `BulkCtx` (`zstd_bulk.go`) per eliminare i round trip per elemento:

```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` impacchetta l'intero batch `[][]byte` in un singolo frame di richiesta con prefisso di lunghezza nella memoria condivisa. Il worker C itera 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'unica operazione di round trip su memoria condivisa.

### 3. Sopravvivere alla terminazione del worker senza causare il crash del chiamante

Poiché `zstd_server` viene eseguito in un processo del sistema operativo separato, un errore fatale all'interno del worker C non compromette mai l'applicazione Go:

- Se `zstd_server` viene terminato dall'esterno (ad esempio tramite `SIGKILL` o un evento OOM), il livello di trasporto HQQ rileva immediatamente il peer interrotto e restituisce un `*rt.TransportError` tipizzato dalla chiamata `Compress` o `Decompress` in corso.
- Le funzioni a livello di package (`zstd.Compress`, `zstd.Decompress`, `zstd.NewWriter`, `zstd.NewReader`) rilevano automaticamente che il worker predefinito condiviso non è più disponibile, avviano una nuova istanza verificata di `zstd_server` alla chiamata successiva e riprendono il funzionamento normale.

## Struttura del repository

La codebase separa la facciata drop-in `DataDog/zstd` dalle primitive di sessione e di bulk di basso livello `hqq-bindgen`:

```text
gosuda.org/zstd
├── autolaunch.go / hashes.go  # API drop-in Compress/Decompress, pool con auto-recupero e hash SHA-256
├── zstd_stream.go             # Writer/Reader in streaming (NewWriterLevelDict, NewReaderDict)
├── zstd_ctx.go                # Ctx a allocazione zero, wrapper CDict e DDict su hqq-bindgen
├── zstd_bulk.go               # Framing CompressBulk e DecompressBulk con singolo round trip
├── zstd.go                    # Client Go hqq-bindgen generato per il servizio IDL zstd
├── zstd.idl                   # Schema IDL HQQ che definisce Context, CDict, DDict e i metodi RPC
├── internal/embedded/         # Binari zstd_server con //go:embed e tag di piattaforma (6 target OS/Arch)
└── c/                         # Implementazione del server C11 (zstd_server.c, zstd_hooks.c, main.c)
```

## Per iniziare

Installa `gosuda.org/zstd` con `go get` e utilizzalo con `CGO_ENABLED=0`:

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

- **Repository GitHub**: [https://github.com/gosuda/hqq-zstd](https://github.com/gosuda/hqq-zstd)
- **Architettura e benchmark**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-zstd/blob/main/docs/ARCHITECTURE.md)
- **HQQ Core Runtime**: [https://gosuda.org/hqq](https://gosuda.org/hqq)
