← Makalelere geri dön
March 27, 2026
5 dakikalık okuma

ZigBolt: Zig ile Kendi Aeron'umuzu Neden Yaptık ve Mesaj Başına 20 Nanosaniyeye Nasıl Ulaştık

#zigbolt
#zig
#hft
#düşük-gecikme
#mesajlaşma
#aeron
#ipc
#açık-kaynak

ZigBolt — ultra düşük gecikmeli mesajlaşma Kilit gerektirmeyen ring buffer'lar, sıfır kopyalı codec'ler, Raft kümesi — hepsi saf Zig ile, hepsi açık kaynak.

Algoritmik ticaret veya piyasa yapıcılığı alanında çalışıyorsanız, her mikrosaniyenin bedelini bilirsiniz. Bir ekstra bağlam geçişi — ve emriniz ikinci sıraya düşer. Bir JVM GC duraklaması — karşı taraftaki piyasa yapıcı kotayı çoktan güncellemiştir. Paranın nanosaniyelerle ölçüldüğü bir dünyada, mesajlaşma altyapısı servisler arasındaki sıradan bir boru değil — rekabetçi bir avantajdır.

Zig ile yüksek frekanslı ticaret için yazılmış bir mesajlaşma sistemi olan ZigBolt'u geliştirdik. Sıfırdan. JVM yok, garbage collector yok, Media Driver yok, XML yapılandırması yok. Ve SPSC ring buffer'da p50 gecikme olarak 20 nanosaniye, paylaşımlı bellek üzerinden IPC'de ise 30 nanosaniye elde ettik.

Bu makale, neden ihtiyaç duyduğumuzu, içeride nasıl çalıştığını ve neden Zig seçtiğimizi ele alıyor.


TL;DR

  • ZigBolt — saf Zig ile yazılmış HFT için açık kaynaklı (MIT) mesajlaşma sistemi
  • SPSC'de p50 20 ns, IPC'de p50 30 ns — Aeron'un yayımlanmış rakamlarından daha hızlı
  • Sıfır kopyalı codec 0 ns'de çalışır (derleme zamanı üretimi, çalışma zamanı sadece bir işaretçi dönüşümüdür)
  • GC yok, JVM yok, Media Driver yok — kütüphane doğrudan uygulamanıza gömülür
  • Raft kümesi, arşiv, sıralayıcı — hepsi dahil
  • Rust, Python, Go, TypeScript, C için FFI bağlantıları — istediğiniz dilde çalışabilirsiniz

Sorun: Aeron Neden Harika Ama Yeterli Değil?

Real Logic'in Aeron'u, sermaye piyasalarında düşük gecikmeli mesajlaşma için fiili standarttır. Düzinelerce HFT firması tarafından kullanılır, savaşta test edilmiştir ve mükemmel bir mimariye sahiptir. Ancak Aeron'un temel bir sorunu var: adı JVM.

JVM Güvenli Noktaları: Görünmez Düşman

Tüm verileri dikkatlice off-heap belleğe koymuş olsanız bile, GC ergonomiklerini devre dışı bırakıp GuaranteedSafepointInterval=300000 ayarlasanız bile — JVM zaman zaman tüm iş parçacıklarını bir güvenli noktada durdurmaya devam eder. Bu bir hata değil, mimari bir karardır: JVM, deoptimizasyon, önyargılı kilitleme ve yığın yürümesi için güvenli noktalara ihtiyaç duyar.

Pratikte şöyle görünür: iş parçacığınız p50 = 200 ns'de mesaj gönderir, ardından birdenbire p99.9 değeri 50 us'a fırlar. Görünür bir neden olmaksızın. Çünkü JVM iş parçacıklarından biri bunun zamanı olduğuna karar vermiştir.

Media Driver: Ekstra Bir Atlama

Aeron, bir Media Driver aracılığıyla çalışır — yayıncı ile abone arasındaki mesajları paylaşımlı bellek üzerinden yönlendiren ayrı bir süreç (veya gömülü JVM). Bu güzel bir yalıtım sağlar, ancak en az bir ekstra atlama ekler:

Aeron:    Uygulama → shm → Media Driver → shm → soket → NIC
ZigBolt:  Uygulama → ring buffer → io_uring → NIC

Her atlama, ekstra nanosaniyeler, ekstra önbellek kayıpları, ekstra öngörülemezlik anlamına gelir.

SBE: Ayrı Bir Derleme Adımı

Simple Binary Encoding — finansal mesajlar için standart FIX codec'i. Aeron ekosisteminde, XML şemalarından kod üreten ayrı bir Java yardımcı programıdır. Ayrı bir bağımlılık, ayrı bir derleme adımı, ayrı bir sorun seti.


Çözüm: ZigBolt

Kendimize şunu sorduk: Aeron'un en iyi fikirlerini — üçlü tamponlu log, kilit gerektirmeyen ring buffer'lar, Raft kümesi — alıp şu özelliklere sahip bir dilde uygulasaydık ne olurdu:

  1. Çalışma zamanı yükü yok (GC yok, güvenli noktalar yok)
  2. Derleme zamanında kod üretimi (comptime)
  3. C kütüphaneleriyle (DPDK, io_uring) önemsiz entegrasyon
  4. ~100 KB ikili dosyaya derlenebilir

Bu dil Zig.


Mimari

┌─────────────────────────────────────────────────────────┐
│  Yayıncı/Abone API'si (tiplenmiş genel sarmalayıcılar)   │
├─────────────────────────────────────────────────────────┤
│  Taşıma Katmanı (kanal fabrikası ve yaşam döngüsü)       │
├─────────────────────────────────────────────────────────┤
│  IPC Kanalı (paylaşımlı bellek)  │ UDP Kanalı (ağ)       │
├─────────────────────────────────────────────────────────┤
│  WireCodec (comptime, sıfır kopyalı) │ SBE Encoder/Decoder│
├─────────────────────────────────────────────────────────┤
│  Ring Buffer'lar (SPSC/MPSC) │ LogBuffer (üçlü tamponlu) │
├─────────────────────────────────────────────────────────┤
│  Arşiv (yeniden oynatma) │ Sıralayıcı (toplam sıra) │ Raft│
└─────────────────────────────────────────────────────────┘

Yedi katman, her biri bağımsız olarak kullanılabilir. İki süreç arasında IPC için sadece bir SPSC ring buffer mu gerekiyor? Alın. Raft mutabakatı ve arşivleme içeren tam bir kümeye mi ihtiyacınız var? O da var.


Kıyaslamalar: Söz Değil, Sayılar

ZigBolt Kıyaslama Sonuçları

10 milyon iterasyon üzerinde gerçek kıyaslama sonuçları (Apple Silicon / macOS):

SPSC Ring Buffer

Mesaj Boyutu p50 p99 p99.9 Throughput
8 bayt 20 ns 30 ns 120 ns 42,8M msg/s
32 bayt 30 ns 50 ns 150 ns 28,5M msg/s
64 bayt 50 ns 60 ns 320 ns 17,6M msg/s
256 bayt 30 ns 50 ns 50 ns 29,5M msg/s

IPC Kanalı (paylaşımlı bellek)

Mesaj Boyutu p50 p99 p99.9 Throughput
64 bayt 30 ns 40 ns 40 ns 35,7M msg/s
256 bayt 40 ns 40 ns 170 ns 27,4M msg/s
1024 bayt 90 ns 260 ns 900 ns 9,9M msg/s

LogBuffer (Aeron tarzı üçlü tamponlu)

Mesaj Boyutu p50 p99 p99.9 Throughput
32 bayt 30 ns 40 ns 320 ns 33,6M msg/s
64 bayt 30 ns 30 ns 160 ns 38,0M msg/s
256 bayt 30 ns 40 ns 60 ns 31,1M msg/s

WireCodec (comptime sıfır kopyalı)

İşlem Gecikme Throughput
Kodlama (32 bayt) 0 ns satır içi memcpy
Kod çözme (32 bayt) ~0,4 ns 2,7 milyar msg/s

Evet, doğru okudunuz: kodlama sıfır nanosaniye alır. Çünkü WireCodec(T), struct'ı derleme zamanında doğrular ve encode/decode işlemlerini düz bir @memcpy veya işaretçi dönüşümüne çevirir. Çalışma zamanı yükü = sıfır.

Karşılaştırma olarak: Aeron, ~250 ns'lik IPC RTT (gidiş-dönüş) iddia etmektedir. Bizim tek yönlü gecikmemiz 30 ns. Gidiş-dönüşü bile hesaba katsak 4 kat daha hızlıyız.


İçeride Nasıl Çalışır?

Kilit Gerektirmeyen SPSC: Erdem Olarak Basitlik

SPSC Ring Buffer

Tek üretici tek tüketici ring buffer'ı, en basit ve en hızlı kilit gerektirmeyen veri yapısıdır. Yazıcı head'i hareket ettirir, okuyucu tail'i hareket ettirir, CAS gerekmez — acquire/release atomic'ler yeterlidir.

Temel numara, önbellek satırı dolgulamasıdır. head ve tail aynı önbellek satırında oturuyorsa, bir sayaçtaki her güncelleme diğer çekirdek için önbelleği geçersiz kılar (yanlış paylaşım). Çözüm:

// Head (yazma konumu) — kendi önbellek satırında
head: std.atomic.Value(usize) align(128) = .init(0),

// 128 bayt dolgu — garantili yalıtım
_pad0: [128 - @sizeOf(std.atomic.Value(usize))]u8 = .{0} ** ...,

// Tail (okuma konumu) — kendi önbellek satırında
tail: std.atomic.Value(usize) align(128) = .init(0),

64 değil 128 bayt — çünkü Apple Silicon'da (ve birçok ARM çipinde) donanım ön getirici önbellek satırı çiftleriyle çalışabilir. Güvende olmak için bu şekilde yapıyoruz.

WireCodec: Kod Üretimi Yerine Comptime

WireCodec — derleme zamanı codec'i

Java/C++ dünyasında, ikili codec'ler ayrı bir adım gerektirir: şema yazın, kod üreticisini çalıştırın, kodu alın, derleyin. Zig'de tüm bunlar derleme zamanında gerçekleşir:

const TickMsg = packed struct {
    symbol_id: u32,
    price: i64,
    quantity: u32,
    side: u8,
    _reserved: [3]u8,
    timestamp: u64,
};

const Codec = WireCodec(TickMsg);

// Kodlama — sadece 32 baytlık bir memcpy. 1-2 talimata satır içi eklenir.
Codec.encode(&msg, buf[0..Codec.wire_size]);

// Kod çözme — işaretçi dönüşümü. Sıfır kopya.
const tick = Codec.decode(buf[0..Codec.wire_size]);

Zig derleyicisi comptime'da doğrular:

  • Struct paketlenmiştir (dolgu delikleri yok)
  • Boyut 8 baytın katıdır (SIMD için hizalama)
  • Tüm alanlar ilkel türlerdir

Bir şey yanlışsa — sabah 3'te üretimde bir çalışma zamanı istisnası değil, derleme hatası alırsınız.

Paylaşımlı Bellek Üzerinden IPC

İki süreç /dev/shm'deki aynı dosyayı eşler. Yayıncı ring buffer'a yazar, abone okur. Kritik yolda soket yok, sistem çağrısı yok:

// Yayıncı
const channel = try IpcChannel.create("/market-data", .{
    .term_length = 1024 * 1024, // 1 MB
});
channel.publish(&msg_bytes, msg_type_id);

// Abone (başka bir süreç)
const channel = try IpcChannel.open("/market-data", .{
    .term_length = 1024 * 1024,
});
const count = channel.poll(handler_fn, 10);

publish()'dan abonedeki handler_fn çağrısına kadar tüm yol — 64 baytlık bir mesaj için 30 nanosaniye.

UDP için NAK Tabanlı Güvenilirlik

Ağ taşıması için ZigBolt, alıcı güdümlü yeniden iletim kullanır. Alıcı, bitmap aracılığıyla dizi numaralarındaki boşlukları takip eder ve gönderene NAK (olumsuz onay) gönderir. Ayrıca AIMD tıkanıklık kontrolü — TCP benzeri yavaş başlangıç ve tıkanıklıktan kaçınma — ağı doldurmamak için.

Raft Kümesi: Tutarlılığa İhtiyaç Duyduğunuzda

Bir mesajın kaybolmasının kabul edilemez olduğu durumlarda (örneğin bir eşleştirme motoru), ZigBolt tam Raft mutabakatını içerir:

  • Yapılandırılabilir zaman aşımı (150-300 ms) ile lider seçimi
  • Log replikasyonu — lider her mesajı takipçilere çoğaltır
  • CRC32 doğrulaması ve çökme kurtarma ile önceden yazma logu
  • WAL'ın sonsuza kadar büyümemesi için anlık görüntüler

Arşiv: Kaydet ve Yeniden Oynat

Tüm mesajlar, segmentlenmiş bir disk arşivine kaydedilebilir. Ardından — zaman veya dizi numarasına göre herhangi bir konumdan yeniden oynatma. Harici bağımlılıklar olmadan yerleşik LZ4 tarzı sıkıştırma. Segmentler içinde hızlı arama için seyrek dizin.

Toplam Sıralı Sıralayıcı

Birden fazla mekan üzerinde piyasa yapıcılığı için, tüm olayların küresel bir sıraya sahip olması kritik öneme sahiptir. Sıralayıcı N giriş akışını alır ve bunları tek bir akışa birleştirerek monoton artan dizi numaraları atar. Her katılımcı aynı olay dizisini görür.


Neden Zig, Rust/C/C++ Değil?

Dört aday arasında seçim yaptık. İşte dürüst bir karşılaştırma:

Kriter Zig C/C++ Rust Java (Aeron)
GC / çalışma zamanı yükü Yok Yok Yok JVM güvenli noktaları, GC
Comptime kod üretimi Yerel Makrolar/şablonlar proc makroları Yok
C birlikte çalışma (DPDK, io_uring) Önemsiz @cImport Yerel FFI/bindgen JNI yükü
SIMD @Vector, yerleşik İç birimler packed_simd (kararsız) Vektörizasyon ipuçları
Çapraz derleme Yerleşik CMake cehennemi cargo target Yok
Derleme süresi Saniyeler Dakikalar (C++) Dakikalar Saniyeler + JVM başlangıcı
Gizli kontrol akışı Yok İstisnalar, örtük dönüşümler unwrap'teki panikler İstisnalar

Zig bize benzersiz bir kombinasyon sağladı: C düzeyinde performans + geliştirme sırasında güvenlik + comptime metaprogramlama (codec'ler, arama tabloları, protokol durum makineleri — hepsi derleme zamanında üretilir) + @cImport aracılığıyla DPDK, liburing, ef_vi ile önemsiz entegrasyon.

Ve bir Zig ikili dosyası ~100 KB ağırlığındadır. JVM tabanlı bir çözümün 20+ MB'ına karşılık.


Bağlantılar: Kendi Dilinizde Çalışın

ZigBolt, C-ABI ile paylaşımlı bir kütüphaneye derlenir ve beş dil için hazır bağlantılarımız var:

TypeScript / Node.js

import { IpcChannel } from "@zigbolt/node";

const channel = IpcChannel.create({
  name: "/my-market-data",
  termLength: 1024 * 1024,
});

const msg = Buffer.from("BTC/USDT 42000.50", "utf-8");
channel.publish(msg, 1);

Rust

use zigbolt::IpcChannel;

let ch = IpcChannel::create("/my-channel", 64 * 1024).unwrap();
ch.publish(b"hello", 1).unwrap();

let sub = IpcChannel::open("/my-channel", 64 * 1024).unwrap();
sub.poll(|data, msg_type_id| {
    println!("got {} bytes, type={}", data.len(), msg_type_id);
}, 10);

Python

from zigbolt import IpcChannel

ch = IpcChannel.create("/market-data", term_length=1024*1024)
ch.publish(b"tick data here", msg_type_id=1)

Ayrıca Go ve sade C. Aynı paylaşımlı bellek kanalı tüm dillerden aynı anda erişilebilir — Zig'de yayıncı, Python'da abone, Go'da izleme. Hepsi aynı mmap bölgesini okur.


SBE Codec: FIX Uyumlu Mesajlar

Finansal protokoller için ZigBolt, derleme zamanı şemaları içeren tam bir SBE (Simple Binary Encoding) codec'i içerir. Yerleşik mesaj türleri:

  • NewOrderSingle — emir gönderimi
  • ExecutionReport — çalıştırma raporu
  • MarketDataIncrementalRefresh — artımlı piyasa verisi güncellemesi
  • MassQuote — toplu kotasyon
  • Heartbeat — bağlantı kontrolü
  • Logon — kimlik doğrulama

Harici kod üreticisi yok, XML yok. Her şey Zig struct'larıyla tanımlanır ve derleme zamanında doğrulanır.


Tel Protokol: Aeron Uyumluluğu

ZigBolt, Aeron uyumlu tel protokol flyweight'lerini uygular:

  • DataHeaderFlyweight — veri çerçeveleri
  • StatusMessage — akış kontrolü
  • NAK — olumsuz onay
  • Setup, RTT, Error — servis çerçeveleri

Bu, ZigBolt'un mevcut Aeron altyapısıyla bir arada var olabileceği anlamına gelir. Geçiş, büyük bir patlama olmak zorunda değildir.


Sırada Ne Var?

ZigBolt şu anda 0.2.1 sürümündedir. Çekirdek kararlıdır, kıyaslamalar yeniden üretilebilir, bağlantılar çalışır. Yakında:

  • io_uring backend — Linux 6.0+'da sıfır kopyalı ağ taşıması (IORING_OP_SEND_ZC)
  • DPDK / AF_XDP — her mikrosaniyenin önemli olduğu durumlarda çekirdek atlama
  • Multi-Raft — enstrüman/strateji bazında parçalama
  • Sütunsal arşiv — analitik için Apache Arrow/Parquet entegrasyonu
  • Büyük sayfa desteği — TLB kayıplarını en aza indirmek için önceden hata yapılmış 2MB/1GB büyük sayfalar

Deneyin

Kaynaktan derleyin (zig build), kıyaslamaları çalıştırın (zig build bench), herhangi bir dilden FFI aracılığıyla bağlanın. Zig 0.15.1 ve birkaç dakikanız varsa — ping-pong kıyaslamasını deneyin ve mevcut çözümünüzle karşılaştırın.


Bağlantılar:


Atıf

@software{soloviov2026zigbolt,
  author = {Soloviov, Eugen},
  title = {ZigBolt: Why We Built Our Own Aeron in Zig and Hit 20 Nanoseconds Per Message},
  year = {2026},
  url = {https://marketmaker.cc/tr/blog/post/zigbolt-zig-messaging-hft},
  version = {0.2.1},
  description = {Zig ile sıfırdan HFT için ultra düşük gecikmeli bir mesajlaşma sistemi nasıl ve neden geliştirdik. JVM yok, GC yok, sürpriz yok.}
}
Sorumluluk Reddi: Bu makalede sağlanan bilgiler yalnızca eğitim ve bilgilendirme amaçlıdır ve finansal, yatırım veya ticaret tavsiyesi niteliği taşımaz. Kripto para ticareti önemli bir kayıp riski içerir.

Yazarlar

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Piyasanın Önünde Olun

Özel yapay zeka ticaret içgörüleri, piyasa analizi ve platform güncellemeleri için bültenimize abone olun.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.