gosuda.org/zstd: Substituição Direta do DataDog/zstd com Compressão Sem CGO e Isolamento de Falhas

gosuda.org/zstd é uma biblioteca de compressão Go com CGO_ENABLED=0 que executa o mecanismo oficial Zstandard em C (libzstd 1.5.7) dentro de um processo trabalhador isolado, por meio de memória compartilhada HQQ. Ela oferece uma API de substituição direta para github.com/DataDog/zstd (incluindo Compress/Decompress em uma única chamada, io.Writer/io.Reader para streaming, dicionários pré-processados e contextos reutilizáveis com alocação zero), mantendo a execução do código C completamente fora do espaço de endereçamento do runtime Go.
1go get gosuda.org/zstd@v0.3.10507
Por Que a Descompressão Deve Ocorrer Fora do Heap Go
Em backends de produção, a compressão e a descompressão situam-se diretamente na fronteira de entrada não confiável. Coletores de logs, corretores de mensagens, gateways RPC e mecanismos de armazenamento descomprimem gigabytes de quadros fornecidos externamente a cada minuto.
Muitas equipes Go escolhem github.com/DataDog/zstd porque ele se vincula diretamente à libzstd em C upstream: a implementação em C oferece alta vazão em um único núcleo, taxas de compressão de dicionário precisas em registros pequenos e arenas C fora do heap (ZSTD_CCtx e ZSTD_DCtx) que não aumentam o tempo de varredura do coletor de lixo do Go.
No entanto, vincular libzstd a um serviço Go por meio de cgo traz dois problemas operacionais:
- Atrito de Build e Implantação: Assim como o
go-sqlite3, oDataDog/zstdexigeCGO_ENABLED=1e um conjunto de ferramentas de compilação C para a plataforma alvo. A compilação cruzada de serviços ou a geração de binários estáticos paralinux/arm64ouwindows/arm64exige sysroots C externos. - Espaço de Endereçamento Compartilhado com Entradas Não Confiáveis: Descomprimir cargas corrompidas ou adversariais no mesmo espaço de endereçamento virtual do seu servidor Go significa que qualquer defeito no código C nativo ou no tratamento de dicionários personalizados pode encerrar todo o processo Go.
O gosuda.org/zstd mantém o mecanismo exato libzstd 1.5.7 em C upstream e a API familiar do github.com/DataDog/zstd, mas transfere a execução do código C para um processo filho verificado (zstd_server) conectado ao seu programa Go por memória compartilhada alinhada a linhas de cache.
Substituição Direta de github.com/DataDog/zstd
A migração a partir de github.com/DataDog/zstd exige alterar apenas o caminho de importação para "gosuda.org/zstd". Não é necessária instalação de binário externo nem código de configuração: na primeira chamada, o gosuda.org/zstd materializa um binário zstd_server embutido, com atestação SLSA, para a sua plataforma, verifica seu digest SHA-256 por meio de gosuda.org/hqq/secureexec e abre um pool de sessões em 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. Compress / Decompress em uma única chamada (espelha 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. Writer e Reader de streaming com suporte a dicionário
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 alvo distribuem binários de trabalhador pré-compilados e fixados por SHA-256, embutidos por meio de tags de build específicas de plataforma (internal/embedded/embed_<goos>_<goarch>.go):
| SO alvo | Arquitetura alvo (GOARCH) | Binário embutido | Tamanho do binário | Cadeia de build 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 em Memória Compartilhada
Para manter uma vazão competitiva com o cgo em processo, o gosuda.org/zstd evita serialização por sockets e alocações de heap no caminho crítico.
1. Codificação de Slots com Alocação Zero (zstd_ctx.go e c/zstd_hooks.c)
Ao usar um zstd.Ctx reutilizável (ou quando as funções de nível de pacote Compress e Decompress emprestam um Ctx do pool interno de sessões), os dados fluem diretamente pelo slot StandardLink mapeado:
1Chamador Go (zstd.Ctx) Slot de Memória Compartilhada Worker C11 (zstd_hooks.c)
2---------------------- ----------------------------- -------------------------
3src []byte -------------------------> Payload do Slot de Requisição ------> ZSTD_compressCCtx(
4 cctx, resp_slot, src, level)
5dst []byte <------------------------- Payload do Slot de Resposta <------ Grava o quadro comprimido diretamente
6 no slot de resposta compartilhado
- O cliente
hqq-bindgengerado copiasrcdiretamente para o slot de requisição mapeado em memória e alterna a palavra de sequência doSeqLock. - O worker C lê
srcno próprio slot de requisição e passa o ponteiro de payload do slot de resposta diretamente como buffer de destino paraZSTD_compressCCtxouZSTD_decompressDCtx. - O cliente Go copia a fatia resultante do slot de resposta para a fatia
dstfornecida pelo chamador. Em regime permanente,ctx.CompressTo(dst, src, level)ectx.DecompressInto(dst, src)executam0 allocs/op.
Dicionários pré-processados (CDict e DDict) residem no worker C atrás de handles de 64 bits com verificação de geração (handle CDict, handle DDict em zstd.idl). Criar um dicionário uma única vez com ctx.NewCDict(rawDict, level) processa as tabelas do dicionário dentro do worker C e retorna um handle leve que pode ser reutilizado em milhares de chamadas sem reenviar os bytes do dicionário.
2. Amortização do Overhead de IPC com Enquadramento em Lote (zstd_bulk.go)
Para cargas grandes (16 KiB ou mais), o percurso de ida e volta de ~200 ns na memória compartilhada é desprezível em comparação com o tempo de CPU gasto dentro da libzstd. Para cargas que comprimem milhares de mensagens pequenas, de 100 bytes a 1 KiB, de uma só vez, porém, pagar até 200 ns por item se acumula.
O gosuda.org/zstd oferece CompressBulk e DecompressBulk em BulkCtx (zstd_bulk.go) para eliminar os percursos 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)
O CompressBulk empacota todo o lote [][]byte em um único quadro de requisição com prefixo de comprimento, na memória compartilhada. O worker C percorre cada item em C (compress_bulk em c/zstd_hooks.c), grava todos os quadros comprimidos de forma contígua no slot de resposta e devolve o lote inteiro em um único percurso de ida e volta pela memória compartilhada.
3. Sobrevivência à Terminação do Worker Sem Derrubar o Chamador
Como o zstd_server executa em um processo do sistema operacional separado, uma falha fatal dentro do worker C nunca derruba sua aplicação Go:
- Se o
zstd_serverfor encerrado externamente (por exemplo, porSIGKILLou por um evento de falta de memória, OOM), a camada de transporte HQQ detecta imediatamente o par interrompido e retorna um*rt.TransportErrortipado da chamadaCompressouDecompressem andamento. - As funções de nível de pacote (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) detectam automaticamente que o worker padrão compartilhado não existe mais, iniciam uma nova instância verificada dezstd_serverna chamada seguinte e retomam a operação normal.
Estrutura do Repositório
A base de código separa a fachada de substituição direta DataDog/zstd das primitivas de sessão e de lote de baixo nível do hqq-bindgen:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # API Compress/Decompress de substituição direta, pool de recuperação automática e hashes SHA-256
3├── zstd_stream.go # Writer/Reader de streaming (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Ctx, CDict e DDict de alocação zero sobre o hqq-bindgen
5├── zstd_bulk.go # Enquadramento de CompressBulk e DecompressBulk em um único percurso de ida e volta
6├── zstd.go # Cliente Go hqq-bindgen gerado para o serviço IDL zstd
7├── zstd.idl # Esquema IDL HQQ que define Context, CDict, DDict e métodos RPC
8├── internal/embedded/ # Binários zstd_server com //go:embed e tags de plataforma (6 alvos OS/arquitetura)
9└── c/ # Implementação do servidor C11 (zstd_server.c, zstd_hooks.c, main.c)
Primeiros Passos
Instale o 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
- HQQ Core Runtime: https://gosuda.org/hqq