gosuda.org/zstd: Drop-In DataDog/zstd tömörítés nulla CGO-val és összeomlás-elkülönítéssel

gosuda.org/zstd egy CGO_ENABLED=0 Go tömörítőkönyvtár, amely a hivatalos C Zstandard (libzstd 1.5.7) motort egy izolált worker folyamatban futtatja, HQQ megosztott memórián keresztül. A github.com/DataDog/zstd csomaghoz drop-in helyettesítő API-t biztosít (beleértve az egyszeri Compress/Decompress függvényeket, a streaming io.Writer/io.Reader típusokat, az előre feldolgozott szótárakat és az újrafelhasználható, nulla allokációs kontextusokat), miközben a C végrehajtás teljes egészében a Go futtatókörnyezet címtartományán kívül marad.
1go get gosuda.org/zstd@v0.3.10507
Miért tartozik a dekompresszió a Go heapen kívülre
Éles backend rendszerekben a tömörítés és a kicsomagolás közvetlenül a nem megbízható bemeneti határon történik. A naplógyűjtők, üzenetközvetítők, RPC-átjárók és tárolómotorok percenként gigabájtnyi külsőleg megadott keretet bontanak ki.
Sok Go csapat a github.com/DataDog/zstd csomagot választja, mert közvetlenül a felső szintű C libzstd könyvtárhoz kapcsolódik: a C implementáció nagy egymagos átviteli sebességet, szoros szótáras tömörítési arányt kis rekordokon, valamint heapen kívüli C memóriaterületeket (ZSTD_CCtx és ZSTD_DCtx) biztosít, amelyek nem növelik a Go szemétgyűjtés bejárási idejét.
A libzstd cgo segítségével történő Go szolgáltatásba integrálása azonban két üzemeltetési problémát okoz:
- Build- és telepítési súrlódás: A
go-sqlite3-hoz hasonlóan aDataDog/zstdis megköveteli aCGO_ENABLED=1beállítást és a céloldali C fordítóeszköztárat. A szolgáltatások keresztfordítása vagy statikus bináris fájlok építéselinux/arm64vagywindows/arm64célra külső C sysroot-okat igényel. - Közös címtartomány nem megbízható bemeneteken: A sérült vagy ellenséges adatok kicsomagolása 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ármely hibája leállíthatja a teljes Go folyamatot.
A gosuda.org/zstd megtartja a pontosan azonos 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, amely a Go programhoz cache-line méretű megosztott memórián keresztül kapcsolódik.
Drop-in csere a github.com/DataDog/zstd csomagra
A github.com/DataDog/zstd csomagról történő áttéréshez csak az importútvonalat kell "gosuda.org/zstd" értékre cserélni. Nincs szükség külső bináris telepítésre vagy beállító kódra: az első hívásnál a gosuda.org/zstd kibont egy beágyazott, SLSA-attesztált zstd_server binárist az adott platformra, ellenőrzi annak SHA-256 digesztjét a gosuda.org/hqq/secureexec segítségével, majd 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 hívások (a github.com/DataDog/zstd API-t tükrözi)
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. Streaming Writer és Reader 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áris fájlok
Mind a hat célplatform előre lefordított, SHA-256 digesztjével rögzített worker binárisokat tartalmaz, amelyek platformspecifikus build tageken keresztül vannak beágyazva (internal/embedded/embed_<goos>_<goarch>.go):
| Céloperációs rendszer | Célarchitektúra (GOARCH) | Beágyazott bináris fájl | Bináris méret | Build-eszköztár és eredet |
|---|---|---|---|---|
| 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 |
Megosztott memórián belüli adatút
Ahhoz, hogy az átviteli sebesség versenyképes maradjon a folyamatba ágyazott cgo megoldással szemben, a gosuda.org/zstd elkerüli a socket-alapú szerializációt és a heap-allokációkat a kritikus úton.
1. Nulla allokációs slot-kódolás (zstd_ctx.go és c/zstd_hooks.c)
Amikor újrafelhasználható zstd.Ctx objektumot használ (vagy amikor a csomagszintű Compress és Decompress függvények egy Ctx objektumot kölcsönöznek a belső munkamenet-készletből), az adatok közvetlenül a leképezett StandardLink slotokon 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 asrcadatot közvetlenül a memórián leképezett kérési slotba másolja, és megfordítja aSeqLocksorszámszót. - A C worker a
srcadatot helyben olvassa a kérési slotból, és a válasz slot payload mutatóját közvetlenül célpufferként adja át aZSTD_compressCCtxvagy aZSTD_decompressDCtxfüggvénynek. - A Go kliens az eredményül kapott szeletet a válasz slotból a hívó által megadott
dstszeletbe másolja. Állandósult állapotban actx.CompressTo(dst, src, level)és actx.DecompressInto(dst, src)0 allocs/opteljesítményt nyújt.
Az előre feldolgozott szótárak (CDict és DDict) a C workeren élnek, generáció-ellenőrzött 64 bites handle-ök mögött (handle CDict, handle DDict a zstd.idl fájlban). Egyszer létrehozva a szótárat a ctx.NewCDict(rawDict, level) hívással, a szótártáblák a C workeren kerülnek feldolgozásra, és egy könnyű handle-t kap vissza, amely a szótár bájtjainak újraküldése nélkül többezer hívás során újrafelhasználható.
2. Az IPC-költség amortizálása kötegelt keretezéssel (zstd_bulk.go)
Nagy adatmennyiségek (16 KiB és afölött) esetén a körülbelül 200 ns-os megosztott memórián belüli körforgási idő elhanyagolható a libzstd belsejében töltött CPU-időhöz képest. Azonban olyan munkaterhelések esetén, amelyek egyszerre több ezer kis, 100 bájttól 1 KiB-ig terjedő üzenetet tömörítenek, elemenként akár 200 ns-os költség is összeadódik.
A gosuda.org/zstd a BulkCtx típuson (zstd_bulk.go) keresztül biztosítja a CompressBulk és a DecompressBulk függvényeket, amelyek kiküszöbölik az elemenkénti körforgási időket:
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, hosszúság-előtagos kérési keretbe csomagolja a megosztott memóriában. A C worker C nyelven iterál minden elemen (compress_bulk a c/zstd_hooks.c fájlban), a tömörített kereteket folytonosan írja a válasz slotba, és a teljes köteget egyetlen megosztott memórián belüli körforgással adja vissza.
3. A worker leállásának túlélése a hívó összeomlása nélkül
Mivel a zstd_server külön operációs rendszer-folyamatban fut, a C workeren belüli végzetes hiba soha nem állítja le a Go alkalmazást:
- Ha a
zstd_serverkülső beavatkozás miatt leáll (példáulSIGKILLjelzés vagy OOM esemény következtében), a HQQ szállítási réteg azonnal észleli a megszakadt partnert, és egy típusos*rt.TransportErrorhibát ad vissza a folyamatban lévőCompressvagyDecompresshívásból. - A csomagszintű függvények (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) automatikusan észlelik, hogy a megosztott alapértelmezett worker megszűnt, a következő hívásnál új, ellenőrzöttzstd_serverpéldányt indítanak, és visszaáll a normál működés.
Tárolóstruktúra
A kódbázis elválasztja a DataDog/zstd drop-in homlokzatot az alacsony szintű hqq-bindgen munkamenet- és kötegelt primitívektől:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # Drop-in Compress/Decompress API, automatikus helyreállítási készlet és SHA-256 hash-ek
3├── zstd_stream.go # Streaming Writer/Reader (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Nulla allokációs Ctx, CDict és DDict burkolók a hqq-bindgen felett
5├── zstd_bulk.go # Egyetlen körforgású CompressBulk és DecompressBulk keretezés
6├── zstd.go # A zstd IDL szolgáltatáshoz generált hqq-bindgen Go kliens
7├── zstd.idl # HQQ IDL séma, amely a Context, CDict, DDict és RPC metódusokat definiálja
8├── internal/embedded/ # Platformcímkézett //go:embed zstd_server binárisok (6 OS/architektúra célplatform)
9└── c/ # C11 szerverimplementáció (zstd_server.c, zstd_hooks.c, main.c)
Első lépések
Telepítse a gosuda.org/zstd csomagot a go get paranccsal, és használja CGO_ENABLED=0 mellett:
1go get gosuda.org/zstd@v0.3.10507
- GitHub-tároló: https://github.com/gosuda/hqq-zstd
- Architektúra és benchmarkok: docs/ARCHITECTURE.md
- HQQ alapvető futtatókörnyezet: https://gosuda.org/hqq