GoSuda

gosuda.org/zstd: Drop-In-yhteensopiva DataDog/zstd-pakkaus ilman CGO-riippuvuutta ja kaatumisten eristäminen

By Lemon Mint views ...

gosuda.org/zstd on CGO_ENABLED=0 Go-pakkauskirjasto, joka suorittaa virallisen C-pohjaisen Zstandard-moottorin (libzstd 1.5.7) eristetyssä työprosessissa HQQ-jaetun muistin kautta. Se tarjoaa pudotettavan korvaavan API:n github.com/DataDog/zstd-kirjastolle (mukaan lukien kertakäyttöiset Compress/Decompress, suoratoistavat io.Writer/io.Reader, esikäsitellyt sanakirjat sekä uudelleenkäytettävät nollaallokaatiokontekstit) ja pitää C-suorituksen kokonaan Go-ajonaikaisen osoiteavaruuden ulkopuolella.

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

Miksi purku kuuluu Go-keon ulkopuolelle

Tuotantotaustajärjestelmissä pakkaus ja purku sijaitsevat suoraan luottamattoman syötteen rajapinnalla. Lokinkerääjät, viestivälittäjät, RPC-yhdyskäytävät ja tallennusmoottorit purkavat gigatavuja ulkoista dataa kehyksinä joka minuutti.

Monet Go-tiimit valitsevat github.com/DataDog/zstd-kirjaston, koska se sitoutuu suoraan alkuperäiseen C-kirjastoon libzstd: C-toteutus tarjoaa korkean yksiytimisen läpimenon, tiiviit sanakirjapakkaussuhteet pienille tietueille sekä Go-keon ulkopuoliset C-areenat (ZSTD_CCtx ja ZSTD_DCtx), jotka eivät kasvata Go-roskienkeruun tarkastusaikoja.

libzstd:n liittäminen Go-palveluun cgo:n kautta tuo kuitenkin kaksi operatiivista ongelmaa:

  1. Rakentamisen ja käyttöönoton haasteet: Samoin kuin go-sqlite3, DataDog/zstd vaatii CGO_ENABLED=1-asetuksen ja kohdealustan C-kääntäjätoolchainin. Palveluiden ristikääntäminen tai staattisten binäärien rakentaminen kohteille linux/arm64 tai windows/arm64 edellyttää ulkoisia C-sysroot-ympäristöjä.
  2. Jaettu osoiteavaruus luottamattomilla syötteillä: Vioittuneiden tai vihamielisten hyötykuormien purku samassa virtuaalisessa osoiteavaruudessa Go-palvelimesi kanssa tarkoittaa, että mikä tahansa virhe natiivissa C-koodissa tai mukautetussa sanakirjakäsittelyssä voi lopettaa koko Go-prosessin.

gosuda.org/zstd säilyttää tarkan alkuperäisen C-pohjaisen libzstd 1.5.7-moottorin ja tutun github.com/DataDog/zstd-API:n, mutta siirtää C-suorituksen vahvistettuun lapsiprosessiin (zstd_server), joka on yhteydessä Go-ohjelmaasi välimuistirivin kokoisen jaetun muistin kautta.

Pudotettava korvaava toteutus github.com/DataDog/zstd-kirjastolle

Siirtyminen github.com/DataDog/zstd-kirjastosta edellyttää vain tuontipolun vaihtamista muotoon "gosuda.org/zstd". Ulkoista binääriasennusta tai alustuskoodia ei tarvita: ensimmäisellä kutsulla gosuda.org/zstd purkaa upotetun SLSA-todistetun zstd_server-binäärin alustallesi, varmistaa sen SHA-256-tiivisteen gosuda.org/hqq/secureexec-paketin avulla ja avaa jaetun muistin istuntoaltaan.

 1package main
 2
 3import (
 4	"bytes"
 5	"fmt"
 6	"io"
 7	"log"
 8
 9	"gosuda.org/zstd"
10)
11
12func main() {
13	// 1. Kertakäyttöinen Compress / Decompress (peilaa github.com/DataDog/zstd-kirjaston)
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. Suoratoistava Writer ja Reader sanakirjatuella
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}

Tuetut alustat ja upotetut binäärit

Kaikki kuusi kohdealustaa toimitetaan valmiiksi käännettyinä, SHA-256-kiinnitettyinä työbinäärinä, jotka upotetaan alustakohtaisilla rakennustunnisteilla (internal/embedded/embed_<goos>_<goarch>.go):

KohdekäyttöjärjestelmäKohdearkkitehtuuri (GOARCH)Upotettu binääriBinäärin kokoRakennustoolchain ja alkuperä
Linuxamd64, arm64zstd_server_linux_<arch>~770 - 880 KiBGCC -O3, SLSA Build L3 -alkuperä
macOSarm64, amd64zstd_server_darwin_<arch>~660 - 795 KiBAppleClang -O3, SLSA Build L3 -alkuperä
Windowsamd64zstd_server_windows_amd64.exe~818 KiBMSVC -O2, SLSA Build L3 -alkuperä
Windowsarm64zstd_server_windows_arm64.exe~551 KiBzig cc -target aarch64-windows-gnu -O3, SLSA L3

Jaetun muistin datapolun sisällä

Pitääkseen läpimenon kilpailukykyisenä sisäprosessin cgo:n kanssa gosuda.org/zstd välttää socket-sarjallistamisen ja keon allokaatiot kuumalla polulla.

1. Nollaallokaatioinen slot-koodaus (zstd_ctx.go ja c/zstd_hooks.c)

Kun käytät uudelleenkäytettävää zstd.Ctx-kontekstia (tai kun pakettitason Compress- ja Decompress-funktiot lainaavat Ctx-kontekstin sisäiseltä istuntoaltaalta), data kulkee suoraan kartoitetun StandardLink-slotin kautta:

1Go-kutsuja (zstd.Ctx)                   Jaetun muistin slot                   C11-työprosessi (zstd_hooks.c)
2---------------------                   ------------------                    -------------------------
3src []byte  ------------------------->  Pyyntöslotin hyötykuorma  ----------->  ZSTD_compressCCtx(
4                                                                                cctx, resp_slot, src, level)
5dst []byte  <-------------------------  Vastausslotin hyötykuorma <-----------  Kirjoittaa pakatun kehyksen suoraan
6                                                                              jaettuun vastausslottiin
  1. Generoitu hqq-bindgen-asiakas kopioi src-datan suoraan muistikartoitettuun pyyntöslottiin ja kääntää SeqLock-sekvenssisanan.
  2. C-työprosessi lukee src-datan paikallaan pyyntöslotista ja välittää vastausslotin hyötykuorman osoittimen suoraan kohdepuskurina funktiolle ZSTD_compressCCtx tai ZSTD_decompressDCtx.
  3. Go-asiakas kopioi tuloksena syntyneen siivun vastausslotista kutsujan antamaan dst-siivuun. Vakaassa tilassa ctx.CompressTo(dst, src, level) ja ctx.DecompressInto(dst, src) suorittavat 0 allocs/op.

Esikäsitellyt sanakirjat (CDict ja DDict) sijaitsevat C-työprosessissa sukupolvitarkistettujen 64-bittisten kahvojen (handle CDict, handle DDict tiedostossa zstd.idl) takana. Sanakirjan luominen kerran kutsulla ctx.NewCDict(rawDict, level) jäsentää sanakirjataulut C-työprosessissa ja palauttaa kevyen kahvan, jota voidaan käyttää uudelleen tuhansissa kutsuissa ilman sanakirjatavujen uudelleenlähettämistä.

2. IPC-yleiskustannusten tasaaminen joukkokehystyksellä (zstd_bulk.go)

Suurille hyötykuormille (16 KiB ja suuremmat) noin 200 ns:n jaetun muistin kierto on merkityksetön verrattuna libzstd:n sisällä kuluvaan CPU-aikaan. Työkuormissa, jotka pakkaavat tuhansia pieniä 100 tavun – 1 KiB:n viestejä kerralla, jo 200 ns per kohde kasvaa kuitenkin merkittäväksi.

gosuda.org/zstd tarjoaa CompressBulk- ja DecompressBulk-funktiot BulkCtx-tyypille (zstd_bulk.go) kierrosten poistamiseksi kohdekohtaisesti:

 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 pakkaa koko [][]byte-erän yhdeksi pituusetuliitteiseksi pyyntökehykseksi jaettuun muistiin. C-työprosessi käy jokaisen kohteen läpi C-koodissa (compress_bulk tiedostossa c/zstd_hooks.c), kirjoittaa kaikki pakatut kehykset peräkkäin vastausslottiin ja palauttaa koko erän yhdellä jaetun muistin kierrolla.

3. Työprosessin päättymisen selviäminen ilman kutsujan kaatumista

Koska zstd_server toimii erillisessä käyttöjärjestelmäprosessissa, C-työprosessin fataali virhe ei koskaan kaada Go-sovellustasi:

  • Jos zstd_server tapetaan ulkoisesti (esimerkiksi SIGKILL-signaalilla tai OOM-tapahtumalla), HQQ-siirtokerros havaitsee katkenneen vastapään välittömästi ja palauttaa tyypitetyn *rt.TransportError-virheen käynnissä olevasta Compress- tai Decompress-kutsusta.
  • Pakettitason funktiot (zstd.Compress, zstd.Decompress, zstd.NewWriter, zstd.NewReader) havaitsevat automaattisesti, että jaettu oletustyöprosessi on poissa, käynnistävät seuraavalla kutsulla uuden vahvistetun zstd_server-instanssin ja palauttavat normaalin toiminnan.

Repositorion rakenne

Koodikanta erottaa pudotettavan DataDog/zstd-julkisivun matalan tason hqq-bindgen-istunto- ja joukkoprimitiiveistä:

1gosuda.org/zstd
2├── autolaunch.go / hashes.go  # Pudotettava Compress/Decompress-API, automaattisen palautumisen allas ja SHA-256-tiivisteet
3├── zstd_stream.go             # Suoratoistava Writer/Reader (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go                # Nollaallokaatioinen Ctx, CDict ja DDict hqq-bindgen-kääreinä
5├── zstd_bulk.go               # Yhden kierroksen CompressBulk- ja DecompressBulk-kehystys
6├── zstd.go                    # Generoitu hqq-bindgen Go -asiakas zstd IDL -palvelulle
7├── zstd.idl                   # HQQ IDL -skeema, joka määrittelee Context-, CDict-, DDict- ja RPC-metodit
8├── internal/embedded/         # Alustakohtaisesti merkityt //go:embed zstd_server -binäärit (6 OS/arkkitehtuuri-kohdetta)
9└── c/                         # C11-palvelintoteutus (zstd_server.c, zstd_hooks.c, main.c)

Aloittaminen

Asenna gosuda.org/zstd komennolla go get ja käytä sitä asetuksella CGO_ENABLED=0:

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