Oluwa.io
Tüm yazılar
·6 dk okumamühendislikkriptogüvenilirlikalım satım

Güvenilir Kripto Emir Yürütmesini Hayata Geçirmek

Kripto emir yürütmesinde güvenilirlik; durum makineleri, idempotency anahtarları ve amansız mutabakattan gelir. MadaiOps'u inşa ederken öğrendiklerimiz.

Yazan Vitor Lima

Çoğu kripto alım satım ürünü hız ve bir grafik satar. Zor kısım mumları çizmek değildir. Zor olan, herhangi bir milisaniyede aldatıcı derecede basit bir soruyu yanıtlayabilmektir: şu anda emrimin gerçek durumu nedir? Dahili emir ve alım satım operasyonları uygulamamız olan MadaiOps'u inşa etmeye başladığımızda, her şeyi bu soru düzenledi. Hız bir özelliktir. Gerçek ise üründür. Kripto emir yürütmesinde güvenilirlik, borsa bozulurken bile uygulamanın bu soruyu dürüstçe yanıtlayabildiğinde sahip olduğunuz şeydir; ve gerçekleşmemiş bir dolumu gösteren -ya da gerçekleşeni gizleyen- bir uygulama, hiçbir gecikme (latency) kazanımının geri satın alamayacağı bir şekilde güveni yok eder.

Bu yazı, bir emir uygulamasını güvenilir kılan mühendislik hakkında: durum makinesi, idempotency, WebSocket üzerinden mutabakat ve yalnızca yük altında ortaya çıkan hata modları. Bunların hepsi konusunda net görüşlere sahibiz, çünkü sıkıcı tercihler sizi ödeme gücü sahibi tutan tercihlerdir.

Emri açık bir durum makinesi olarak modelleyin

Verdiğimiz en iyi karar, bir emri UPDATE ettiğiniz bir satır olarak görmeyi bırakmak ve onu adlandırılmış, numaralandırılmış geçişlere sahip sonlu bir durum makinesi olarak görmeye başlamaktı. Bir emir; PENDING_NEW, WORKING, PARTIALLY_FILLED, FILLED, CANCEL_REQUESTED, CANCELED, REJECTED ve EXPIRED gibi durumlarda yaşar. Her geçiş, mevcut durum artı gelen bir olayın bir fonksiyonudur ve geçersiz geçişler sessizce yutulmak yerine yüksek sesle reddedilir.

Getirisi: belirsizlik bir gecenin ikisinde çıkan bir olay yerine, doğrulama zamanında ele alınan bir mesele haline gelir. FILLED bir emir asla WORKING durumuna geri dönemez. CANCELED bir emir için gelen bir dolum olayı "imkânsız" değildir -bu gerçek bir yarış durumudur (race)- ve durum makinesi bunun ne anlama geldiğine karar vermenizi zorunlu kılar. Genellikle bu, iptalin yarışı kaybettiği, emrin aslında dolduğu ve sizin yerel CANCELED durumunuzun yanlış olduğu anlamına gelir. Emirleri değiştirilebilir satırlar olarak modelleyin ve o olay bir şeyin üzerine yazsın; mutabakat -ya da bir kullanıcı- yakalayana kadar bunu asla fark etmezsiniz.

Çıkarım

Tek bir API çağrısı yazmadan önce durumlarınızı numaralandırın. Diyagramı bir beyaz tahtaya çizemiyorsanız, alanınızı henüz anlamamışsınızdır ve borsa size bunu zor yoldan öğretecektir. Yalnızca mevcut durumu değil, geçişleri saklayın. Ne olduğuna dair yalnızca ekleme yapılan (append-only) bir olay günlüğü, tek bir değiştirilebilir durum sütununu yener; çünkü günlük yeniden oynatılabilir, sütun ise bir tahmindir.

Idempotency isteğe bağlı değildir

Ağlar yeniden dener. Kendi yeniden deneme mantığınız yeniden dener. Bir kullanıcı "Al"a iki kez dokunur. Bunların herhangi biri aynı emri iki kez gönderebilir ve bir borsada mükerrer bir emir gerçek paradır. Savunma, kullanıcı işlemi onayladığı anda, istek cihazdan veya sunucudan çıkmadan önce her emre iliştirilen istemci tarafından üretilmiş bir idempotency anahtarıdır -çoğu borsa buna clientOrderId ya da newClientOrderId der.

Uyguladığımız kural: anahtar bir kez üretilir, ağ çağrısından önce yerel olarak kalıcılaştırılır ve her yeniden denemede olduğu gibi yeniden kullanılır. İlk deneme yanıt alınmadan zaman aşımına uğradıysa, yeniden deneme aynı anahtarı taşır ve borsa ya emri bir kez oluşturur ya da mevcut olanı döndürür. Her iki durumda da tek bir emirde birleşirsiniz. Anahtarı bir başarısızlıktan sonra üretirseniz, mükerrer emir üreten bir makine inşa etmişsinizdir.

Bu, iptallere ve değişikliklere de uzanır. Bir iptal isteği de idempotent olmalıdır: zaten iptal edilmiş bir emri iptal etmek, başka bir yeniden deneme döngüsünü tetikleyen bir hata değil, başarı döndüren bir işlem yapmama (no-op) durumudur. "İstenen son durum zaten doğru" olanı her yerde başarı olarak ele alırız. Kulağa önemsiz geliyor. Bütün bir yeniden deneme fırtınası (retry storm) kategorisini ortadan kaldırır.

Çıkarım

Idempotency anahtarını başarılı olduktan sonra değil, harekete geçmeden önce kalıcılaştırın. "Gönderdim" ile "işe yaradığını biliyorum" arasındaki boşluk, paranın tam olarak kaybolduğu yerdir ve anahtar, o boşluğu kapatan tek şeydir.

Amansızca mutabakat kurun: WebSocket bir ipucudur, gerçek değil

Borsalar emir güncellemelerini WebSocket üzerinden gönderir ve çalıştıklarında harikadırlar: saniyenin altında dolumlar, canlı kısmi dolum miktarları, iptal onayları. Ama bir WebSocket, kaçırabileceğiniz bir akıştır. Bağlantınız kesilecek. Mesajlar sırasız gelir, yeniden bağlanma sırasında düşer ya da borsa bozuk olduğu için hiç gelmez. Emir durumunuz yalnızca soketin size en son söylediği şeyse, soket takıldığı anda uygulamanız kendinden emin bir şekilde yanılıyordur.

Bu yüzden iki döngü çalıştırıyoruz. Hızlı döngü WebSocket'i tüketir ve olayları iyimser bir şekilde uygular. Yavaş döngü REST API'sini -GET /order ve açık emir uç noktalarını- belirli aralıklarla ve her yeniden bağlanmadan sonra sorgular, ardından o anlık görüntüyü yerel durumla mutabık kılar. REST gerçeğin kaynağıdır; soket ise düşük gecikmeli bir ipucudur. Uyuşmadıklarında REST kazanır ve biz sapmayı günlüğe kaydederiz; çünkü bir sapma, sistemin sizin için yazdığı bir hata raporudur.

Sıralama önemlidir. Çoğu borsa güncellemeleri monoton artan bir sıra numarasıyla ya da bir updateTime ile damgalar. O emir için son uygulanandan daha eski herhangi bir olayı düşürürüz; bu da sırasız teslimatı zararsız hâle getirir. Yeniden bağlanmada doğru hamle şudur: yeniden abone ol, boşluğu doldurmak için hemen tam bir REST anlık görüntüsü çek, ardından canlı akışa yeniden güven. Anlık görüntüyü atlayın ve çevrimdışı olduğunuz iki saniye boyunca gerçekleşen dolumu kaçırırsınız.

Çıkarım

Gerçek zamanlı bir beslemenin durumunuzun tek yazarı olmasına asla izin vermeyin. Soket sizi hızlı yapar; mutabakat döngüsü sizi doğru yapar. İkisine de ihtiyacınız var ve çatıştıklarında doğruluk, hızı her seferinde yener.

Kısmi dolumlar ve bağlantı kopmaları: sizi ifşa eden iki şey

Kısmi dolumlar, naif uygulamaların sessizce yalan söylediği yerdir. 1.0 BTC'lik, önce 0.3, sonra 0.4 dolan, ardından takılan bir emir ne "açık"tır ne de "tamamlanmış". Bu, 0.7'si yürütülmüş ve 0.3'ü kalmış PARTIALLY_FILLED durumundaki bir emirdir ve bu sayıların her biri tek bir durum alanından çıkarım yapılarak değil, tek tek dolum olaylarından izlenmelidir. Yürütülen miktarı ve hacim ağırlıklı ortalama fiyatı ayrı ayrı dolumlardan biriktiririz, böylece pozisyon her zaman ilkellerden (primitives) yeniden inşa edilebilir. "Doldu" gören bir kullanıcının, ne kadar, hangi ortalama fiyattan dolduğunu bilmesi gerekir. Bundan azı yarım bir gerçektir.

Bağlantı kopmaları bunu katmerleştirir. Dolum sırasında çevrimdışı düşün ve WebSocket size 0.4'ten hiç bahsetmesin; işte o zaman yerel durumunuz bayattır. Yeniden-bağlan-sonra-anlık-görüntü-al disiplininin var olma nedeni tam olarak budur: REST anlık görüntüsü kümülatif yürütülen miktarı döndürür ve mutabakat döngüsü, kullanıcı asla yanlış bir sayı görmeden boşluğu kapatır. Karşısında tasarım yaptığımız hata modu "uygulama çöktü" değildir -çökmeler dürüsttür. O mod, "uygulama çalışmaya devam etti ve yanlış bir şey gösterdi" modudur.

Çıkarım

Kümülatif yürütülen miktarı birincil gerçek olarak izleyin ve diğer her şeyi ondan türetin. "Kalan"ı değiştirdiğiniz bağımsız bir alan olarak asla saklamayın. Onu türetin (ordered − executed) ki gerçeklikle senkronizasyondan çıkıp kayamasın.

Baskı altında dürüst olmak, zekilikten iyidir

Zeki olmaya dair kalıcı bir cazibe vardır: onaydan önce dolumları tahmin etmek, "geçici" hataları gizlemek, arayüz asla titremeyecek şekilde bir yeniden bağlanmanın üzerini örtmek. Bunların çoğuna direnmeyi öğrendik. Bir alım satım operatörü pürüzsüz bir yalan istemez; bağlantının az önce koptuğunu ve durumun mutabık kılınmakta olduğunu dürüstçe bilmek ister. Görünür bir "yeniden bağlanılıyor, emirler doğrulanıyor" durumu, dünün gerçeğini gösterdiği ortaya çıkan kusursuz bir arayüzden daha değerlidir.

Somut olarak, belirsizliğin üzerini örtmek yerine onu görünür kılarız. Yerel durum ile borsa yakın zamanda mutabık kılınmadığında, uygulama bunu söyler. Bir emir CANCEL_REQUESTED durumundayken ama henüz iptali onaylanmamışken, erken bir CANCELED değil, tam olarak bunu gösterir. İyimser arayüz (optimistic UI) bir yapılacaklar listesi uygulaması için gayet iyidir. Para söz konusu olduğunda, iyimserlik destek talepleri ve kaybolan güvenle bedelini ödediğiniz bir yükümlülüktür.

Çıkarım

Şüphedeyken, şüpheyi gösterin. Finansal bir uygulamanın baskı altında yapabileceği en güven verici şey, tahmin yürütmeyi reddetmesidir.

Kapanış düşüncesi

Bunların hiçbiri gösterişli değil. "Idempotency anahtarlarımız ağ çağrısından önce kalıcılaştırılıyor" ifadesinin alkış aldığı bir demo yoktur. Ama güvenilirlik sonradan eklediğiniz bir özellik değildir. O, erkenden verdiğiniz ve taviz vermeyi reddettiğiniz, ardından bir kestirme yol cazip göründüğünde her seferinde savunduğunuz kararlar bütünüdür. Her şey yanarken gerçeği söyleyen sistemi inşa edin, hızlı ve güzel kısımlar kendi işlerini kendileri halleder.