GoSuda

gosuda.org/zstd: Drop-In съвместимост с DataDog/zstd, компресия със Zero CGO и изолация на сривовете

By Lemon Mint views ...

gosuda.org/zstd е Go библиотека за компресия с CGO_ENABLED=0, която изпълнява официалния C енджин на Zstandard (libzstd 1.5.7) в изолиран работен процес чрез споделена памет HQQ. Тя предоставя API за директна замяна на github.com/DataDog/zstd (включително еднократни Compress/Decompress, поточни io.Writer/io.Reader, предварително обработени речници и повторно използваеми контексти с нулева алокация), като същевременно изпълнението на C кода остава напълно извън адресното пространство на Go рънтайма.

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

Защо декомпресията трябва да бъде извън Go heap паметта

В продуктивни бекенд системи компресията и декомпресията се намират directly на границата с недоверени входни данни. Колектори на логове, брокери на съобщения, RPC шлюзове и системи за съхранение декомпресират гигабайти външно подадени фреймове всяка минута.

Много Go екипи избират github.com/DataDog/zstd, защото се свързва директно с upstream C libzstd: C имплементацията осигурява висока производителност на едно ядро, добри коефициенти на компресия с речници за малки записи и off-heap C арени (ZSTD_CCtx и ZSTD_DCtx), които не увеличават времето за сканиране от garbage collector-а на Go.

Свързването на libzstd с Go услуга чрез cgo обаче носи два оперативни проблема:

  1. Трудности при изграждане и внедряване: Подобно на go-sqlite3, DataDog/zstd изисква CGO_ENABLED=1 и целеви C компилаторен toolchain. Кроскомпилирането на услуги или изграждането на статични бинарни файлове за linux/arm64 или windows/arm64 изисква външни C sysroot среди.
  2. Споделено адресно пространство при недоверени входни данни: Декомпресирането на повредени или злонамерени полезни товари в същото виртуално адресно пространство като вашия Go сървър означава, че всеки бъг в native C кода или в персонализираното обработване на речници може да прекрати целия Go процес.

gosuda.org/zstd запазва точния upstream C енджин libzstd 1.5.7 и познатия API на github.com/DataDog/zstd, но премества изпълнението на C кода в проверен дъщерен процес (zstd_server), свързан с вашата Go програма чрез споделена памет, подравнена по cache line.

Директна замяна на github.com/DataDog/zstd

Миграцията от github.com/DataDog/zstd изисква промяна само на импорт пътя към "gosuda.org/zstd". Не е необходима инсталация на външен бинарен файл или код за настройка: при първото извикване gosuda.org/zstd материализира вграден zstd_server бинарен файл с SLSA атестация за вашата платформа, проверява SHA-256 дайджеста му чрез gosuda.org/hqq/secureexec и отваря пул от сесии в споделена памет.

 1package main
 2
 3import (
 4	"bytes"
 5	"fmt"
 6	"io"
 7	"log"
 8
 9	"gosuda.org/zstd"
10)
11
12func main() {
13	// 1. Еднократни Compress / Decompress (повтаря 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. Поточни Writer и Reader с поддръжка на речници
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}

Поддържани платформи и вградени бинарни файлове

Всички шест целеви платформи се доставят с предварително компилирани работни бинарни файлове, фиксирани чрез SHA-256 и вградени чрез платформено-специфични build tags (internal/embedded/embed_<goos>_<goarch>.go):

Целева ОСЦелева архитектура (GOARCH)Вграден бинарен файлРазмер на файлаBuild toolchain и произход
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

Вътрешна структура на пътя за данни в споделена памет

За да остане пропускателната способност конкурентна спрямо in-process cgo, gosuda.org/zstd избягва сериализация през сокети и heap алокации в горещия път.

1. Кодиране в слот без алокация (zstd_ctx.go и c/zstd_hooks.c)

Когато използвате повторно използваем zstd.Ctx (или когато функциите на пакетно ниво Compress и Decompress заемат Ctx от вътрешния пул от сесии), данните текат директно през картографирания 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. Генерираният hqq-bindgen клиент копира src directly в картографирания request слот и превключва sequence word-а на SeqLock.
  2. C работникът чете src на място от request слота и подава указателя към полезния товар на response слота directly като целеви буфер на ZSTD_compressCCtx или ZSTD_decompressDCtx.
  3. Go клиентът копира получения slice от response слота в подадения от извикващия slice dst. В стационарно състояние ctx.CompressTo(dst, src, level) и ctx.DecompressInto(dst, src) извършват 0 allocs/op.

Предварително обработените речници (CDict и DDict) живеят в C работника зад генерационно проверявани 64-битови handles (handle CDict, handle DDict в zstd.idl). Създаването на речник веднъж чрез ctx.NewCDict(rawDict, level) парсва таблиците на речника в C работника и връща лек handle, който може да се използва повторно при хиляди извиквания, без повторно изпращане на байтовете на речника.

2. Амортизиране на IPC разходите чрез bulk framing (zstd_bulk.go)

При големи полезни товари (16 KiB и повече) ~200 ns цикъл през споделената памет е пренебрежим спрямо процесорното време, прекарано в libzstd. При работни натоварвания, които компресират хиляди малки съобщения от 100 байта до 1 KiB наведнъж, обаче заплащането на дори 200 ns за всеки елемент се натрупва.

gosuda.org/zstd предоставя CompressBulk и DecompressBulk върху BulkCtx (zstd_bulk.go), за да елиминира обмените по елемент:

 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 опакова целия пакет [][]byte в една заявка с префикс за дължина в споделената памет. C работникът обхожда всеки елемент на C ниво (compress_bulk в c/zstd_hooks.c), записва всички компресирани фреймове последователно в response слота и връща целия пакет при един обмен през споделената памет.

3. Оцеляване при прекратяване на работника без срив на извикващия

Тъй като zstd_server работи в отделен OS процес, фатален провал в C работника никога не сваля вашето Go приложение:

  • Ако zstd_server бъде убит externally (например чрез SIGKILL или OOM събитие), транспортният слой на HQQ незабавно открива нарушения peer и връща типизиран *rt.TransportError от текущото извикване на Compress или Decompress.
  • Функциите на пакетно ниво (zstd.Compress, zstd.Decompress, zstd.NewWriter, zstd.NewReader) автоматично откриват, че споделеният по подразбиране работник е изчезнал, стартират нова проверена инстанция на zstd_server при следващото извикване и възобновяват нормалната работа.

Структура на хранилището

Кодовата база отделя facade-а DataDog/zstd за директна замяна от низкоуровневите примитиви за сесии и bulk операции на hqq-bindgen:

1gosuda.org/zstd
2├── autolaunch.go / hashes.go  # API за Compress/Decompress за директна замяна, пул с автоматично възстановяване и SHA-256 хешове
3├── zstd_stream.go             # Поточни Writer/Reader (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go                # Ctx без алокация, CDict и DDict обвивки върху hqq-bindgen
5├── zstd_bulk.go               # Framing за CompressBulk и DecompressBulk с един обмен
6├── zstd.go                    # Генериран hqq-bindgen Go клиент за IDL услугата zstd
7├── zstd.idl                   # HQQ IDL схема, дефинираща Context, CDict, DDict и RPC методи
8├── internal/embedded/         # Платформено-тагнати //go:embed бинарни файлове zstd_server (6 OS/Arch цели)
9└── c/                         # Имплементация на C11 сървъра (zstd_server.c, zstd_hooks.c, main.c)

Начало на работа

Инсталирайте gosuda.org/zstd с go get и го използвайте с CGO_ENABLED=0:

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