---
id: e52d68870be3de9f644fcfc584bd8aec
author: Lemon Mint
title: 'gosuda.org/zstd: Compresión compatible con DataDog/zstd sin CGO y con aislamiento de fallos'
description: CGO_ENABLED=0 Biblioteca Go de Zstandard que ejecuta la libzstd de C upstream 1.5.7 en un worker de memoria compartida, con una API compatible con DataDog/zstd, contextos reutilizables y procesamiento por lotes.
language: es
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` es una biblioteca de compresión en Go con `CGO_ENABLED=0` que ejecuta el motor oficial de Zstandard en C (`libzstd 1.5.7`) dentro de un proceso trabajador aislado sobre memoria compartida de [HQQ](https://gosuda.org/hqq). Ofrece una API de reemplazo directo para `github.com/DataDog/zstd` (incluyendo `Compress`/`Decompress` de una sola llamada, `io.Writer`/`io.Reader` en modo streaming, diccionarios precalculados y contextos reutilizables sin asignaciones de memoria) y mantiene la ejecución de C completamente fuera del espacio de direcciones del runtime de Go.

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

## Por qué la descompresión debe ubicarse fuera del heap de Go

En los backends de producción, la compresión y la descompresión se encuentran directamente en el límite de entrada no confiable. Los colectores de logs, los brokers de mensajes, las puertas de enlace RPC y los motores de almacenamiento descomprimen gigabytes de tramas suministradas externamente cada minuto.

Muchos equipos de Go eligen `github.com/DataDog/zstd` porque se enlaza directamente con la `libzstd` original escrita en C: la implementación en C ofrece un alto rendimiento por núcleo, relaciones de compresión con diccionario muy ajustadas en registros pequeños y arenas de C fuera del heap (`ZSTD_CCtx` y `ZSTD_DCtx`) que no incrementan los tiempos de escaneo del recolector de basura de Go.

No obstante, enlazar `libzstd` en un servicio Go mediante `cgo` introduce dos problemas operativos:

1. **Fricción en la compilación y el despliegue**: al igual que `go-sqlite3`, `DataDog/zstd` requiere `CGO_ENABLED=1` y una cadena de herramientas de compilación de C para el objetivo. La compilación cruzada de servicios o la generación de binarios estáticos para `linux/arm64` o `windows/arm64` exige sysroots de C externos.
2. **Espacio de direcciones compartido con entradas no confiables**: descomprimir cargas corruptas o adversarias dentro del mismo espacio de direcciones virtual que su servidor Go implica que cualquier error en el código nativo de C o en el manejo de diccionarios personalizados puede terminar todo el proceso Go.

`gosuda.org/zstd` conserva el motor exacto `libzstd 1.5.7` original en C y la API familiar de `github.com/DataDog/zstd`, pero traslada la ejecución de C a un proceso hijo verificado (`zstd_server`) conectado a su programa Go mediante memoria compartida alineada a líneas de caché.

## Reemplazo directo de `github.com/DataDog/zstd`

Migrar desde `github.com/DataDog/zstd` requiere cambiar únicamente la ruta de importación a `"gosuda.org/zstd"`. No se necesita instalar ningún binario externo ni código de configuración: en la primera llamada, `gosuda.org/zstd` extrae un binario `zstd_server` incrustado con atestación SLSA para su plataforma, verifica su digest SHA-256 mediante `gosuda.org/hqq/secureexec` y abre un pool de sesiones de memoria compartida.

```go
package main

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

	"gosuda.org/zstd"
)

func main() {
	// 1. Compress / Decompress de una sola llamada (replica 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 y Reader en streaming con soporte de diccionarios
	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)
}
```

### Plataformas compatibles y binarios incrustados

Las seis plataformas objetivo incluyen binarios de trabajador preconstruidos y fijados por SHA-256, incrustados mediante etiquetas de compilación específicas de plataforma (`internal/embedded/embed_<goos>_<goarch>.go`):

| Sistema objetivo | Arquitectura objetivo (`GOARCH`) | Binario incrustado              | Tamaño del binario | Cadena de compilación y procedencia                 |
| :--------------- | :------------------------------- | :------------------------------ | -----------------: | :-------------------------------------------------- |
| **Linux**        | `amd64`, `arm64`                 | `zstd_server_linux_<arch>`      |   ~770 - 880 KiB   | GCC `-O3`, procedencia SLSA Build L3                |
| **macOS**        | `arm64`, `amd64`                 | `zstd_server_darwin_<arch>`     |   ~660 - 795 KiB   | AppleClang `-O3`, procedencia SLSA Build L3         |
| **Windows**      | `amd64`                          | `zstd_server_windows_amd64.exe` |        ~818 KiB    | MSVC `-O2`, procedencia SLSA Build L3               |
| **Windows**      | `arm64`                          | `zstd_server_windows_arm64.exe` |        ~551 KiB    | `zig cc -target aarch64-windows-gnu -O3`, SLSA L3   |

## Dentro de la ruta de datos en memoria compartida

Para mantener un rendimiento competitivo frente a `cgo` en proceso, `gosuda.org/zstd` evita la serialización por sockets y las asignaciones de heap en la ruta crítica.

### 1. Codificación de slots sin asignaciones (`zstd_ctx.go` y `c/zstd_hooks.c`)

Cuando utiliza un `zstd.Ctx` reutilizable (o cuando las funciones `Compress` y `Decompress` a nivel de paquete toman prestado un `Ctx` del pool de sesiones interno), los datos fluyen directamente a través del slot `StandardLink` mapeado:

```text
Llamador Go (zstd.Ctx)                  Slot de memoria compartida            Trabajador C11 (zstd_hooks.c)
--------------------                    --------------------------            -----------------------------
src []byte  ------------------------->  Carga del slot de solicitud  ------>  ZSTD_compressCCtx(
                                                                                cctx, resp_slot, src, level)
dst []byte  <-------------------------  Carga del slot de respuesta  <------  Escribe la trama comprimida directamente
                                                                              en el slot de respuesta compartido
```

1. El cliente `hqq-bindgen` generado copia `src` directamente en el slot de solicitud mapeado en memoria e invierte la palabra de secuencia `SeqLock`.
2. El trabajador C lee `src` en el lugar desde el slot de solicitud y pasa el puntero de carga del slot de respuesta directamente como búfer de destino a `ZSTD_compressCCtx` o `ZSTD_decompressDCtx`.
3. El cliente Go copia el slice resultante desde el slot de respuesta al slice `dst` proporcionado por el llamador. En estado estacionario, `ctx.CompressTo(dst, src, level)` y `ctx.DecompressInto(dst, src)` realizan `0 allocs/op`.

Los diccionarios precalculados (`CDict` y `DDict`) residen en el trabajador C detrás de manejadores de 64 bits con verificación de generación (`handle CDict`, `handle DDict` en `zstd.idl`). Crear un diccionario una vez mediante `ctx.NewCDict(rawDict, level)` analiza las tablas del diccionario dentro del trabajador C y devuelve un manejador ligero que puede reutilizarse en miles de llamadas sin reenviar los bytes del diccionario.

### 2. Amortización de la sobrecarga de IPC con empaquetado por lotes (`zstd_bulk.go`)

Para cargas grandes (16 KiB o más), el viaje de ida y vuelta por memoria compartida de ~200 ns es despreciable frente al tiempo de CPU dedicado dentro de `libzstd`. Sin embargo, para cargas de trabajo que comprimen miles de mensajes pequeños de entre 100 bytes y 1 KiB de una sola vez, pagar incluso 200 ns por elemento se acumula.

`gosuda.org/zstd` proporciona `CompressBulk` y `DecompressBulk` sobre `BulkCtx` (`zstd_bulk.go`) para eliminar los viajes de ida y vuelta por 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` empaqueta el lote completo `[][]byte` en una única trama de solicitud con prefijos de longitud en memoria compartida. El trabajador C recorre cada elemento en C (`compress_bulk` en `c/zstd_hooks.c`), escribe todas las tramas comprimidas de forma contigua en el slot de respuesta y devuelve el lote completo en un único viaje de ida y vuelta por memoria compartida.

### 3. Sobrevivir a la terminación del trabajador sin bloquear al llamador

Dado que `zstd_server` se ejecuta en un proceso del sistema operativo separado, un fallo fatal dentro del trabajador C nunca derriba su aplicación Go:

- Si `zstd_server` se termina externamente (por ejemplo, mediante `SIGKILL` o un evento de OOM), la capa de transporte HQQ detecta de inmediato el par roto y devuelve un `*rt.TransportError` tipado desde la llamada `Compress` o `Decompress` en curso.
- Las funciones a nivel de paquete (`zstd.Compress`, `zstd.Decompress`, `zstd.NewWriter`, `zstd.NewReader`) detectan automáticamente que el trabajador predeterminado compartido ya no existe, lanzan una nueva instancia verificada de `zstd_server` en la siguiente llamada y reanudan la operación normal.

## Estructura del repositorio

La base de código separa la fachada de reemplazo directo de `DataDog/zstd` de las primitivas de sesión y de lotes de bajo nivel de `hqq-bindgen`:

```text
gosuda.org/zstd
├── autolaunch.go / hashes.go  # API Compress/Decompress de reemplazo directo, pool de auto-recuperación y hashes SHA-256
├── zstd_stream.go             # Writer/Reader en streaming (NewWriterLevelDict, NewReaderDict)
├── zstd_ctx.go                # Ctx sin asignaciones, y envoltorios CDict y DDict sobre hqq-bindgen
├── zstd_bulk.go               # Framing CompressBulk y DecompressBulk en un único viaje de ida y vuelta
├── zstd.go                    # Cliente Go de hqq-bindgen generado para el servicio IDL de zstd
├── zstd.idl                   # Esquema IDL de HQQ que define Context, CDict, DDict y los métodos RPC
├── internal/embedded/         # Binarios zstd_server incrustados con //go:embed y etiquetas por plataforma (6 objetivos OS/Arch)
└── c/                         # Implementación del servidor en C11 (zstd_server.c, zstd_hooks.c, main.c)
```

## Primeros pasos

Instale `gosuda.org/zstd` con `go get` y úselo con `CGO_ENABLED=0`:

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

- **Repositorio de GitHub**: [https://github.com/gosuda/hqq-zstd](https://github.com/gosuda/hqq-zstd)
- **Arquitectura y benchmarks**: [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)
