gosuda.org/zstd: Sıfır CGO ve Çökme İzolasyonu ile Drop-In DataDog/zstd Sıkıştırması
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:
- Derleme ve Dağıtım Sürtünmesi:
go-sqlite3gibi,DataDog/zstddeCGO_ENABLED=1ve hedef bir C derleyici araç zinciri gerektirir. Servisleri çapraz derlemek (cross-compile) veyalinux/arm64ya dawindows/arm64için statik ikili dosyalar (binary) oluşturmak harici C sistem kökleri (sysroots) gerektirir. - 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 Sistemi | Hedef Mimar (GOARCH) | Gömülü İkili Dosya | İkili Boyutu | Derleme Araç Zinciri & Köken (Provenance) |
|---|---|---|---|---|
| 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 |
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
- Üretilen
hqq-bindgenistemcisi,srcverisini doğrudan belleğe eşlenmiş istek yuvasına kopyalar veSeqLocksıra sözcüğünü (sequence word) değiştirir. - C işçisi, istek yuvasındaki
srcverisini yerinde (in place) okur ve yanıt yuvasının yük işaretçisini hedef arabellek (buffer) olarak doğrudanZSTD_compressCCtxveyaZSTD_decompressDCtxfonksiyonuna iletir. - Go istemcisi, elde edilen dilimi (slice) yanıt yuvasından çağrı yapan tarafından sağlanan
dstdilimine kopyalar. Kararlı durumda,ctx.CompressTo(dst, src, level)vectx.DecompressInto(dst, src)fonksiyonları0 allocs/opgerç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_serverdışarıdan sonlandırılırsa (örneğinSIGKILLveya bir OOM olayı nedeniyle), HQQ aktarım katmanı bozulan eşi (peer) anında tespit eder ve devam edenCompressveyaDecompressçağrısından tiplendirilmiş bir*rt.TransportErrordö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ış birzstd_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
- GitHub Deposu: https://github.com/gosuda/hqq-zstd
- Mimari ve Performans Testleri: docs/ARCHITECTURE.md
- HQQ Çekirdek Çalışma Zamanı: https://gosuda.org/hqq