GoSuda

gosuda.org/zstd: Drop-In-ersättning för DataDog/zstd-komprimering utan CGO och med krasch-isolering

By Lemon Mint views ...

gosuda.org/zstd är ett Go-kompressionsbibliotek med CGO_ENABLED=0 som kör den officiella C-implementationen av Zstandard (libzstd 1.5.7) i en isolerad arbetsprocess över HQQ-delat minne. Det tillhandahåller ett drop-in-ersättande API för github.com/DataDog/zstd (inklusive engångs-Compress/Decompress, strömmande io.Writer/io.Reader, förberedda ordböcker och återanvändbara kontexter utan allokeringar) samtidigt som C-exekveringen hålls helt utanför Go-runtimens adressutrymme.

1go get gosuda.org/zstd@v0.3.10507

Varför dekompression hör hemma utanför din Go-heap

I produktionsbackends befinner sig komprimering och dekompression direkt på gränsen till opålitlig indata. Loggsamlare, meddelandemäklare, RPC-gatewayer och lagringsmotorer dekomprimerar gigabyte med externt tillhandahållna ramar varje minut.

Många Go-team väljer github.com/DataDog/zstd eftersom det binder direkt mot uppströms C-biblioteket libzstd: C-implementationen levererar hög genomströmning per kärna, täta komprimeringsförhållanden med ordböcker för små poster samt off-heap C-arenor (ZSTD_CCtx och ZSTD_DCtx) som inte ökar tiden för skräpinsamlingens genomsökning av Go-heapen.

Att länka libzstd till en Go-tjänst via cgo medför dock två driftsmässiga problem:

  1. Friktion vid bygge och driftsättning: Precis som go-sqlite3 kräver DataDog/zstd CGO_ENABLED=1 och en C-kompilatorverktygskedja för målplattformen. Korskompilering av tjänster eller byggande av statiska binärer för linux/arm64 eller windows/arm64 kräver externa C-sysroots.
  2. Delat adressutrymme vid opålitliga indata: Dekomprimering av skadade eller angripande nyttolaster inuti samma virtuella adressutrymme som din Go-server innebär att varje fel i native C-kod eller i hantering av egna ordböcker kan avsluta hela Go-processen.

gosuda.org/zstd behåller den exakta uppströms C-implementationen libzstd 1.5.7 och det välbekanta API:et från github.com/DataDog/zstd, men flyttar C-exekveringen till en verifierad barnprocess (zstd_server) som är ansluten till ditt Go-program via delat minne anpassat till cacheradens storlek.

Drop-in-ersättning för github.com/DataDog/zstd

Migrering från github.com/DataDog/zstd kräver att endast importsökvägen ändras till "gosuda.org/zstd". Ingen extern binär installation eller konfigurationskod behövs: vid första anropet materialiserar gosuda.org/zstd en inbäddad SLSA-attesterad zstd_server-binär för din plattform, verifierar dess SHA-256-digest via gosuda.org/hqq/secureexec och öppnar en pool av delade minnessessioner.

 1package main
 2
 3import (
 4	"bytes"
 5	"fmt"
 6	"io"
 7	"log"
 8
 9	"gosuda.org/zstd"
10)
11
12func main() {
13	// 1. One-shot Compress / Decompress (speglar 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. Streaming Writer and Reader with Dictionary Support
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}

Stödda plattformar och inbäddade binärer

Alla sex målplattformar levereras med förbyggda worker-binärer med SHA-256-fästning, inbäddade via plattformsspecifika build-taggar (internal/embedded/embed_<goos>_<goarch>.go):

Målets OSMålarkitektur (GOARCH)Inbäddad binärBinärstorlekByggverktygskedja & proveniens
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

Inuti datavägen för delat minne

För att hålla genomströmningen konkurrenskraftig med in-process cgo undviker gosuda.org/zstd socketserialisering och heapallokeringar på den kritiska vägen.

1. Kodning av slots utan allokeringar (zstd_ctx.go och c/zstd_hooks.c)

När du använder en återanvändbar zstd.Ctx (eller när paketnivåfunktionerna Compress och Decompress lånar en Ctx från den interna sessionspoolen) flödar data direkt genom den mappade StandardLink-slotten:

1Go-anropare (zstd.Ctx)                  Delad minnesslot                      C11-worker (zstd_hooks.c)
2--------------------                    ------------------                    -------------------------
3src []byte  ------------------------->  Nyttolast i förfrågningsslot  ------->  ZSTD_compressCCtx(
4                                                                                cctx, resp_slot, src, level)
5dst []byte  <-------------------------  Nyttolast i svarsslot <-------------  Skriver den komprimerade ramen direkt
6                                                                              till den delade svarsslotten
  1. Den genererade hqq-bindgen-klienten kopierar src direkt till den minnesmappade förfrågningsslotten och vänder sekvensordet i SeqLock.
  2. C-workern läser src på plats från förfrågningsslotten och skickar svarsslottens nyttolastpekare direkt som destinationsbuffert till ZSTD_compressCCtx eller ZSTD_decompressDCtx.
  3. Go-klienten kopierar den resulterande slicen från svarsslotten till den av anroparen angivna dst-slicen. I stationärt läge utför ctx.CompressTo(dst, src, level) och ctx.DecompressInto(dst, src) 0 allocs/op.

Förberedda ordböcker (CDict och DDict) lever i C-workern bakom generationskontrollerade 64-bitars handtag (handle CDict, handle DDict i zstd.idl). Att skapa en ordbok en gång via ctx.NewCDict(rawDict, level) tolkar ordbokens tabeller inuti C-workern och returnerar ett lättviktigt handtag som kan återanvändas över tusentals anrop utan att ordbokens byte skickas på nytt.

2. Amortering av IPC-overhead med bulkramning (zstd_bulk.go)

För stora nyttolaster (16 KiB och större) är den cirka 200 ns långa rundresan via delat minne försumbar jämfört med CPU-tiden som går åt inuti libzstd. För arbetslaster som komprimerar tusentals små meddelanden om 100 byte till 1 KiB samtidigt blir dock även 200 ns per post betydande.

gosuda.org/zstd tillhandahåller CompressBulk och DecompressBulk på BulkCtx (zstd_bulk.go) för att eliminera rundresor per post:

 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 packar hela [][]byte-batchen i en enda längdprefixerad förfrågningsram i delat minne. C-workern loopar över varje post i C (compress_bulk i c/zstd_hooks.c), skriver alla komprimerade ramar sammanhängande till svarsslotten och returnerar hela batchen i en enda rundresa via delat minne.

3. Att överleva avslutad worker utan att krascha anroparen

Eftersom zstd_server körs i en separat OS-process kan ett fatalt fel inuti C-workern aldrig fälla ned ditt Go-program:

  • Om zstd_server dödas externt (till exempel genom SIGKILL eller en OOM-händelse) upptäcker HQQ-transportlagret den brutna motparten omedelbart och returnerar en typad *rt.TransportError från det pågående Compress- eller Decompress-anropet.
  • Paketnivåfunktioner (zstd.Compress, zstd.Decompress, zstd.NewWriter, zstd.NewReader) upptäcker automatiskt att den delade standardworkern är borta, startar en ny verifierad zstd_server-instans vid nästa anrop och återupptar normal drift.

Förvarets struktur

Kodbasen separerar drop-in-fasaden DataDog/zstd från de lågnivåprimitiver för session och bulk som tillhandahålls av hqq-bindgen:

1gosuda.org/zstd
2├── autolaunch.go / hashes.go  # Drop-in Compress/Decompress-API, pool med automatisk återhämtning och SHA-256-hashar
3├── zstd_stream.go             # Strömmande Writer/Reader (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go                # Ctx utan allokeringar, samt CDict- och DDict-omslag över hqq-bindgen
5├── zstd_bulk.go               # Bulkramning med en enda rundresa för CompressBulk och DecompressBulk
6├── zstd.go                    # Genererad hqq-bindgen Go-klient för zstd IDL-tjänsten
7├── zstd.idl                   # HQQ IDL-schema som definierar Context, CDict, DDict och RPC-metoder
8├── internal/embedded/         # Plattformstaggade //go:embed zstd_server-binärer (6 OS/arkitekturmål)
9└── c/                         # C11-serverimplementation (zstd_server.c, zstd_hooks.c, main.c)

Komma igång

Installera gosuda.org/zstd med go get och använd det med CGO_ENABLED=0:

1go get gosuda.org/zstd@v0.3.10507