Mikrosaniyelerin Peşinde: Lighter'ın Gecikme Mühendisliği

@Lighter_xyz
İNGILIZCE1 gün önce · 08 Tem 2026
190K
552
79
39
266

TL;DR

Yüksek performanslı bir Ethereum L2'yi ölçeklendirmek için GC ayarları, CPU yakınlığı ve bellek yönetimi konularını kapsayan, Lighter'ın milisaniyenin altındaki gecikme süresi yolculuğunun teknik bir analizi.

Giriş

Aylar süren iterasyonlar sonucunda, Lighter'ın uçtan uca p99 değeri 280 ms'den sabit 55 ms'ye düştü. İşlem işleme p99 değeri 20-30 ms'lik ani yükselmelerden 1 ms'nin altına indi. Sıcak yol uygulama süresi 100-250 µs seviyesinde. Tüm bunlar ana ağda, ölçekli olarak gerçekleşti.

Ne kadar ölçek? 5 Haziran 2026'da Lighter, tek bir günde 811 milyon işlemle tüm zamanların en yüksek seviyesine ulaştı, ortalama 9.388 TPS ve 20.740 TPS'lik zirvelerle.

Lighter'ın sıralayıcısı Go ile yazılmıştır. İşlemler, Lighter L2'de tam kriptografik kanıtlarla yürütülür ve L1'e yerleşir. Temel mimari içgörü, kanıtlamanın yürütmeden ayrıştırılmış olmasıdır: kanıt oluşturma asenkron olarak çalışır ve ticareti asla engellemez. Bu, yatırımcıların deneyimlediği gecikmenin tamamen Lighter'ın yürütme ve API katmanları tarafından belirlendiği anlamına gelir.

Lighter'ın API katmanı, yatırımcıların doğrudan etkileşime girdiği hizmettir. Borsa durumunun eksiksiz ve güncel bir görünümünü bellekte tutar ve bu yerel durumdan REST ve WebSocket bağlantılarına hizmet verir. Kritik bir goroutine, yürütme motorundan durum güncellemelerini alır ve bunları gerçek zamanlı olarak bellek içi önbelleklere uygular. Aşağı akıştaki her şey (WebSocket gönderimleri, API okumaları, işlem simülasyonları) bu önbelleklerden hizmet alır.

Kapsanan Temel Kavramlar

Aşağıdaki kavramlar, bu teknik yazının geri kalanında ele alınan temel konulara yüksek seviyeli bir genel bakış sağlar:

Ana ağdan önce bul: Sentetik hesaplar ve gerçekçi yük içeren, üretimden çok daha agresif enstrümante edilmiş özdeş bir yük testi ortamı, darboğazları yatırımcılar fark etmeden yakalar.

Çift ortam gözlemlenebilirliği: Yük testi, üretim için çok pahalı olan metrikleri taşır; ana ağ, tazelik, simülasyon gecikmesi ve uçtan uca işlem yaşam döngüsü için gerçek zamanlı olarak izlenir. Birlikte, tam kapsama sağlarlar.

Binlerce ayırma ile ölüm: Yığın ağırlıklı türlerin ve derin kopyaların, yığın tahsisli alternatifler ve değişmez anlık görüntülerle sistematik olarak değiştirilmesi. GC'li bir dilde, daha az ayırma = daha az duraklama = daha düşük kuyruk gecikmesi.

İşletim sistemi seviyesinde kontrol: Hayati iş parçacıklarını, gerçek zamanlıya yakın öncelikle özel CPU çekirdeklerine sabitleme.

Donanım bilinçli dağıtım: NUMA bilinçli yerleştirme, sıcak yolun hızlı, yerel bellek erişimine sahip olmasını sağlar. Gecikme yığınındaki son katman.

Kolaylık yerine ikili: Sıcak yolda elle yazılmış ikili serileştirme, yansıma ve ayırma yükünü ortadan kaldırır. Saniyede binlerce güncelleme boyunca kazanılan her mikrosaniye birleşir.

Darboğazları Ana Ağa Çıkmadan Bulma

Göremediğiniz bir şeyi düzeltemezsiniz. Herhangi bir şeyi optimize etmeden önce, zamanın tam olarak nerede harcandığını belirlemek için araçlara büyük yatırım yaptık.

Yük Testi Ortamı

Bir şeyi test etmek istediğimizde, özel bir yük testi ortamı oluştururuz: ana ağ altyapısının aynı konfigürasyonlarla ve aynı dağıtım topolojisi altında çalışan hizmetlerin birebir kopyası. Her zaman çalışmaz; belirli bir test için ihtiyaç duyulduğunda ayağa kaldırır ve sonrasında kapatırız.

Bu ortam hazır olduğunda, sentetik hesaplar oluşturur ve stres altındaki gerçek piyasa koşullarını simüle etmek için gerçekçi ticaret yükü üretiriz. Bu şunlara olanak tanır:

  • Ekstra ayrıntılı zamanlama: Sıcak yol içinde adım adım zamanlama, işlem başına önbellek oluşturma süreleri ve gönderimden onaya kadar her aşamayı izleyen işlem yaşam döngüsü zaman damgaları.
  • Talep üzerine profilleme ve uçuş kaydedici: Yük sırasında talep üzerine CPU, bellek ve yürütme izi profillerini yakalarız. Go'nun uçuş kaydedicisi bize her zaman açık iz toplama sağlar. Yavaş bir olay algılandığında, yürütmenin son birkaç saniyesi otomatik olarak yakalanır ve geçici gecikme artışlarını sonradan teşhis etmemizi sağlar.
  • Dağıtık izleme: Sıcak yoldaki her önemli fonksiyonu iz aralıklarıyla enstrümante ettik ve bu bize tek bir güncelleme döngüsü içinde, hizmet sınırları arasında zamanın tam olarak nerede harcandığına dair ayrıntılı görünürlük sağladı.

Çift Ortam İzleme

Her iki ortamı da (ana ağ ve yük testi) yakından izliyoruz, ancak farklı ayrıntı düzeylerinde.

Ana ağ gerçek zamanlı olarak sıkı bir şekilde izlenir. Ticaret deneyimi için önemli olan her şeyi takip ederiz:

  • Tazelik takibi: En aktif piyasalardaki order_book WebSocket kanalı üzerinden gecikmeyi ölçeriz. Temel olarak, bir emir defteri güncellemesinin üretilmesi ile bir istemcinin onu alması arasındaki kayma. Bu, yatırımcıların en doğrudan hissettiği metriktir: baktığınız emir defteri ne kadar güncel değil? Bunu iki noktada takip ederiz. En önemlisi yürütme motoru → istemci: yürütme motorunun bir durum değişikliğini işlemesinden, ortaya çıkan order_book güncellemesinin istemciye ulaştığı ana kadar olan tam boru hattı. Ayrıca API katmanı → istemciyi de ölçeriz: API katmanı, her giden güncellemeyi zaman damgalar ve istemci bunu kendi saatiyle karşılaştırarak bize son mil gecikmesini izole olarak verir.
  • Kuru çalıştırma gecikmesi: API katmanı, her işlemi yürütme motoruna iletmeden önce kuru çalıştırır (imzaları, nonce'ları ve bakiyeleri doğrulayarak). Her kuru çalıştırma zamanlanır ve raporlanır.
  • Uçtan uca işlem yaşam döngüsü: Gönderimden onaya kadar tam döngü gecikme histogramları.
  • Önbellek etkinliği: Bellekten ne sıklıkta hizmet verdiğimiz ve daha yavaş depolamaya geri döndüğümüz.

Yük testi daha da ayrıntılı olarak izlenir. Gerçek yatırımcılara hizmet vermediği için, ek yük konusunda endişelenmeden enstrümantasyonu artırabiliriz: sıcak yol içinde adım adım zamanlama, ayırma profillemesi, her önbellek mutasyonunda ek histogram kovaları. Bu daha ince ayrıntı, ana ağın üretim güvenli metriklerinin ortaya çıkarmayacağı mikro seviye gerilemeleri yakalar.

Ana ağ izleme bize sistemin gerçek yatırımcılar için şu anda nasıl performans gösterdiğini söyler. Yük testi izleme, gerilemeleri üretime ulaşmadan önce yakalar.

Derin Kopya Eliminasyonu ve Yığın Ayırma Savaşı

Go'nun çöp toplayıcısı bir gecikme vergisidir. Her yığın ayırması sonunda bir GC duraklaması haline gelir ve bir ticaret motorundaki GC duraklamaları, yatırımcılar için gecikme artışlarıdır. Bu nedenle sıcak yolu gözden geçirdik ve mümkün olan her yerde ayırmaları kestik. Sonuç, daha az gecikme artışı ve daha öngörülebilir performans oldu.

Derin Kopya Eliminasyonu

Derin kopyalar, ayırma fabrikalarıdır. Bunları birkaç şekilde ele aldık:

  • Değişmez anlık görüntüler: Emir defteri önbelleklerini değişmez hale getirdik. Okumalar, geçerli anlık görüntüye bir işaretçi döndürür, kopya gerekmez. Güncellemeler yeni bir sürüm oluşturur ve atomic.Pointer ile atomik olarak değiştirir.
  • Gereksiz kopyaları kaldırdık: Daha sonra hiçbir zaman mutasyona uğramayan verileri derin kopyalayan kod yollarını bulduk. Bunları tamamen kaldırdık.

Ayırma Bilinçli Tasarım

  • Yığın tahsisli sayısal değerler: Sıcak yolda yığın ağırlıklı big.Int ve big.Rat'ı yığın tahsisli alternatiflerle (int128, float64, int64 bölme) değiştirdik: fiyat dönüşümleri, emir defteri derinlik haritaları, boyut hesaplamaları. Anahtar biçimlendirme fonksiyonlarında 8,3 kata kadar daha hızlı, işlem başına sıfır yığın ayırması.
  • Koşullu güncellemeler: Hiçbir şey değişmediğinde ayırmayı atla.
  • Önceden boyutlandırılmış koleksiyonlar: Veri yapılarını önceden boyutlandırarak büyüme ve kopyalama döngülerini ortadan kaldırın.
  • Tampon yeniden kullanımı: Abone yolu, saniyede binlerce güncellemeyi seri durumdan çıkarır. Havuz tabanlı ara tampon yeniden kullanımıyla ayırmaları azalttık.

Bu, kuyruğu düzleştirdi. Daha önce, Lighter'ın uçtan uca p99'u (yürütme motorundan istemciye, order_book WebSocket kanalında ölçülen) ayırma yoğun dönemlerde 200-280 ms'ye fırlardı. Derin kopya eliminasyonu, değişmez önbellekler ve yığın ayırma çalışmasından sonra, p99, neredeyse hiç artış olmadan sabit ~50-60 ms bandına yerleşti:

Lighter - inline image

Uçtan Uca Gecikme: Yürütme Motoru -> İstemci (p99)

API katmanındaki GC baskısı da ölçülebilir şekilde düştü, ancak bu sadece tek bir değişiklik değildi. API sunucu önbelleklerinin nasıl çalıştığını temelden yeniden düzenledik. Daha önce, önbelleklerin TTL tabanlı süre sonu vardı ve her güncellemede yeni nesneler tahsis ediyordu. Bu kısa ömürlü tahsislerin her biri GC işi haline geliyordu. Yeniden düzenlemeden sonra, tam borsa durumu bir anlık görüntüden sıcak başlatılır ve atomik olarak değiştirilen uzun ömürlü, değişmez yapılar olarak bellekte tutulur. Bu, TTL değişimini ve güncelleme başına tahsisleri ortadan kaldırdı ve GC baskısı da onlarla birlikte düştü. Bellek kullanımını yakından izliyoruz ve çalışma seti sınırlı.

Eski tip API sunucusunu ve yeniden düzenlenmiş API sunucusunu ana ağ trafiğinde yan yana çalıştırdık. Yeniden düzenlenmiş sunucunun GC duraklama süresi (p75) ~3 ms civarındayken, eski sunucuda ~5-6 ms idi. Kabaca GC duraklama süresinin yarısı:

Lighter - inline image

GC Duraklama Süresi: Standart ve Anlık Görüntü API Sunucusu

Sıklık daha da net bir hikaye anlatıyor. Eski API sunucusu, yeniden düzenlenmiş olandan ~2,2 kat daha sık GC döngüsü tetikledi; bu, doğrudan daha az kısa ömürlü ayırma ve genel olarak daha az GC baskısı olduğunu yansıtıyor:

Lighter - inline image

GC Döngü Sıklığı: Standart API Sunucusu ve Anlık Görüntü API Sunucusu

Yürütme Motorunda GOGC Ayarı

Benzer GC düşüncesini yürütme motorunun kendisine de uyguladık. Go'nun GOGC parametresi, çöp toplayıcının ne kadar agresif çalıştığını kontrol eder. Varsayılan, CPU süresini bellek verimliliği için takas eder, ancak gecikme açısından kritik bir yol için bu takas yanlıştı.

GOGC ayarlandıktan sonra, yürütme motorunun GC süresi, ortalama ~30 µs'den ve 100 µs'ye varan artışlardan, istikrarlı bir ~10 µs bandına düştü. Neredeyse hiç artış olmadan ~3 kat azalma:

Lighter - inline image

GOGC Ayarından Sonra Yürütme Motoru GC Süresi

Üç aylık ana ağ verisi, iyileşmenin sürdüğünü doğruluyor: sonraki dönem düz ve öngörülebilir.

İşlem işleme de onunla birlikte iyileşti. Her işlem türünü (emir oluşturma, iptal etme, tasfiye etme, kaldıraç azaltma, transfer ve daha fazlası) kapsayan 90 günlük ana ağ verisinde, p99, GOGC ayarından önce sık sık 20-30 ms'lik artışlardan, sonra çoğunlukla 1 ms'nin altına, ara sıra 3-4 ms'lik aykırı değerlerle düştü. İyileşme iki aydan fazla süredir devam ediyor:

Lighter - inline image

İşlem İşleme Süreleri - p99 (Son 90 gün)

Anlık Görüntü Hizmeti — Daha Az Veri, Sıcak Dağıtımlar

Ayırma yükünü ortadan kaldırdıktan sonra, bir sonraki darboğazı ele aldık: dahili mesaj veriyolundan akan veri hacmi. Tam bellek içi durum anlık görüntülerini koruyan bir anlık görüntü hizmeti oluşturduk. Bunun iki büyük etkisi oldu:

  • Kabloda daha az veri. Anlık görüntü hizmeti tam durumu tuttuğu için, yürütme motorunun artık her güncellemede tam durumu mesaj veriyolu üzerinden göndermesi gerekmez. Daha az veri yazar, bu da daha az ağ bant genişliği tüketimi ve alıcı tarafta daha az seri durumdan çıkarma işi anlamına gelir.
  • Sıfır ısınma süreli dağıtımlar. Dağıtımda, API katmanı bir anlık görüntüden (hesaplar, emir defterleri, piyasa bilgileri, API genel anahtarları) başlar ve hemen hizmet vermeye hazır hale gelir. Önbellekler doldurulurken herhangi bir ısınma süresi yoktur. Anlık görüntüyü yükledikten sonra, API katmanı güncelleme akışına abone olur ve delta'ları gerçek zamanlı olarak uygular.

Anlık görüntü tabanının üzerinde, bellek içi önbellekler sürekli olarak güncellenir:

  • Hesap bilgisi: sync.Map aracılığıyla kilitsiz okumalar, güncellemeler için atomik işaretçi değişimleri.
  • Emir defterleri: Değişmez anlık görüntüler olarak saklanır. Okumalar bir işaretçi alır, güncellemeler yeni bir sürümü değiştirir. Okuma yolunda kilit yoktur.
  • API anahtarı önbelleği: Tüm anahtarlar belleğe sığar. Harici aramalar tamamen ortadan kaldırılmıştır.

Sonuç: tam borsa durumu yerel bellekte yaşar, gerçek zamanlı olarak güncellenir ve her dağıtım sıcak başlar.

Yürütme motoru üzerindeki dolaylı etki önemliydi. Daha önce, yürütme motoru ağ üzerinden önbellek anahtarları yazıyordu, hizmetlerin zaman zaman okuyacağı güncellemeler. Her şey bellekte yaşarken ve anlık görüntü hizmeti durum dağıtımını hallederken, bu ağ yazmaları gereksiz hale geldi. Onları sildik. Sonuç: blok süreleri p99, ~2,6 ms'den ~1,2-1,8 ms'ye düştü, çünkü yürütme motoru artık çok daha az yazıyor:

Lighter - inline image

Blok Süreleri - p99

CPU Planlaması

Kullanıcı alanındaki her şeyi optimize ettikten sonra, çekirdeğe uzandık.

Birçok goroutine'imiz var ve bunların çalışması için CPU'da zamanlanmaları gerekiyor. Düşük gecikmeli sistemler için zamanlama yükünü en aza indirmek önemlidir. Varsayılan olarak, Go'nun çalışma zamanı goroutine'leri işletim sistemi iş parçacıkları arasında çoğullar ve işletim sistemi, iş parçacıklarını CPU çekirdekleri arasında serbestçe taşıyabilir. Her ikisi de öngörülemeyen gecikme getirir.

Bunu dört mekanizmayı üst üste koyarak ortadan kaldırdık:

  1. `runtime.LockOSThread()`: Goroutine'i tek bir işletim sistemi iş parçacığına kilitler ve Go'nun zamanlayıcısının onu taşımasını engeller.
  2. `sched_setaffinity` ile CPU benzeşimi: Bu işletim sistemi iş parçacığını belirli bir CPU çekirdeğine (Linux) sabitler. Bu, çekirdeğin onu çekirdekler arasında taşımasını engelleyerek L1/L2 önbellek geçersiz kılmayı önler.
  3. SCHED_FIFO ile yüksek öncelikli zamanlama: İş parçacığının zamanlama önceliğini yükselterek çekirdeğin onu diğer işlere tercih etmesini sağlar.
  4. Meşgul bekleme döngüsü: Sıcak yol, boş bir default durumu olan bir select çalıştırır, böylece goroutine asla park etmez. Bu olmadan, Go kullanılabilir veri olmadığında goroutine'i "çalıştırılabilir" duruma taşır ve yeniden zamanlama, uyanma gecikmesi ekler. Bekleme döngüsü ile goroutine, sabitlenmiş çekirdeğinde çalışmaya devam eder ve sıfır zamanlama gecikmesiyle yeni güncellemeleri alır.

Etkiyi doğru ölçmek burada önemlidir. Uygulama süresi trafik koşullarına göre değişir, bu nedenle mutlak sayılar yüke göre kayar. Sabitlemenin etkisini izole etmek için, aynı trafik altında iki grup API sunucusunu yan yana çalıştırdık. Bir grup sabitlenmemiş olarak kontrol grubu olarak kaldı ve diğerini CPU sabitlemesine geçirdik. Aynı yük altında aynı anda ölçülen aralarındaki yüzde farkı, gerçek hikayeyi anlatır.

Sabitlemeden önce, her iki grup birlikte hareket eder. Aynı temel gecikme, aynı artış davranışı:

Lighter - inline image

Sıcak Yol Uygulama Süresi - CPU Planlamasından Önce

Bir grupta sabitleme etkinleştirildikten sonra, sürekli olarak sabitlenmemiş taban çizgisinin altında kalır. Aynı trafik, daha düşük gecikme. Sabitlenmiş grubun artışları da daha düşük sınırlanır, çünkü iş parçacığı geçiş titreşimi ve L1/L2 önbellek geçersiz kılma ortadan kaldırılmıştır:

Lighter - inline image

Sıcak Yol Uygulama Süresi - CPU Planlamasından Sonra

NUMA Bilinçli Dağıtım

Sabitlenmiş çekirdeğin bellek erişimleri NUMA sınırlarını aşarsa, CPU sabitlemesi tek başına yeterli değildir. Bir NUMA düğümü, kendi yerel belleğine sahip bir CPU grubudur. Uzak bir NUMA düğümünden belleğe erişmek, yerel erişime kıyasla 10 kat ceza taşır.

Lighter'ın API sunucuları başlangıçta 2 NUMA düğümlü daha büyük makinelerde çalışıyordu:

text
1$ lscpu | grep NUMA
2NUMA node(s): 2
3NUMA node0 CPU(s): 0-95
4NUMA node1 CPU(s): 96-191
5
6$ cat /sys/devices/system/node/node0/distance
710 100

Mesafe matrisi hikayeyi anlatıyor: yerel erişim 10'a mal olur, düğümler arası erişim 100'e mal olur. 10 kat ceza. Makineyi paylaşan N API sunucusuyla, bazılarının kaçınılmaz olarak sabitlenmiş CPU'su bir NUMA düğümünde ve çalışma belleği (bellek içi önbellekler, güncelleme tamponları) diğerindeydi. Her sıcak yol yinelemesi, düğümler arası vergiyi ödüyordu.

Düzeltme sezgisel değildi: API sunucularını tek NUMA düğümlü daha küçük makinelere taşıdık. Yarı özellik, ancak tüm bellek erişimleri artık garantili olarak yerel:

text
1$ lscpu | grep NUMA
2NUMA node(s): 1
3NUMA node0 CPU(s): 0-95

Bu, maliyetleri düşürdü ve aynı anda gecikmeyi iyileştirdi. Sıcak yol uygulama süresi, yoğun olmayan saatlerde ~100 µs'ye düşerek ~100-250 µs aralığına daha da düştü. Bunu, 2 NUMA düğümlü makinelerde yalnızca CPU sabitlemesiyle ~200-520 µs aralığıyla karşılaştırın:

Lighter - inline image

Sıcak Yol Uygulama Süresi - NUMA Optimizasyonu

Özel İkili Serileştirme

Lighter'ın yürütme motoru, durum güncellemelerini dahili bir mesaj veriyolu aracılığıyla API katmanına yayınlar. Her sistem güncellemesi bu yoldan akar. Orijinal serileştirme, genel amaçlı bir kodlama kitaplığı kullanıyordu, ancak yansıma, tür anahtarları ve alan başına ayırmalar, sıcak bir yolda gereksiz yük oluşturuyordu.

Bunu, elle yazılmış ikili serileştirme ile değiştirdik: sistemdeki her varlık türü için sabit düzen, sıfır yansıma kodlama/kod çözme. Her türün, alanları bilinen bayt ofsetlerinde okuyan ve yazan özel bir kodlayıcısı/kod çözücüsü vardır. Yansıma veya tür anahtarları yok ve minimum ayırma. Her codec, gerilemeleri yakalamak için gidiş-dönüş ve fuzz testlerine sahiptir.

Sonuç, aşağı akıştaki her önbelleği besleyen yolda önemli ölçüde daha az serileştirme yükü oldu.

Bağımsız kıyaslama, topluluk üyesi @UngusTrade tarafından oluşturulan ve sürdürülen ve sürekli işlem gecikmelerini karşılaştıran bir pano aracılığıyla mevcuttur: latency.perps.trading

Tek tıkla kaydet

YouMind ile viral makaleleri AI derin okumayla incele

Save the source, ask focused questions, summarize the argument, and turn a viral article into reusable notes in one AI workspace.

Explore YouMind
Üreticiler için

Markdown'ınızı temiz bir 𝕏 makalesine dönüştürün

Kendi uzun yazılarınızı yayımlarken görselleri, tabloları ve kod bloklarını 𝕏 için biçimlendirmek zahmetlidir. YouMind, eksiksiz bir Markdown taslağını temiz ve hemen paylaşılabilir bir 𝕏 makalesine dönüştürür.

Markdown'dan 𝕏'e deneyin

Çözülecek daha fazla kalıp

Son viral makaleler

Daha fazla viral makale keşfet