gosuda.org/zstd: Drop-In DataDog/zstd Compression with Zero CGO and Crash Isolation
A gosuda.org/zstd egy CGO_ENABLED=0 Go tömörítési könyvtár, amely a hivatalos C Zstandard (libzstd 1.5.7) motort futtatja egy izolált munkafolyamaton belül, a HQQ megosztott memóriát használva. Egy közvetlenül behelyettesíthető API-t biztosít a github.com/DataDog/zstd könyvtárhoz (beleértve az egyszeri Compress/Decompress műveleteket, afolyamatos io.Writer/io.Reader interfészeket, az előre feldolgozott szótárakat, valamint az újrahasznosítható, memóriafoglalás-mentes kontextusokat), miközben a C kód végrehajtását teljes egészében a Go futtatókörnyezet címtartományán kívül tartja.
1go get gosuda.org/zstd@v0.3.10507
Miért tartozik a kitömörítés a Go heapen kívülre?
A termelési háttérrendszerekben a tömörítés és a kitömörítés közvetlenül a nem megbízható bemeneti határon helyezkedik el. Naplógyűjtők, üzenetközvetítők, RPC-átjárók és tárolómotorok külsőleg biztosított keretek gigabájtjait tömörítik ki percenként.
Számos Go csapat választja a github.com/DataDog/zstd könyvtárat, mivel az közvetlenül kapcsolódik az upstream C libzstd könyvtárhoz: a C implementáció magas egyszálas teljesítményt, szoros szótártömörítési arányt nyújt kis rekordok esetén, valamint olyan heapen kívüli C arénákat (ZSTD_CCtx és ZSTD_DCtx) használ, amelyek nem növelik a Go szemétgyűjtési (garbage-collection) vizsgálati időket.
A libzstd C-szolgáltatásba való becsatolása a cgo révén azonban két üzemeltetési nehézséget von maga után:
- Fordítási és telepítési súrlódás: A
go-sqlite3könyvtárhoz hasonlóan aDataDog/zstdis megköveteli aCGO_ENABLED=1beállítást és egy C fordító láncot. A szolgáltatások keresztfordítása vagy a statikus binárisok építéselinux/arm64vagywindows/arm64rendszerekhez külső C sysrootokat igényel. - Megosztott címtartomány a nem megbízható bemeneteken: A sérült vagy ellenséges rakományok kitömörítése ugyanabban a virtuális címtartományban, mint a Go szerver, azt jelenti, hogy a natív C kód vagy az egyéni szótárkezelés bármilyen hibája leállíthatja az egész Go folyamatot.
A gosuda.org/zstd megőrzi a pontos upstream C libzstd 1.5.7 motort és az ismerős github.com/DataDog/zstd API-t, de a C végrehajtást egy ellenőrzött gyermekfolyamatba (zstd_server) helyezi át, amely a gyorsítótár-sor megosztott memóriáján keresztül kapcsolódik a Go programhoz.
Behelyettesíthető helyettesítő a github.com/DataDog/zstd könyvtárhoz
A github.com/DataDog/zstd könyvtárról való áttérés kizárólag az importálási útvonal "gosuda.org/zstd" értékre történő módosítását igényli. Nincs szükség külső bináris telepítésre vagy beállítási kódra: az első hívás alkalmával a gosuda.org/zstd létrehoz egy beágyazott, SLSA-hitelesített zstd_server binárist az adott platformhoz, ellenőrzi annak SHA-256 kivonatát a gosuda.org/hqq/secureexec segítségével, és megnyit egy megosztott memóriás munkamenet-készletet.
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log"
8
9 "gosuda.org/zstd"
10)
11
12func main() {
13 // 1. Egyszeri Compress / Decompress (tükrözi a github.com/DataDog/zstd működését)
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. Folyamatos (Streaming) író és olvasó szótártámogatással
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}
Támogatott platformok és beágyazott binárisok
Mind a hat célplatform előre elkészített, SHA-256 kivonattal rögzített munkafolyamat-binárisokat tartalmaz, amelyek platformspecifikus fordítási címkékkel (internal/embedded/embed_<goos>_<goarch>.go) vannak beágyazva:
| Cél OS | Cél architektúra (GOARCH) | Beágyazott bináris | Bináris méret | Fordítási lánc és eredet (Provenance) |
|---|---|---|---|---|
| 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 |
A megosztott memóriás adatútvonal belsejében
Annak érdekében, hogy az áteresztőképesség versenyképes maradjon a folyamaton belüli cgo megoldással, a gosuda.org/zstd elkerüli a socket sorosítást és a heap memóriafoglalásokat a kritikus útvonalon.
1. Memóriafoglalás-mentes slot kódolás (zstd_ctx.go és c/zstd_hooks.c)
Amikor egy újrahasznosítható zstd.Ctx kontextust használ (vagy amikor a csomagszintű Compress és Decompress függvények kölcsönöznek egy Ctx kontextust a belső munkamenet-készletből), az adatok közvetlenül a leképezett StandardLink sloton keresztül áramlanak:
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
- A generált
hqq-bindgenkliens közvetlenül a memóriába leképezett kérésslotba másolja azsrcadatokat, és átbillenti aSeqLockszekvenciaszót. - A C munkafolyamat a kérésslotból helyben olvassa be az
srcadatokat, és a válaszslot hasznos teher (payload) mutatóját közvetlenül célpufferként adja át aZSTD_compressCCtxvagyZSTD_decompressDCtxfüggvényeknek. - A Go kliens átmásolja a kapott szeletet a válaszslotból a hívó által megadott
dstszeletbe. Stabil állapotban actx.CompressTo(dst, src, level)és actx.DecompressInto(dst, src)függvények0 allocs/opmutatóval hajthatók végre.
Az előre feldolgozott szótárak (CDict és DDict) a C munkafolyamaton belül, generációellenőrzött 64 bites leírók (handle CDict, handle DDict a zstd.idl fájlban) mögött élnek. A szótár egyszeri létrehozása a ctx.NewCDict(rawDict, level) révén elemzi a szótártáblákat a C munkafolyamaton belül, és egy könnyűsúlyú leírót ad vissza, amely ezrekre rúgó hívások során újra felhasználható anélkül, hogy újra el kellene küldeni a szótárbájtokat.
2. Az IPC-fejlesztési költségek amortizálása tömeges keretezéssel (zstd_bulk.go)
Nagyobb adatsorok (16 KiB és felette) esetén a ~200 ns-os megosztott memóriás oda-vissza út elhanyagolható a libzstd belsejében eltöltött CPU-időhöz képest. Azoknál a munkaterheléseknél azonban, amelyek egyszerre több ezer kis méretű, 100 bájt és 1 KiB közötti üzenetet tömörítenek, a tételenkénti 200 ns megfizetése is számottevővé válik.
A gosuda.org/zstd a CompressBulk és DecompressBulk függvényeket biztosítja a BulkCtx (zstd_bulk.go) keretében a tételenkénti oda-vissza utak megszüntetésére:
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)
A CompressBulk a teljes [][]byte köteget egyetlen, hosszprefixszel ellátott kéréskeretté csomagolja a megosztott memóriában. A C munkafolyamat végigiterál minden egyes tételen C-ben (compress_bulk a c/zstd_hooks.c fájlban), az összes tömörített keretet folytonosan írja be a válaszslotba, és a teljes köteget egyetlen megosztott memóriás oda-vissza út alatt adja vissza.
3. Túlélés a munkafolyamat leállásakor a hívó összeomlása nélkül
Mivel a zstd_server egy külön operációsrendszer-folyamatban fut, a C munkafolyamaton belüli végzetes hiba soha nem dönti romba a Go alkalmazást:
- Ha a
zstd_serverfolyamatot külsőleg kényszerítik ki (példáulSIGKILLvagy OOM esemény által), a HQQ szállítási réteg azonnal érzékeli a megszakadt partnert, és egy típusos*rt.TransportErrorhibát ad vissza az aktuálisCompressvagyDecompresshívásból. - A csomagszintű függvények (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) automatikusan észlelik, hogy a megosztott alapértelmezett munkafolyamat megszűnt, a következő híváskor létrehoznak egy friss, ellenőrzöttzstd_serverpéldányt, és folytatják a normál működést.
Adattár elrendezése
A kódbázis elválasztja a behelyettesíthető DataDog/zstd homlokzatot az alacsony szintű hqq-bindgen munkamenettől és tömeges primitívektől:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # Behelyettesíthető Compress/Decompress API, automatikus helyreállítási készlet és SHA-256 kivonatok
3├── zstd_stream.go # Folyamatos (Streaming) Író/Olvasó (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Memóriafoglalás-mentes Ctx, CDict és DDict burkolók a hqq-bindgen felett
5├── zstd_bulk.go # Egyszeri oda-vissza utas CompressBulk és DecompressBulk keretezés
6├── zstd.go # Generált hqq-bindgen Go kliens a zstd IDL szolgáltatáshoz
7├── zstd.idl # HQQ IDL séma, amely definiálja a Context, CDict, DDict és RPC metódusokat
8├── internal/embedded/ # Platformcímkékkel ellátott //go:embed zstd_server binárisok (6 OS/Arch cél)
9└── c/ # C11 szerver implementáció (zstd_server.c, zstd_hooks.c, main.c)
Kezdő lépések
Telepítse a gosuda.org/zstd könyvtárat a go get paranccsal, és használja a CGO_ENABLED=0 beállítással:
1go get gosuda.org/zstd@v0.3.10507
- GitHub adattár: https://github.com/gosuda/hqq-zstd
- Architektúra és teljesítménymérések: docs/ARCHITECTURE.md
- HQQ alapszintű futtatókörnyezet: https://gosuda.org/hqq