GoSuda

gosuda.org/zstd: Drop-In DataDog/zstd Compression with Zero CGO and Crash Isolation

By Lemon Mint
views ...

gosuda.org/zstd é uma biblioteca de compressão Go com CGO_ENABLED=0 que executa o motor oficial C Zstandard (libzstd 1.5.7) dentro de um processo de trabalhador isolado sobre a memória compartilhada do HQQ. Ela fornece uma API de substituição direta para github.com/DataDog/zstd (incluindo Compress/Decompress de disparo único, io.Writer/io.Reader de fluxo, dicionários pré-digeridos e contextos reutilizáveis sem alocação) mantendo a execução em C completamente fora do espaço de endereçamento do tempo de execução do Go.

1go get gosuda.org/zstd@v0.3.10507

Por Que a Descompressão Pertence Fora do Seu Heap do Go

Em backends de produção, a compressão e a descompressão situam-se diretamente no limite de entrada não confiável. Coletores de logs, corretores de mensagens, gateways RPC e motores de armazenamento descompressam gigabytes de quadros fornecidos externamente a cada minuto.

Muitas equipes de Go escolhem github.com/DataDog/zstd porque ele se vincula diretamente ao C upstream libzstd: a implementação em C oferece alta taxa de transferência em núcleo único, taxas rigorosas de compressão de dicionário em registros pequenos e arenas C fora do heap (ZSTD_CCtx e ZSTD_DCtx) que não inflam os tempos de varredura do garbage-collection do Go.

No entanto, vincular libzstd a um serviço Go via cgo traz dois pontos problemáticos operacionais:

  1. Atrito de Construção e Implantação: Assim como o go-sqlite3, o DataDog/zstd requer CGO_ENABLED=1 e uma cadeia de ferramentas de compilador C de destino. A compilação cruzada de serviços ou a construção de binários estáticos para linux/arm64 ou windows/arm64 requer sysroots C externas.
  2. Espaço de Endereçamento Compartilhado em Entradas Não Confiáveis: Descomprimir cargas úteis corrompidas ou adversariais dentro do mesmo espaço de endereço virtual que o seu servidor Go significa que qualquer erro no código C nativo ou no manuseio de dicionários personalizados pode encerrar todo o processo Go.

gosuda.org/zstd mantém o motor C upstream exato libzstd 1.5.7 e a API familiar de github.com/DataDog/zstd, mas move a execução em C para um processo filho verificado (zstd_server) conectado ao seu programa Go por meio de memória compartilhada de linha de cache.

Substituição Direta para github.com/DataDog/zstd

A migração de github.com/DataDog/zstd requer apenas a alteração do caminho de importação para "gosuda.org/zstd". Nenhuma instalação de binário externo ou código de configuração é necessário: na primeira chamada, gosuda.org/zstd materializa um binário zstd_server embutido e atestado por SLSA para a sua plataforma, verifica seu resumo SHA-256 via gosuda.org/hqq/secureexec e abre um pool de sessões de memória compartilhada.

 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}

Plataformas Suportadas e Binários Embutidos

Todas as seis plataformas de destino são fornecidas com binários de trabalhadores pré-construídos e fixados por SHA-256 embutidos por meio de tags de compilação específicas da plataforma (internal/embedded/embed_<goos>_<goarch>.go):

SO de DestinoArquitetura de Destino (GOARCH)Binário EmbutidoTamanho do BinárioCadeia de Ferramentas de Compilação e Proveniência
Linuxamd64, arm64zstd_server_linux_<arch>~770 - 880 KiBGCC -O3, Proveniência SLSA Build L3
macOSarm64, amd64zstd_server_darwin_<arch>~660 - 795 KiBAppleClang -O3, Proveniência SLSA Build L3
Windowsamd64zstd_server_windows_amd64.exe~818 KiBMSVC -O2, Proveniência SLSA Build L3
Windowsarm64zstd_server_windows_arm64.exe~551 KiBzig cc -target aarch64-windows-gnu -O3, SLSA L3

Dentro do Caminho de Dados de Memória Compartilhada

Para manter a taxa de transferência competitiva com o cgo em processo, gosuda.org/zstd evita a serialização de sockets e alocações de heap no caminho crítico.

1. Codificação de Slots sem Alocação (zstd_ctx.go e c/zstd_hooks.c)

Quando você usa um zstd.Ctx reutilizável (ou quando as funções Compress e Decompress em nível de pacote emprestam um Ctx do pool de sessões interno), os dados fluem diretamente através do slot mapeado 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
  1. O cliente gerado hqq-bindgen copia src diretamente para o slot de solicitação mapeado em memória e altera a palavra de sequência SeqLock.
  2. O trabalhador C lê src diretamente do slot de solicitação e passa o ponteiro de carga útil do slot de resposta diretamente como o buffer de destino para ZSTD_compressCCtx ou ZSTD_decompressDCtx.
  3. O cliente Go copia a fatia resultante do slot de resposta para a fatia dst fornecida pelo chamador. Em estado estacionário, ctx.CompressTo(dst, src, level) e ctx.DecompressInto(dst, src) realizam 0 allocs/op.

Dicionários pré-digeridos (CDict e DDict) residem no trabalhador C atrás de manipuladores de 64 bits verificados por geração (handle CDict, handle DDict em zstd.idl). Criar um dicionário uma vez via ctx.NewCDict(rawDict, level) analisa as tabelas de dicionário dentro do trabalhador C e retorna um manipulador leve que pode ser reutilizado em milhares de chamadas sem reenviar os bytes do dicionário.

2. Amortização da Sobrecarga de IPC com Enquadramento em Massa (zstd_bulk.go)

Para cargas úteis grandes (16 KiB e acima), a viagem de ida e volta de memória compartilhada de ~200 ns é insignificante em comparação com o tempo de CPU gasto dentro de libzstd. Para cargas de trabalho que comprimem milhares de pequenas mensagens de 100 bytes a 1 KiB de uma só vez, no entanto, pagar mesmo 200 ns por item acumula-se.

gosuda.org/zstd fornece CompressBulk e DecompressBulk em BulkCtx (zstd_bulk.go) para eliminar viagens de ida e volta por item:

 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 empacota todo o lote [][]byte em um único quadro de solicitação com prefixo de comprimento na memória compartilhada. O trabalhador C faz um loop sobre cada item em C (compress_bulk em c/zstd_hooks.c), grava todos os quadros comprimidos contiguous no slot de resposta e retorna todo o lote em uma única viagem de ida e volta de memória compartilhada.

3. Sobrevivência ao Término do Trabalhador Sem Convidar o Chamador à Falha

Como o zstd_server é executado em um processo de SO separado, uma falha fatal dentro do trabalhador C nunca derruba sua aplicação Go:

  • Se o zstd_server for encerrado externamente (por exemplo, por SIGKILL ou um evento OOM), a camada de transporte HQQ detecta o par corrompido imediatamente e retorna um *rt.TransportError tipado da chamada Compress ou Decompress em andamento.
  • Funções em nível de pacote (zstd.Compress, zstd.Decompress, zstd.NewWriter, zstd.NewReader) detectam automaticamente que o trabalhador padrão compartilhado se foi, geram uma nova instância verificada de zstd_server na próxima chamada e retomam a operação normal.

Layout do Repositório

A base de código separa a fachada de substituição direta DataDog/zstd da sessão de baixo nível hqq-bindgen e primitivos em massa:

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)

Primeiros Passos

Instale gosuda.org/zstd com go get e use-o com CGO_ENABLED=0:

1go get gosuda.org/zstd@v0.3.10507