gosuda.org/zstd: Drop-In DataDog/zstd komprese s nulovým CGO a izolací pádů
gosuda.org/zstd je kompresní knihovna pro jazyk Go s nastavením CGO_ENABLED=0, která spouští oficiální C engine Zstandard (libzstd 1.5.7) uvnitř izolovaného pracovního procesu prostřednictvím sdílené paměti HQQ. Poskytuje API pro přímou náhradu knihovny github.com/DataDog/zstd (včetně jednorázových operací Compress/Decompress, streamování pomocí io.Writer/io.Reader, predigestořovaných slovníků a znovupoužitelných kontextů bez alokace paměti), přičemž uchovává provádění kódu v jazyce C zcela mimo adresní prostor runtime Go.
1go get gosuda.org/zstd@v0.3.10507
Proč dekomprese patří mimo váš Go Heap
V produkčních backendech leží komprese a dekomprese přímo na hranici nedůvěryhodných vstupů. Sběrače logů, zprostředkovatelé zpráv, RPC brány a úložné systémy dekomprimují gigabyty externě dodaných rámců každou minutu.
Mnoho týmů používajících Go volí github.com/DataDog/zstd, protože se přímo váže na upstreamovou knihovnu C libzstd: implementace v jazyce C přináší vysokou propustnost jednoho jádra, těsné kompresní poměry slovníků u malých záznamů a C arény mimo haldu (ZSTD_CCtx a ZSTD_DCtx), které nezvyšují dobu skenování garbage collectoru v Go.
Propojení libzstd do služby v Go prostřednictvím cgo však přináší dvě provozní úskalí:
- Komplikace při sestavování a nasazení: Stejně jako
go-sqlite3vyžadujeDataDog/zstdnastaveníCGO_ENABLED=1a cílovou řetězu nástrojů kompilátoru jazyka C. Křížová kompilace služeb nebo sestavování statických binárních souborů pro platformylinux/arm64nebowindows/arm64vyžaduje externí systémy C. - Sdílený adresní prostor pro nedůvěryhodné vstupy: Dekomprese poškozených nebo adversariálních datových proudů uvnitř stejného virtuálního adresního prostoru jako váš Go server znamená, že jakákoli chyba v nativním kódu v jazyce C nebo ve vlastním zpracování slovníků může ukončit celý proces Go.
gosuda.org/zstd zachovává přesný upstreamový C engine libzstd 1.5.7 a známé API github.com/DataDog/zstd, ale přesouvá provádění kódu v jazyce C do ověřeného podřízeného procesu (zstd_server), který je připojen k vašemu programu v Go prostřednictvím sdílené paměti v cache lince.
Přímá náhrada za github.com/DataDog/zstd
Migrace z github.com/DataDog/zstd vyžaduje změnu pouze importní cesty na "gosuda.org/zstd". Není vyžadována žádná instalace externího binárního souboru ani nastavovací kód: při prvním volání gosuda.org/zstd materializuje vložený binární soubor zstd_server s certifikací SLSA pro vaši platformu, ověří jeho otisk SHA-256 pomocí gosuda.org/hqq/secureexec a otevře fond relací sdílené paměti.
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á komprese / dekomprese (odpovídá 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í zapisovač a čtečka 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 vložené binární soubory
Všech šest cílových platforem se dodává s předem sestavenými binárními soubory pracovních procesů zajištěnými pomocí SHA-256, které jsou vloženy prostřednictvím sestavovacích značek specifických pro danou platformu (internal/embedded/embed_<goos>_<goarch>.go):
| Cílový OS | Cílová architektura (GOARCH) | Vložený binární soubor | Velikost binárního souboru | Sestavovací řetězec nástrojů 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 sdílené paměti
Aby byla propustnost konkurenceschopná vůči in-process cgo, gosuda.org/zstd se vyhýbá serializaci soketů a alokacím na haldě v kritické cestě.
1. Kódování slotů bez alokace (zstd_ctx.go a c/zstd_hooks.c)
Když používáte znovupoužitelný zstd.Ctx (nebo když funkce Compress a Decompress na úrovni balíčku si půjčují Ctx z interního fondu relací), data plynou přímo přes mapovaný slot 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
- Vygenerovaný klient
hqq-bindgenzkopírujesrcpřímo do paměťově mapovaného slotu požadavku a přepne sekvenční slovoSeqLock. - Pracovní proces v C přečte
srcna místě ze slotu požadavku a předá ukazatel na datové zatížení slotu odpovědi přímo jako cílovou vyrovnávací paměť funkcímZSTD_compressCCtxneboZSTD_decompressDCtx. - Klient v Go zkopíruje výsledný řez ze slotu odpovědi do řezu
dstposkytnutého volajícím. V ustáleném stavu provádějí funkcectx.CompressTo(dst, src, level)actx.DecompressInto(dst, src)0 alokací/operaci.
Predigestořené slovníky (CDict a DDict) žijí na pracovním procesu v C za 64bitovými handly ověřenými generací (handle CDict, handle DDict v zstd.idl). Jednorázové vytvoření slovníku pomocí ctx.NewCDict(rawDict, level) parsuje tabulky slovníku uvnitř pracovního procesu C a vrací lehký handle, který lze opakovaně používat v tisících voláních bez opětovného odesílání bajtů slovníku.
2. Amortizace režie IPC pomocí hromadného rámování (zstd_bulk.go)
Pro velká datová zatížení (16 KiB a více) je zanedbatelná doba kruhové cesty ve sdílené paměti trvající ~200 ns ve porovnání s časem CPU stráveným uvnitř libzstd. U úloh, které komprimují tisíce malých zpráv o velikosti 100 bajtů až 1 KiB najednou, se však placení i 200 ns na položku sčítá.
gosuda.org/zstd poskytuje funkce CompressBulk a DecompressBulk na objektu BulkCtx (zstd_bulk.go) k eliminaci kruhových cest na jednotlivé položky:
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)
Funkce CompressBulk zabalí celou dávku [][]byte do jednoho požadavkového rámce s předponou délky ve sdílené paměti. Pracovní proces C prochází v cyklu každou položku v jazyce C (compress_bulk v c/zstd_hooks.c), zapíše všechny komprimované rámce souvisle do slotu odpovědi a vrátí celou dávku v jediné kruhové cestě sdílené paměti.
3. Přežití ukončení pracovního procesu bez pádu volajícího
Protože zstd_server běží v samostatném operačním procesu, fatální selhání uvnitř pracovního procesu C nikdy nezpůsobí pád vaší aplikace v Go:
- Pokud je
zstd_serverexterně ukončen (například příkazemSIGKILLnebo událostí OOM), přenosová vrstva HQQ okamžitě detekuje přerušený protějšek 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 detekují, že sdílený výchozí pracovní proces zmizel, při dalším volání vytvoří novou ověřenou instancizstd_servera obnoví normální provoz.
Uspořádání repozitáře
Základní kód odděluje fasádu DataDog/zstd pro přímou náhradu od nízkoúrovňových primitiv relací a hromadných operací hqq-bindgen:
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)
Začínáme
Nainstalujte gosuda.org/zstd pomocí příkazu go get a používejte ji s nastavením CGO_ENABLED=0:
1go get gosuda.org/zstd@v0.3.10507
- Repozitář GitHub: https://github.com/gosuda/hqq-zstd
- Architektura a srovnávací testy: docs/ARCHITECTURE.md
- Jádro runtime HQQ: https://gosuda.org/hqq