GoSuda

gosuda.org/zstd: Drop-In DataDog/zstd komprese s nulovým CGO a izolací pádů

By Lemon Mint
views ...

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í:

  1. Komplikace při sestavování a nasazení: Stejně jako go-sqlite3 vyžaduje DataDog/zstd nastavení CGO_ENABLED=1 a 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 platformy linux/arm64 nebo windows/arm64 vyžaduje externí systémy C.
  2. 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ý OSCílová architektura (GOARCH)Vložený binární souborVelikost binárního souboruSestavovací řetězec nástrojů a provenience
Linuxamd64, arm64zstd_server_linux_<arch>~770 - 880 KiBGCC -O3, SLSA Build L3 Provenance
macOSarm64, amd64zstd_server_darwin_<arch>~660 - 795 KiBAppleClang -O3, SLSA Build L3 Provenance
Windowsamd64zstd_server_windows_amd64.exe~818 KiBMSVC -O2, SLSA Build L3 Provenance
Windowsarm64zstd_server_windows_arm64.exe~551 KiBzig 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
  1. Vygenerovaný klient hqq-bindgen zkopíruje src přímo do paměťově mapovaného slotu požadavku a přepne sekvenční slovo SeqLock.
  2. Pracovní proces v C přečte src na 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ím ZSTD_compressCCtx nebo ZSTD_decompressDCtx.
  3. Klient v Go zkopíruje výsledný řez ze slotu odpovědi do řezu dst poskytnutého volajícím. V ustáleném stavu provádějí funkce ctx.CompressTo(dst, src, level) a ctx.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_server externě ukončen (například příkazem SIGKILL nebo událostí OOM), přenosová vrstva HQQ okamžitě detekuje přerušený protějšek a vrátí typovanou chybu *rt.TransportError z probíhajícího volání Compress nebo Decompress.
  • 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 instanci zstd_server a 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