---
id: e52d68870be3de9f644fcfc584bd8aec
author: Lemon Mint
title: 'gosuda.org/zstd: CGO不要かつクラッシュ隔離を備えたDataDog/zstdの完全互換ドロップイン置換'
description: CGO_ENABLED=0 の Go 向け Zstandard ライブラリであり、共有メモリワーカー内で上流の C ライブラリ libzstd 1.5.7 を実行します。DataDog/zstd 互換 API、再利用可能なコンテキスト、一括バッチ処理を備えています。
language: ja
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` は `CGO_ENABLED=0` で動作する Go 圧縮ライブラリであり、[HQQ](https://gosuda.org/hqq) 共有メモリを介して、公式の C Zstandard (`libzstd 1.5.7`) エンジンを隔離されたワーカープロセス内で実行します。`github.com/DataDog/zstd` に対するドロップイン置換 API を提供し（一回限りの `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 実装は、単一コアでの高いスループット、小さなレコードに対する高密度な辞書圧縮率、そして Go のガベージコレクションのスキャン時間を増大させないオフヒープの C アリーナ (`ZSTD_CCtx` および `ZSTD_DCtx`) を提供します。

しかしながら、`cgo` を介して `libzstd` を Go サービスにリンクすると、運用上の問題が二つ生じます。

1. **ビルドおよびデプロイの困難**: `go-sqlite3` と同様に、`DataDog/zstd` は `CGO_ENABLED=1` と対象となる C コンパイラツールチェーンを必要とします。`linux/arm64` や `windows/arm64` 向けにサービスをクロスコンパイルしたり、静的バイナリをビルドしたりするには、外部の C sysroot が必要になります。
2. **信頼されていない入力に対する共有アドレス空間**: 破損した、あるいは悪意のあるペイロードを Go サーバーと同じ仮想アドレス空間内で展開すると、ネイティブ C コードやカスタム辞書処理のバグによって Go プロセス全体が終了する可能性があります。

`gosuda.org/zstd` は、上流の C `libzstd 1.5.7` エンジンそのものと、慣れ親しんだ `github.com/DataDog/zstd` API を維持しつつ、C による実行を、キャッシュライン単位の共有メモリで Go プログラムに接続された検証済みの子プロセス (`zstd_server`) に移します。

## `github.com/DataDog/zstd` のドロップイン置換

`github.com/DataDog/zstd` から移行するには、インポートパスを `"gosuda.org/zstd"` に変更するだけで十分です。外部バイナリのインストールやセットアップコードは不要です。最初の呼び出し時に、`gosuda.org/zstd` は、お使いのプラットフォーム向けの SLSA 証明付きで埋め込まれた `zstd_server` バイナリを展開し、`gosuda.org/hqq/secureexec` を通じてその SHA-256 ダイジェストを検証し、共有メモリセッションプールを開きます。

```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)
}
```

### サポートされるプラットフォームと埋め込みバイナリ

six つの対象プラットフォームすべてに、プラットフォーム固有のビルドタグ (`internal/embedded/embed_<goos>_<goarch>.go`) を介して埋め込まれた、ビルド済みで SHA-256 により固定されたワーカーバイナリが含まれています。

| 対象 OS     | 対象アーキテクチャ (`GOARCH`) | 埋め込みバイナリ                | バイナリサイズ | ビルドツールチェーンと来歴                         |
| :---------- | :---------------------------- | :------------------------------ | -------------: | :------------------------------------------------ |
| **Linux**   | `amd64`, `arm64`              | `zstd_server_linux_<arch>`      | 約 770 - 880 KiB | GCC `-O3`、SLSA Build L3 来歴                     |
| **macOS**   | `arm64`, `amd64`              | `zstd_server_darwin_<arch>`     | 約 660 - 795 KiB | AppleClang `-O3`、SLSA Build L3 来歴              |
| **Windows** | `amd64`                       | `zstd_server_windows_amd64.exe` |       約 818 KiB | MSVC `-O2`、SLSA Build L3 来歴                    |
| **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` スロットを通じて直接流れます。

```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`) は、`zstd.idl` の `handle CDict`、`handle DDict` で定義された、世代検査付きの 64 ビットハンドルの背後で C ワーカー上に存在します。`ctx.NewCDict(rawDict, level)` で辞書を一度作成すると、C ワーカー内で辞書テーブルが解析され、辞書のバイト列を再送することなく、数千回の呼び出しで再利用できる軽量なハンドルが返されます。

### 2. バルクフレーミングによる IPC オーバーヘッドの償却 (`zstd_bulk.go`)

大きなペイロード（16 KiB 以上）の場合、約 200 ns の共有メモリのラウンドトリップは、`libzstd` 内で費やされる CPU 時間に比べて無視できる程度です。しかしながら、100 バイトから 1 KiB の小さなメッセージを数千個まとめて圧縮するワークロードでは、項目ごとに 200 ns を費やすだけでも累積します。

`gosuda.org/zstd` は、項目ごとのラウンドトリップを排除するために、`BulkCtx`（`zstd_bulk.go`）上に `CompressBulk` と `DecompressBulk` を提供します。

```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 内（`c/zstd_hooks.c` の `compress_bulk`）ですべての項目をループ処理し、圧縮されたフレームをすべて連続してレスポンススロットに書き込み、バッチ全体を単一の共有メモリのラウンドトリップで返します。

### 3. ワーカーの終了を生き延び、呼び出し元をクラッシュさせない

`zstd_server` は独立した OS プロセスで実行されるため、C ワーカー内で発生した致命的な障害が Go アプリケーション全体を停止させることはありません。

- `zstd_server` が外部から（例えば `SIGKILL` や OOM イベントによって）強制終了された場合、HQQ トランスポート層は切断されたピアを即座に検出し、進行中の `Compress` または `Decompress` 呼び出しから型付きの `*rt.TransportError` を返します。
- パッケージレベルの関数（`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)
```

## はじめに

`go get` で `gosuda.org/zstd` をインストールし、`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 コアランタイム**: [https://gosuda.org/hqq](https://gosuda.org/hqq)
