---
id: e52d68870be3de9f644fcfc584bd8aec
author: Lemon Mint
title: 'gosuda.org/zstd: Zamiennik DataDog/zstd z kompresją bez CGO i izolacją awarii'
description: CGO_ENABLED=0 biblioteka Go Zstandard uruchamiająca upstreamową bibliotekę C libzstd 1.5.7 w procesie roboczym ze wspólną pamięcią, z API zgodnym z DataDog/zstd, wielokrotnie wykorzystywanymi kontekstami oraz grupowym przetwarzaniem wsadowym.
language: pl
date: 2026-10-18T14:07:11.281986997Z
path: /zstd
go_package: gosuda.org/zstd
go_repourl: https://github.com/gosuda/hqq-zstd.git
category: lowlevel
---

`gosuda.org/zstd` to biblioteka kompresji dla Go działająca z `CGO_ENABLED=0`, która uruchamia oficjalny silnik C Zstandard (`libzstd 1.5.7`) w odizolowanym procesie roboczym za pośrednictwem pamięci współdzielonej [HQQ](https://gosuda.org/hqq). Udostępnia ona zamiennik API typu drop-in dla `github.com/DataDog/zstd` (obejmujący jednorazowe `Compress`/`Decompress`, strumieniowe `io.Writer`/`io.Reader`, wstępnie przetworzone słowniki oraz wielokrotnego użytku konteksty bez alokacji), przy zachowaniu pełnego oddzielenia wykonania kodu C od przestrzeni adresowej środowiska uruchomieniowego Go.

```bash
go get gosuda.org/zstd@v0.3.10507
```

## Dlaczego dekompresja powinna znajdować się poza heapem Go

W środowiskach produkcyjnych backendów kompresja i dekompresja znajdują się bezpośrednio na granicy niezaufanych danych wejściowych. Kolektory logów, brokery komunikatów, bramy RPC oraz silniki pamięci masowej dekompresują co minutę gigabajty zewnętrznie dostarczonych ramek.

Wiele zespołów korzystających z Go wybiera `github.com/DataDog/zstd`, ponieważ bezpośrednio wiąże się ono z oryginalną biblioteką C `libzstd`: implementacja w C zapewnia wysoką przepustowość na pojedynczym rdzeniu, wysoki stopień kompresji słownikowej dla małych rekordów oraz pozaheapowe areny C (`ZSTD_CCtx` i `ZSTD_DCtx`), które nie zwiększają czasu skanowania przez garbage collector Go.

Połączenie `libzstd` z usługą Go za pośrednictwem `cgo` wiąże się jednak z dwoma problemami operacyjnymi:

1. **Utrudnienia w budowaniu i wdrażaniu**: podobnie jak `go-sqlite3`, `DataDog/zstd` wymaga `CGO_ENABLED=1` oraz docelowego zestawu narzędzi kompilatora C. Kompilacja krzyżowa usług lub tworzenie statycznych plików binarnych dla `linux/arm64` lub `windows/arm64` wymaga zewnętrznych sysroot C.
2. **Wspólna przestrzeń adresowa dla niezaufanych danych wejściowych**: dekompresja uszkodzonych lub przygotowanych w sposób wrogi ładunków w tej samej przestrzeni adresowej co serwer Go oznacza, że każdy błąd w natywnym kodzie C lub w obsłudze niestandardowych słowników może zakończyć cały proces Go.

`gosuda.org/zstd` zachowuje dokładny, oryginalny silnik `libzstd 1.5.7` w C oraz znane API `github.com/DataDog/zstd`, lecz przenosi wykonanie kodu C do zweryfikowanego procesu potomnego (`zstd_server`) połączonego z programem Go za pośrednictwem pamięci współdzielonej wyrównanej do linii cache.

## Zamiennik typu drop-in dla `github.com/DataDog/zstd`

Migracja z `github.com/DataDog/zstd` wymaga zmiany wyłącznie ścieżki importu na `"gosuda.org/zstd"`. Nie jest potrzebna instalacja zewnętrznych plików binarnych ani kod konfiguracyjny: przy pierwszym wywołaniu `gosuda.org/zstd` udostępnia osadzony plik binarny `zstd_server` dla danej platformy, poświadczony w standardzie SLSA, weryfikuje jego skrót SHA-256 za pomocą `gosuda.org/hqq/secureexec` i otwiera pulę sesji pamięci współdzielonej.

```go
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"

	"gosuda.org/zstd"
)

func main() {
	// 1. Jednorazowe Compress / Decompress (odpowiada github.com/DataDog/zstd)
	src := []byte("shared-memory zstd payload without cgo")
	compressed, err := zstd.CompressLevel(nil, src, zstd.BestSpeed)
	if err != nil {
		log.Fatal(err)
	}
	plain, err := zstd.Decompress(nil, compressed)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("one-shot: %d bytes -> %d bytes (%s)\n", len(src), len(compressed), plain)

	// 2. Strumieniowe Writer i Reader z obsługą słowników
	var buf bytes.Buffer
	w := zstd.NewWriterLevel(&buf, zstd.DefaultCompression)
	if _, err := w.Write(src); err != nil {
		log.Fatal(err)
	}
	if err := w.Close(); err != nil {
		log.Fatal(err)
	}

	r := zstd.NewReader(&buf)
	defer r.Close()
	streamed, err := io.ReadAll(r)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("streamed: %s\n", streamed)
}
```

### Obsługiwane platformy i osadzone pliki binarne

Wszystkie sześć docelowych platform zawiera przygotowane wstępnie, przypięte skrótem SHA-256 pliki binarne procesu roboczego, osadzone za pomocą specyficznych dla platformy znaczników budowania (`internal/embedded/embed_<goos>_<goarch>.go`):

| System docelowy | Architektura docelowa (`GOARCH`) | Osadzony plik binarny           |   Rozmiar pliku | Łańcuch narzędzi budowania i pochodzenie                 |
| :-------------- | :------------------------------- | :------------------------------ | --------------: | :------------------------------------------------------- |
| **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        |

## Wnętrze ścieżki danych we pamięci współdzielonej

Aby utrzymać przepustowość porównywalną z `cgo` działającym w tym samym procesie, `gosuda.org/zstd` unika serializacji przez gniazda oraz alokacji sterty na gorącej ścieżce.

### 1. Kodowanie slotów bez alokacji (`zstd_ctx.go` i `c/zstd_hooks.c`)

Gdy używany jest wielokrotnego użytku `zstd.Ctx` (lub gdy funkcje pakietowe `Compress` i `Decompress` pożyczają `Ctx` z wewnętrznej puli sesji), dane przepływają bezpośrednio przez zmapowany slot `StandardLink`:

```text
Wywołujący Go (zstd.Ctx)                Slot pamięci współdzielonej           Worker C11 (zstd_hooks.c)
------------------------                ---------------------------           -------------------------
src []byte  ------------------------->  Ładunek slotu żądania  ------------->  ZSTD_compressCCtx(
                                                                                cctx, resp_slot, src, level)
dst []byte  <-------------------------  Ładunek slotu odpowiedzi <------------  Zapisuje skompresowaną ramkę bezpośrednio
                                                                              do slotu odpowiedzi we pamięci współdzielonej
```

1. Wygenerowany klient `hqq-bindgen` kopiuje `src` bezpośrednio do zmapowanego w pamięci slotu żądania i przełącza słowo sekwencji `SeqLock`.
2. Worker C odczytuje `src` w miejscu z slotu żądania i przekazuje wskaźnik ładunku slotu odpowiedzi bezpośrednio jako bufor docelowy do `ZSTD_compressCCtx` lub `ZSTD_decompressDCtx`.
3. Klient Go kopiuje wynikowy wycinek z slotu odpowiedzi do dostarczonego przez wywołującego wycinka `dst`. W stanie ustalonym `ctx.CompressTo(dst, src, level)` oraz `ctx.DecompressInto(dst, src)` wykonują `0 allocs/op`.

Wstępnie przetworzone słowniki (`CDict` i `DDict`) znajdują się po stronie workera C za uchwytami 64-bitowymi kontrolowanymi pod kątem generacji (`handle CDict`, `handle DDict` w `zstd.idl`). Jednorazowe utworzenie słownika za pomocą `ctx.NewCDict(rawDict, level)` przetwarza tabele słownika wewnątrz workera C i zwraca lekki uchwyt, który może być wielokrotnie używany w tysiącach wywołań bez ponownego przesyłania bajtów słownika.

### 2. Amortyzacja narzutu IPC dzięki framingowi zbiorczemu (`zstd_bulk.go`)

Dla dużych ładunków (16 KiB i większych) około 200 ns opóźnienia pełnej rundy przez pamięć współdzieloną jest pomijalne w porównaniu z czasem procesora spędzonym wewnątrz `libzstd`. Jednak dla obciążeń kompresujących tysiące małych komunikatów (od 100 bajtów do 1 KiB) jednocześnie, nawet 200 ns na element sumuje się do znaczącego kosztu.

`gosuda.org/zstd` udostępnia `CompressBulk` i `DecompressBulk` w `BulkCtx` (`zstd_bulk.go`), aby wyeliminować rundy na każdy element:

```go
bctx, err := client.NewBulkCtx(1 << 20)
if err != nil {
	log.Fatal(err)
}
defer bctx.Close()

items := [][]byte{
	[]byte(`{"event":"login","user":101}`),
	[]byte(`{"event":"purchase","user":101,"amount":4200}`),
	[]byte(`{"event":"logout","user":101}`),
}
compressedItems, err := bctx.CompressBulk(nil, items, zstd.DefaultCompression)
```

`CompressBulk` pakuje całą partię `[][]byte` w pojedynczą ramkę żądania z prefiksem długości w pamięci współdzielonej. Worker C przechodzi w pętli przez każdy element w kodzie C (`compress_bulk` w `c/zstd_hooks.c`), zapisuje wszystkie skompresowane ramki w sposób ciągły do slotu odpowiedzi i zwraca całą partię w ramach jednej rundy przez pamięć współdzieloną.

### 3. Przetrwanie zakończenia workera bez awarii wywołującego

Ponieważ `zstd_server` działa w osobnym procesie systemowym, krytyczna awaria wewnątrz workera C nigdy nie zakańcza aplikacji Go:

- Jeśli `zstd_server` zostanie zabity z zewnątrz (na przykład przez `SIGKILL` lub zdarzenie OOM), warstwa transportu HQQ natychmiast wykrywa uszkodzonego partnera i zwraca typowany błąd `*rt.TransportError` z trwającego wywołania `Compress` lub `Decompress`.
- Funkcje pakietowe (`zstd.Compress`, `zstd.Decompress`, `zstd.NewWriter`, `zstd.NewReader`) automatycznie wykrywają, że współdzielony domyślny worker zniknął, uruchamiają nową, zweryfikowaną instancję `zstd_server` przy następnym wywołaniu i wznawiają normalne działanie.

## Układ repozytorium

Kod źródłowy rozdziela fasadę typu drop-in `DataDog/zstd` od niskopoziomowych prymitywów sesji i pakietowania zbiorczego `hqq-bindgen`:

```text
gosuda.org/zstd
├── autolaunch.go / hashes.go  # API Compress/Decompress typu drop-in, pula z automatycznym odzyskiwaniem oraz skróty SHA-256
├── zstd_stream.go             # Strumieniowe Writer/Reader (NewWriterLevelDict, NewReaderDict)
├── zstd_ctx.go                # Ctx bez alokacji oraz opakowania CDict i DDict nad hqq-bindgen
├── zstd_bulk.go               # Framing CompressBulk i DecompressBulk w jednej rundzie
├── zstd.go                    # Wygenerowany klient Go hqq-bindgen dla usługi zstd IDL
├── zstd.idl                   # Schemat HQQ IDL definiujący Context, CDict, DDict oraz metody RPC
├── internal/embedded/         # Osadzone za pomocą //go:embed pliki binarne zstd_server ze znacznikami platform (6 celów OS/Arch)
└── c/                         # Implementacja serwera C11 (zstd_server.c, zstd_hooks.c, main.c)
```

## Pierwsze kroki

Zainstaluj `gosuda.org/zstd` za pomocą `go get` i używaj go przy `CGO_ENABLED=0`:

```bash
go get gosuda.org/zstd@v0.3.10507
```

- **Repozytorium GitHub**: [https://github.com/gosuda/hqq-zstd](https://github.com/gosuda/hqq-zstd)
- **Architektura i benchmarki**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-zstd/blob/main/docs/ARCHITECTURE.md)
- **Główne środowisko uruchomieniowe HQQ**: [https://gosuda.org/hqq](https://gosuda.org/hqq)
