gosuda.org/zstd: Drop-In DataDog/zstd Compression with Zero CGO and Crash Isolation
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 изпълнението (runtime).
1go get gosuda.org/zstd@v0.3.10507
Защо декомпресията принадлежи извън вашата Go купа (heap)
В производствените бекенд системи компресията и декомпресията се намират директно на границата за ненадеждни входни данни. Колекторите на логове, съобщенните брокери, RPC шлюзовете и системите за съхранение декомпресират гигабайти външно предоставени рамки всяка минута.
Много Go екипи избират github.com/DataDog/zstd, тъй като тя се свързва директно с нагоре по веригата C libzstd: C реализацията осигурява висока пропускателна способност на едно ядро, стегнати коефициенти на компресия с речници при малки записи и C арени извън купата (ZSTD_CCtx и ZSTD_DCtx), които не увеличават времето за сканиране при събиране на паметта (garbage collection) в Go.
Свързването на libzstd в Go услуга чрез cgo обаче води до две оперативни затруднения:
- Триене при компилация и разгръщане: Точно както
go-sqlite3,DataDog/zstdизискваCGO_ENABLED=1и целеви инструмент за компилиране на C. Кръстосаното компилиране на услуги или изграждането на статични двоични файлове заlinux/arm64илиwindows/arm64изисква външни C системни коренови директории (sysroots). - Споделено адрeсно пространство при ненадеждни входни данни: Декомпресирането на повредени или злонамерени полезни данни в същото виртуално адресно пространство като вашия Go сървър означава, че всяка грешка в нативния C код или в обработката на потребителски речници може да прекрати целия Go процес.
gosuda.org/zstd запазва точния C libzstd 1.5.7 двигател нагоре по веригата и познатия github.com/DataDog/zstd API, но премества C изпълнението във верифициран дъщерен процес (zstd_server), свързан към вашата Go програма чрез споделена памет на ниво кеш линия.
Взаимозаменяем заместител на 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. Еднократно компресиране / декомпресиране (отразява 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. Стрийминг записващо и четящо устройство с поддръжка на речници
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, вградени чрез специфични за платформата тагове за компилация (internal/embedded/embed_<goos>_<goarch>.go):
| Целева ОС | Целева архитектура (GOARCH) | Вграден двоичен файл | Размер на двоичния файл | Инструменти за компилация и произход |
|---|---|---|---|---|
| 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 |
Вътре в пътя на данните със споделена памет
За да поддържа пропускателна способност, конкурентна на процесинга в рамките на cgo, gosuda.org/zstd избягва сериализацията в сокети и заделянето на памет в купата на горещия път.
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
- Генерираният клиент
hqq-bindgenкопираsrcдиректно в мапнатия в паметта слот за заявка и обръща секвенционната думаSeqLock. - C работният процес прочита
srcна място от слота за заявка и подава указателя към полезния товар на слота за отговор директно като целеви буфер къмZSTD_compressCCtxилиZSTD_decompressDCtx. - Go клиентът копира получения слайс от слота за отговор в предоставения от извикващата страна
dstслайс. В стабилно състояниеctx.CompressTo(dst, src, level)иctx.DecompressInto(dst, src)извършват0 allocs/op.
Предварително компилираните речници (CDict и DDict) съществуват в C работния процес зад проверени по поколение 64-битови дескриптори (handle CDict, handle DDict в zstd.idl). Създаването на речник веднъж чрез ctx.NewCDict(rawDict, level) анализира таблиците на речника в C работния процес и връща лек дескриптор, който може да се използва многократно в хиляди извиквания без повторно изпращане на байтовете на речника.
2. Амортизиране на IPC овърхеда с групово рамкиране (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), записва всички компресирани рамки последователно в слота за отговор и връща цялата партида в рамките на един кръгов път в споделената памет.
3. Оцеляване при прекратяване на работния процес без срив на извикващата страна
Тъй като zstd_server работи в отделен ОС процес, фаталната грешка вътре в C работния процес никога не срива вашето Go приложение:
- Ако
zstd_serverбъде прекратен отвън (например чрезSIGKILLили OOM събитие), транспортният слой на HQQ открива нарушената връзка незабавно и връща типизирана грешка*rt.TransportErrorот текущото извикване заCompressилиDecompress. - Функциите на ниво пакет (
zstd.Compress,zstd.Decompress,zstd.NewWriter,zstd.NewReader) автоматично откриват, че споделеният работен процес по подразбиране е изчезнал, стартират нов верифициран екземпляр наzstd_serverпри следващото извикване и възобновяват нормалната си работа.
Структура на хранилището (Repository Layout)
Кодовата база отделя взаимозаменяемия фасад DataDog/zstd от ниско ниво сесиите на hqq-bindgen и груповите примитиви:
1gosuda.org/zstd
2├── autolaunch.go / hashes.go # Взаимозаменяем Compress/Decompress API, пул за автоъзстановяване и SHA-256 хешове
3├── zstd_stream.go # Стрийминг записващо/четящо устройство (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go # Обвивки Ctx, CDict и DDict с нулево заделяне над hqq-bindgen
5├── zstd_bulk.go # Рамкиране на CompressBulk и DecompressBulk с един кръгов път
6├── zstd.go # Генериран hqq-bindgen Go клиент за zstd IDL услугата
7├── zstd.idl # HQQ IDL схема, дефинираща контекст, CDict, DDict и RPC методи
8├── internal/embedded/ # Двоични файлове zstd_server със съответните тагове за платформа //go:embed (6 целеви ОС/архитектури)
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
- Хранилище в GitHub: https://github.com/gosuda/hqq-zstd
- Архитектура и бенчмаркове: docs/ARCHITECTURE.md
- Основно изпълнение на HQQ: https://gosuda.org/hqq