GoSuda

gosuda.org/zstd: Sıfır CGO ve Çökme İzolasyonu ile Drop-In DataDog/zstd Sıkıştırması

By Lemon Mint
views ...

gosuda.org/zstd, HQQ paylaşımlı belleği üzerinde izole edilmiş bir işçi (worker) süreci içinde resmi C Zstandard (libzstd 1.5.7) motorunu çalıştıran bir CGO_ENABLED=0 Go sıkıştırma kütüphanesidir. C yürütme işlemlerini tamamen Go çalışma zamanı (runtime) adres alanının dışında tutarken, github.com/DataDog/zstd için doğrudan kullanılabilir (drop-in replacement) bir API sağlar (tek seferlik Compress/Decompress, akış tabanlı io.Writer/io.Reader, önceden derlenmiş sözlükler ve yeniden kullanılabilir sıfır tahsisli (zero-allocation) bağlamlar dahil).

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

Neden Veri Çıkarma (Decompression) İşlemi Go Yığınınızın (Heap) Dışında Olmalıdır?

Üretim (production) ortamındaki arka uçlarda, sıkıştırma ve sıkıştırma açma işlemleri doğrudan güvenilmeyen girdi sınırında yer alır. Log toplayıcılar, mesaj aracıkları (message brokers), RPC ağ geçitleri ve depolama motorları her dakika dışarıdan sağlanan gigabaytlarca çerçeveyi açar.

Birçok Go ekibi github.com/DataDog/zstd kütüphanesini tercih eder çünkü doğrudan yukarı akış (upstream) C libzstd kütüphanesine bağlanır: C uygulaması yüksek tek çekirdekli iş hacmi (throughput), küçük kayıtlar üzerinde sıkı sözlük sıkıştırma oranları ve Go çöp toplayıcı tarama sürelerini artırmayan yığın dışı C alanları (ZSTD_CCtx ve ZSTD_DCtx) sunar.

Ancak, libzstd kütüphanesini cgo aracılığıyla bir Go servisine bağlamak iki operasyonel zorluk getirir:

  1. Derleme ve Dağıtım Sürtünmesi: go-sqlite3 gibi, DataDog/zstd de CGO_ENABLED=1 ve hedef bir C derleyici araç zinciri gerektirir. Servisleri çapraz derlemek (cross-compile) veya linux/arm64 ya da windows/arm64 için statik ikili dosyalar (binary) oluşturmak harici C sistem kökleri (sysroots) gerektirir.
  2. Güvenilmeyen Girdiler Üzerinde Paylaşımlı Adres Alanı: Bozulmuş veya kötü niyetli yüklerin (payloads) Go sunucunuzla aynı sanal adres alanı içinde sıkıştırmasının açılması, yerel C kodundaki veya özel sözlük yönetimindeki herhangi bir hatanın tüm Go sürecini sonlandırabileceği anlamına gelir.

gosuda.org/zstd, orijinal yukarı akış C libzstd 1.5.7 motorunu ve tanıdık github.com/DataDog/zstd API'sini korur, ancak C yürütme işlemini Go programınıza önbellek satırı (cache-line) paylaşımlı belleği üzerinden bağlanan doğrulanmış bir alt sürece (zstd_server) taşır.

github.com/DataDog/zstd için Doğrudan Kullanılabilir Alternatif

github.com/DataDog/zstd kütüphanesinden geçiş yapmak, yalnızca içe aktarma yolunu (import path) "gosuda.org/zstd" olarak değiştirmeyi gerektirir. Hiçbir harici ikili dosya kurulumu veya kurulum kodu gerekmez: ilk çağrıda gosuda.org/zstd, platformunuz için gömülü, SLSA onaylı bir zstd_server ikili dosyasını somutlaştırır, SHA-256 özetini gosuda.org/hqq/secureexec aracılığıyla doğrular ve bir paylaşımlı bellek oturum havuzu açar.

 1package main
 2
 3import (
 4	"bytes"
 5	"fmt"
 6	"io"
 7	"log"
 8
 9	"gosuda.org/zstd"
10)
11
12func main() {
13	// 1. Tek seferlik Sıkıştır / Sıkıştırmayı Aç (github.com/DataDog/zstd ile aynıdır)
14	src := []byte("cgo olmadan paylaşımlı bellek zstd yükü")
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("tek seferlik: %d bayt -> %d bayt (%s)\n", len(src), len(compressed), plain)
24
25	// 2. Sözlük Desteği ile Akış Tabanlı Yazar (Writer) ve Okuyucu (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("akışlı: %s\n", streamed)
42}

Desteklenen Platformlar ve Gömülü İkili Dosyalar

Altı hedef platformun tümü, platforma özgü derleme etiketleri (internal/embedded/embed_<goos>_<goarch>.go) aracılığıyla gömülmüş, önceden derlenmiş ve SHA-256 ile sabitlenmiş işçi ikili dosyalarıyla birlikte gelir:

Hedef İşletim SistemiHedef Mimar (GOARCH)Gömülü İkili Dosyaİkili BoyutuDerleme Araç Zinciri & Köken (Provenance)
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

Paylaşımlı Bellek Veri Yolunun İç Yapısı

İşlem içi cgo ile rekabet edebilir iş hacmini korumak için gosuda.org/zstd, kritik yolda soket serileştirmesinden ve yığın (heap) tahsislerinden kaçınır.

1. Sıfır Tahsisli Yuva Kodlaması (zstd_ctx.go ve c/zstd_hooks.c)

Yeniden kullanılabilir bir zstd.Ctx kullandığınızda (veya paket düzeyindeki Compress ve Decompress fonksiyonları dahili oturum havuzundan bir Ctx ödünç aldığında), veriler doğrudan eşlenen StandardLink yuvası üzerinden akar:

1Go Çağıran (zstd.Ctx)                  Paylaşımlı Bellek Yuvası              C11 İşçisi (zstd_hooks.c)
2--------------------                    ------------------                    -------------------------
3src []byte  ------------------------->  İstek Yuvası Yükü     ------------->  ZSTD_compressCCtx(
4                                                                                cctx, resp_slot, src, level)
5dst []byte  <-------------------------  Yanıt Yuvası Yükü     <-------------  Sıkıştırılmış çerçeveyi doğrudan
6                                                                              paylaşımlı yanıt yuvasına yazar
  1. Üretilen hqq-bindgen istemcisi, src verisini doğrudan belleğe eşlenmiş istek yuvasına kopyalar ve SeqLock sıra sözcüğünü (sequence word) değiştirir.
  2. C işçisi, istek yuvasındaki src verisini yerinde (in place) okur ve yanıt yuvasının yük işaretçisini hedef arabellek (buffer) olarak doğrudan ZSTD_compressCCtx veya ZSTD_decompressDCtx fonksiyonuna iletir.
  3. Go istemcisi, elde edilen dilimi (slice) yanıt yuvasından çağrı yapan tarafından sağlanan dst dilimine kopyalar. Kararlı durumda, ctx.CompressTo(dst, src, level) ve ctx.DecompressInto(dst, src) fonksiyonları 0 allocs/op gerçekleştirir.

Önceden derlenmiş sözlükler (CDict ve DDict), C işçisinde nesil denetimli 64 bitlik tanıtıcıların (handle CDict, handle DDict in zstd.idl) arkasında yer alır. ctx.NewCDict(rawDict, level) aracılığıyla bir sözlüğü bir kez oluşturmak, C işçisi içindeki sözlük tablolarını ayrıştırır ve sözlük baytlarını yeniden göndermeden binlerce çağrıda yeniden kullanılabilen hafif bir tanıtıcı döndürür.

2. Toplu Çerçeveleme ile IPC Ek Yükünün Amortize Edilmesi (zstd_bulk.go)

Büyük yükler için (16 KiB ve üzeri), ~200 ns'lik paylaşımlı bellek gidiş-dönüş süresi, libzstd içinde harcanan CPU süresine kıyasla ihmal edilebilir düzeydedir. Ancak, aynı anda binlerce küçük 100 baytlık ila 1 KiB'lık mesajı sıkıştıran iş yükleri için, öğe başına 200 ns ödemek bile birikmektedir.

gosuda.org/zstd, öğe başına gidiş-dönüş sürelerini ortadan kaldırmak için BulkCtx (zstd_bulk.go) üzerinde CompressBulk ve DecompressBulk sağlar:

 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, tüm [][]byte toplu işini paylaşımlı bellekte tek bir uzunluk önekli istek çerçevesinde paketler. C işçisi C'de her bir öğe üzerinde döngü kurar (c/zstd_hooks.c içindeki compress_bulk), tüm sıkıştırılmış çerçeveleri yanıt yuvasına ardışık olarak yazar ve tüm toplu işi tek bir paylaşımlı bellek gidiş-dönüşünde döndürür.

3. Çağıranı Çökertmeden İşçi Sonlandırmasından Kurtulma

zstd_server ayrı bir işletim sistemi sürecinde çalıştığı için, C işçisi içindeki kritik bir hata asla Go uygulamanızı çöktürmez:

  • Eğer zstd_server dışarıdan sonlandırılırsa (örneğin SIGKILL veya bir OOM olayı nedeniyle), HQQ aktarım katmanı bozulan eşi (peer) anında tespit eder ve devam eden Compress veya Decompress çağrısından tiplendirilmiş bir *rt.TransportError döndürür.
  • Paket düzeyindeki fonksiyonlar (zstd.Compress, zstd.Decompress, zstd.NewWriter, zstd.NewReader), paylaşılan varsayılan işçinin kaybolduğunu otomatik olarak algılar, sonraki çağrıda yeni ve doğrulanmış bir zstd_server örneği başlatır ve normal çalışmaya devam eder.

Depo Düzeni

Kod tabanı, doğrudan kullanılabilen DataDog/zstd arayüzünü (facade), düşük seviyeli hqq-bindgen oturumu ve toplu ilkel bileşenlerinden ayırır:

1gosuda.org/zstd
2├── autolaunch.go / hashes.go  # Doğrudan kullanılabilen Compress/Decompress API'si, otomatik kurtarma havuzu ve SHA-256 özetleri
3├── zstd_stream.go             # Akış Tabanlı Yazar/Okuyucu (NewWriterLevelDict, NewReaderDict)
4├── zstd_ctx.go                # hqq-bindgen üzerinde sıfır tahsisli Ctx, CDict ve DDict sarmalayıcıları
5├── zstd_bulk.go               # Tek gidiş-dönüşlü CompressBulk ve DecompressBulk çerçevelemesi
6├── zstd.go                    # zstd IDL servisi için üretilmiş hqq-bindgen Go istemcisi
7├── zstd.idl                   # Context, CDict, DDict ve RPC yöntemlerini tanımlayan HQQ IDL şeması
8├── internal/embedded/         # Platform etiketli //go:embed zstd_server ikili dosyaları (6 İşletim Sistemi/Mimari hedefi)
9└── c/                         # C11 sunucu uygulaması (zstd_server.c, zstd_hooks.c, main.c)

Başlarken

gosuda.org/zstd kütüphanesini go get ile kurun ve CGO_ENABLED=0 ile kullanın:

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