gosuda.org/zstd: Drop-In náhrada DataDog/zstd s kompresí bez CGO a izolací pádů

gosuda.org/zstd je knihovna pro kompresi v Go s CGO_ENABLED=0, která spouští oficiální C engine Zstandard (libzstd 1.5.7) uvnitř izolovaného pracovního procesu přes sdílenou paměť HQQ. Poskytuje API jako náhradu za github.com/DataDog/zstd (včetně jednorázových Compress/Decompress, streamovacích io.Writer/io.Reader, předem zpracovaných slovníků a znovupoužitelných kontextů s nulovou alokací), přičemž provádění C kódu zůstává zcela mimo adresní prostor běhového prostředí Go.
1go get gosuda.org/zstd@v0.3.10507
Proč by dekomprese měla probíhat mimo haldu Go
V produkčních backendech se komprese a dekomprese nacházejí přímo na hranici nedůvěryhodného vstupu. Sběrače logů, message brokery, RPC brány a úložné enginy dekomprimují každou minutu gigabajty externě dodaných rámců.
Mnoho týmů v Go volí github.com/DataDog/zstd, protože se váže přímo na upstream C libzstd: C implementace poskytuje vysokou propustnost na jednom jádře, těsné poměry komprese se slovníkem u malých záznamů a off-heap C arény (ZSTD_CCtx a ZSTD_DCtx), které nezvyšují dobu prohledávání paměti garbage collectorem v Go.
Propojení libzstd s Go službou přes cgo však přináší dva provozní problémy:
- Obtíže při sestavení a nasazení: Stejně jako
go-sqlite3vyžadujeDataDog/zstdCGO_ENABLED=1a cílový toolchain C kompilátoru. Křížová kompilace služeb nebo sestavení statických binárních souborů prolinux/arm64čiwindows/arm64vyžaduje externí C sysroot. - Sdílený adresní prostor při nedůvěryhodných vstupech: Dekomprese poškozených nebo protivníkových datových částí ve stejném virtuálním adresním prostoru jako váš Go server znamená, že jakákoli chyba v nativním C kódu nebo ve vlastní práci se slovníky může ukončit celý proces Go.
gosuda.org/zstd zachovává přesný upstream C engine libzstd 1.5.7 a známé API github.com/DataDog/zstd, ale přesouvá provádění C kódu do ověřeného child procesu (zstd_server), který je s vaším Go programem propojen přes sdílenou paměť zarovnanou na cache line.
Náhrada za github.com/DataDog/zstd bez úprav kódu
Migrace z github.com/DataDog/zstd vyžaduje změnit pouze importní cestu na "gosuda.org/zstd". Není potřeba instalovat žádný externí binární soubor ani provádět nastavení: při prvním volání gosuda.org/zstd vytvoří z vestavěného binárního souboru zstd_server s attestací SLSA pro vaši platformu, ověří jeho digest SHA-256 pomocí gosuda.org/hqq/secureexec a otevře fond relací se sdílenou pamětí.
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log"
8
9 "gosuda.org/zstd"
10)
11
12func main() {
13 // 1. Jednorázové Compress / Decompress (kopíruje 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. Streamovací Writer a Reader s podporou slovníků
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}
Podporované platformy a vestavěné binární soubory
Všech šest cílových platforem obsahuje předkompilované pracovní binární soubory s pevným SHA-256, vestavěné pomocí specifických build tagů pro platformu (internal/embedded/embed_<goos>_<goarch>.go):
| Cílový OS | Cílová architektura (GOARCH) | Vestavěný binární soubor | Velikost binárního souboru | Build toolchain a provenience |
|---|---|---|---|---|
| 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 |
Uvnitř datové cesty se sdílenou pamětí
Aby gosuda.org/zstd udržel propustnost konkurenceschopnou vůči procesu cgo běžícímu v rámci stejného procesu, vyhýbá se serializaci přes sockety a alokacím na heapu v kritické cestě.
1. Kódování slotů bez alokací (zstd_ctx.go a c/zstd_hooks.c)
Při použití znovupoužitelného zstd.Ctx (nebo když funkce na úrovni balíčku Compress a Decompress vypůjčí Ctx z interního fondu relací) data proudí přímo přes namapovaný slot StandardLink:
1Go volající (zstd.Ctx) Slot sdílené paměti C11 worker (zstd_hooks.c)
2-------------------- ------------------- -------------------------
3src []byte -------------------------> Payload požadavkového slotu -------> ZSTD_compressCCtx(
4 cctx, resp_slot, src, level)
5dst []byte <------------------------- Payload odpovědního slotu <--------- Zapisuje komprimovaný rámec přímo
6 do sdíleného odpovědního slotu
- Generovaný klient
hqq-bindgenzkopírujesrcpřímo do namapovaného požadavkového slotu a přepne sekvenční slovoSeqLock. - C worker čte
srcna místě z požadavkového slotu a předává ukazatel na payload odpovědního slotu přímo jako cílový buffer doZSTD_compressCCtxneboZSTD_decompressDCtx. - Go klient zkopíruje výsledný slice z odpovědního slotu do volajícím poskytnutého slice
dst. V ustáleném stavuctx.CompressTo(dst, src, level)actx.DecompressInto(dst, src)provádějí0 allocs/op.
Předem zpracované slovníky (CDict a DDict) žijí v C workeru za 64bitovými handly s kontrolou generace (handle CDict, handle DDict v zstd.idl). Jednorázové vytvoření slovníku pomocí ctx.NewCDict(rawDict, level) naparsuje tabulky slovníku uvnitř C workeru a vrátí lehký handle, který lze znovu použít v tisících volání bez opětovného odesílání bajtů slovníku.
2. Amortizace režie IPC pomocí hromadného rámování (zstd_bulk.go)
U velkých datových částí (16 KiB a více) je round trip sdílené paměti přibližně 200 ns zanedbatelný oproti času CPU strávenému uvnitř libzstd. U úloh, které komprimují tisíce malých zpráv o velikosti 100 bajtů až 1 KiB najednou, však i 200 ns na položku se sčítá.
gosuda.org/zstd poskytuje CompressBulk a DecompressBulk na BulkCtx (zstd_bulk.go), aby odstranil round trip pro každou položku:
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 zabalí celou dávku [][]byte do jediného rámce požadavku s předponou délky ve sdílené paměti. C worker prochází každou položku v C (compress_bulk v c/zstd_hooks.c), zapisuje všechny komprimované rámce souvisle do odpovědního slotu a vrací celou dávku v jediném round tripu sdílené paměti.
3. Přežití ukončení workeru bez pádu volajícího
Protože zstd_server běží v samostatném procesu operačního systému, fatální selhání uvnitř C workeru nikdy neshodí vaši Go aplikaci:
- Pokud je
zstd_serverukončen zvenčí (například signálemSIGKILLnebo událostí OOM), transportní vrstva HQQ okamžitě detekuje rozbitého partnera a vrátí typovanou chybu*rt.TransportErrorz probíhajícího voláníCompressneboDecompress. - Funkce na úrovni balíčku (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) automaticky zjistí, že sdílený výchozí worker zmizel, při dalším volání spustí novou ověřenou instancizstd_servera obnoví normální provoz.
Rozložení repozitáře
Kódová základna odděluje facade DataDog/zstd pro drop-in použití od nízkoúrovňových primitiv relací a hromadných operací hqq-bindgen:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # Drop-in API Compress/Decompress, fond s automatickým obnovením a hashe SHA-256
3├── zstd_stream.go # Streamovací Writer/Reader (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Ctx bez alokací, obaly CDict a DDict nad hqq-bindgen
5├── zstd_bulk.go # Rámování CompressBulk a DecompressBulk v jediném round tripu
6├── zstd.go # Generovaný klient hqq-bindgen pro službu zstd IDL
7├── zstd.idl # Schéma HQQ IDL definující Context, CDict, DDict a RPC metody
8├── internal/embedded/ # Binární soubory zstd_server s tagy platforem pro //go:embed (6 cílů OS/architektura)
9└── c/ # Implementace serveru v C11 (zstd_server.c, zstd_hooks.c, main.c)
Začínáme
Nainstalujte gosuda.org/zstd pomocí go get a používejte jej s CGO_ENABLED=0:
1go get gosuda.org/zstd@v0.3.10507
- Repozitář na GitHubu: https://github.com/gosuda/hqq-zstd
- Architektura a benchmarky: docs/ARCHITECTURE.md
- Jádro běhového prostředí HQQ: https://gosuda.org/hqq