---
id: e52d68870be3de9f644fcfc584bd8aec
author: Lemon Mint
title: 'gosuda.org/zstd: Совместимая замена DataDog/zstd с сжатием без CGO и изоляцией сбоев'
description: CGO_ENABLED=0 Go-библиотека Zstandard, которая запускает вышестоящую C-библиотеку libzstd 1.5.7 в рабочем процессе с общей памятью, с API, совместимым с DataDog/zstd, повторно используемыми контекстами и пакетной обработкой данных.
language: ru
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` — это библиотека сжатия на Go с `CGO_ENABLED=0`, которая запускает официальный C-движок Zstandard (`libzstd 1.5.7`) внутри изолированного рабочего процесса через разделяемую память [HQQ](https://gosuda.org/hqq). Она предоставляет API-замену с прямой совместимостью для `github.com/DataDog/zstd` (включая однократные `Compress`/`Decompress`, потоковые `io.Writer`/`io.Reader`, предварительно обработанные словари и переиспользуемые контексты с нулевым количеством аллокаций), при этом полностью выводя выполнение C-кода за пределы адресного пространства среды выполнения Go.

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

## Почему распаковка должна выполняться за пределами кучи Go

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

Многие команды Go выбирают `github.com/DataDog/zstd`, поскольку он напрямую связывается с исходной C-библиотекой `libzstd`: C-реализация обеспечивает высокую пропускную способность на одном ядре, эффективные коэффициенты сжатия со словарями для небольших записей и внешние по отношению к куче C-арены (`ZSTD_CCtx` и `ZSTD_DCtx`), которые не увеличивают время сканирования сборщиком мусора Go.

Однако подключение `libzstd` к сервису на Go через `cgo` создаёт две эксплуатационные проблемы:

1. **Трудности со сборкой и развёртыванием**: Так же, как `go-sqlite3`, `DataDog/zstd` требует `CGO_ENABLED=1` и целевой инструментарий C-компилятора. Кросс-компиляция сервисов или сборка статических бинарных файлов для `linux/arm64` или `windows/arm64` требует внешних C-sysroot.
2. **Общее адресное пространство при недоверенных входных данных**: Распаковка повреждённых или враждебных полезных нагрузок в том же виртуальном адресном пространстве, что и ваш Go-сервер, означает, что любая ошибка в нативном C-коде или в обработке пользовательских словарей может завершить весь процесс Go.

`gosuda.org/zstd` сохраняет точный исходный движок `libzstd 1.5.7` и знакомый API `github.com/DataDog/zstd`, но переносит выполнение 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` и открывает пул сессий разделяемой памяти.

```go
package main

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

	"gosuda.org/zstd"
)

func main() {
	// 1. One-shot Compress / Decompress (mirrors 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. Streaming Writer and Reader with Dictionary Support
	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)
}
```

### Поддерживаемые платформы и встроенные бинарные файлы

Все шесть целевых платформ поставляются с предварительно собранными рабочими бинарными файлами с закреплённым 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 |

## Внутри пути данных через разделяемую память

Чтобы сохранить пропускную способность, сопоставимую с in-process `cgo`, `gosuda.org/zstd` избегает сериализации через сокеты и аллокаций в куче на горячем пути.

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

При использовании переиспользуемого `zstd.Ctx` (или когда функции уровня пакета `Compress` и `Decompress` заимствуют `Ctx` из внутреннего пула сессий), данные передаются непосредственно через отображённый слот `StandardLink`:

```text
Go Caller (zstd.Ctx)                    Shared-Memory Slot                    C11 Worker (zstd_hooks.c)
--------------------                    ------------------                    -------------------------
src []byte  ------------------------->  Request Slot Payload  ------------->  ZSTD_compressCCtx(
                                                                                cctx, resp_slot, src, level)
dst []byte  <-------------------------  Response Slot Payload <-------------  Writes compressed frame directly
                                                                              into shared response slot
```

1. Сгенерированный клиент `hqq-bindgen` копирует `src` непосредственно в отображённый в память слот запроса и переключает слово последовательности `SeqLock`.
2. C-воркер читает `src` на месте из слота запроса и передаёт указатель на полезную нагрузку слота ответа непосредственно как буфер назначения в `ZSTD_compressCCtx` или `ZSTD_decompressDCtx`.
3. 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 нс незначительна по сравнению с процессорным временем, затрачиваемым внутри `libzstd`. Однако для рабочих нагрузок, которые сжимают тысячи небольших сообщений размером от 100 байт до 1 KiB за один раз, даже 200 нс на элемент накапливаются в существенную величину.

`gosuda.org/zstd` предоставляет `CompressBulk` и `DecompressBulk` в `BulkCtx` (`zstd_bulk.go`) для устранения обращений на каждый элемент:

```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` упаковывает весь пакет `[][]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` при следующем вызове и возобновляют нормальную работу.

## Структура репозитория

Кодовая база отделяет фасад `DataDog/zstd` для прямой замены от низкоуровневых примитивов сессий и пакетной обработки `hqq-bindgen`:

```text
gosuda.org/zstd
├── autolaunch.go / hashes.go  # Drop-in Compress/Decompress API, auto-recovery pool, and SHA-256 hashes
├── zstd_stream.go             # Streaming Writer/Reader (NewWriterLevelDict, NewReaderDict)
├── zstd_ctx.go                # Zero-allocation Ctx, CDict, and DDict wrappers over hqq-bindgen
├── zstd_bulk.go               # Single-round-trip CompressBulk and DecompressBulk framing
├── zstd.go                    # Generated hqq-bindgen Go client for the zstd IDL service
├── zstd.idl                   # HQQ IDL schema defining Context, CDict, DDict, and RPC methods
├── internal/embedded/         # Platform-tagged //go:embed zstd_server binaries (6 OS/Arch targets)
└── c/                         # C11 server implementation (zstd_server.c, zstd_hooks.c, main.c)
```

## Начало работы

Установите `gosuda.org/zstd` с помощью `go get` и используйте его с `CGO_ENABLED=0`:

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

- **Репозиторий GitHub**: [https://github.com/gosuda/hqq-zstd](https://github.com/gosuda/hqq-zstd)
- **Архитектура и бенчмарки**: [docs/ARCHITECTURE.md](https://github.com/gosuda/hqq-zstd/blob/main/docs/ARCHITECTURE.md)
- **HQQ Core Runtime**: [https://gosuda.org/hqq](https://gosuda.org/hqq)
