gosuda.org/zstd: Drop-In DataDog/zstd Compression with Zero CGO and Crash Isolation
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:
- Atrito de Construção e Implantação: Assim como o
go-sqlite3, oDataDog/zstdrequerCGO_ENABLED=1e 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 paralinux/arm64ouwindows/arm64requer sysroots C externas. - 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 Destino | Arquitetura de Destino (GOARCH) | Binário Embutido | Tamanho do Binário | Cadeia de Ferramentas de Compilação e Proveniência |
|---|---|---|---|---|
| Linux | amd64, arm64 | zstd_server_linux_<arch> | ~770 - 880 KiB | GCC -O3, Proveniência SLSA Build L3 |
| macOS | arm64, amd64 | zstd_server_darwin_<arch> | ~660 - 795 KiB | AppleClang -O3, Proveniência SLSA Build L3 |
| Windows | amd64 | zstd_server_windows_amd64.exe | ~818 KiB | MSVC -O2, Proveniência SLSA Build L3 |
| Windows | arm64 | zstd_server_windows_arm64.exe | ~551 KiB | zig 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
- O cliente gerado
hqq-bindgencopiasrcdiretamente para o slot de solicitação mapeado em memória e altera a palavra de sequênciaSeqLock. - O trabalhador C lê
srcdiretamente do slot de solicitação e passa o ponteiro de carga útil do slot de resposta diretamente como o buffer de destino paraZSTD_compressCCtxouZSTD_decompressDCtx. - O cliente Go copia a fatia resultante do slot de resposta para a fatia
dstfornecida pelo chamador. Em estado estacionário,ctx.CompressTo(dst, src, level)ectx.DecompressInto(dst, src)realizam0 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_serverfor encerrado externamente (por exemplo, porSIGKILLou um evento OOM), a camada de transporte HQQ detecta o par corrompido imediatamente e retorna um*rt.TransportErrortipado da chamadaCompressouDecompressem 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 dezstd_serverna 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
- Repositório no GitHub: https://github.com/gosuda/hqq-zstd
- Arquitetura e Benchmarks: docs/ARCHITECTURE.md
- Tempo de Execução Principal do HQQ: https://gosuda.org/hqq