Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Ovo je nastavak duge priče o našem trnovitom putu do stvaranja moćnog, opterećenog sustava koji osigurava rad Burze. Prvi dio je ovdje: habr.com/en/post/444300

Tajanstvena greška

Nakon brojnih testiranja ažurirani sustav trgovanja i kliringa pušten je u rad, a naišli smo na bug o kojem bismo mogli napisati detektivsko-mističnu priču.

Ubrzo nakon pokretanja na glavnom poslužitelju, jedna od transakcija je obrađena s pogreškom. Međutim, na backup serveru je sve bilo u redu. Ispostavilo se da je jednostavna matematička operacija izračunavanja eksponenta na glavnom poslužitelju dala negativan rezultat iz stvarnog argumenta! Nastavili smo istraživanje, te smo u SSE2 registru pronašli razliku u jednom bitu, koji je odgovoran za zaokruživanje pri radu s brojevima s pomičnim zarezom.

Napisali smo jednostavan testni program za izračunavanje eksponenta s postavljenim bitovima zaokruživanja. Ispostavilo se da je u verziji RedHat Linuxa koju smo koristili došlo do greške u radu s matematičkom funkcijom kada je ubačen zlosretni bit. Prijavili smo to RedHatu, nakon nekog vremena dobili smo zakrpu od njih i pustili je. Greška se više nije javljala, ali je bilo nejasno odakle je uopće došao ovaj bit? Funkcija je bila odgovorna za to fesetround iz jezika C. Pažljivo smo analizirali naš kod u potrazi za pretpostavljenom greškom: provjerili smo sve moguće situacije; pogledao sve funkcije koje su koristile zaokruživanje; pokušao reproducirati neuspjelu sesiju; koristio različite prevoditelje s različitim opcijama; Korištene su statička i dinamička analiza.

Nije moguće pronaći uzrok greške.

Zatim su počeli provjeravati hardver: proveli su testiranje opterećenja procesora; provjerio RAM; Čak smo proveli testove za vrlo malo vjerojatan scenarij višebitne pogreške u jednoj ćeliji. Bezuspješno.

Na kraju smo se odlučili za teoriju iz svijeta fizike visokih energija: neka visokoenergetska čestica uletjela je u naš podatkovni centar, probila stijenku kućišta, udarila u procesor i uzrokovala da se zasun okidača zaglavi baš u tom dijelu. Ova apsurdna teorija nazvana je "neutrino". Ako ste daleko od fizike čestica: neutrini gotovo ne komuniciraju s vanjskim svijetom i sigurno ne mogu utjecati na rad procesora.

Budući da nije bilo moguće pronaći uzrok kvara, “prekršajni” server je za svaki slučaj isključen iz rada.

Nakon nekog vremena počeli smo poboljšavati sustav vruće sigurnosne kopije: uveli smo takozvane “tople rezerve” (warm) - asinkrone replike. Primili su niz transakcija koje su se mogle nalaziti u različitim podatkovnim centrima, ali topline nisu aktivno komunicirale s drugim poslužiteljima.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Zašto je to učinjeno? Ako rezervni poslužitelj zakaže, tada topli vezan uz glavni poslužitelj postaje nova rezervna kopija. Odnosno, nakon kvara, sustav ne ostaje s jednim glavnim poslužiteljem do kraja sesije trgovanja.

A kada je nova verzija sustava testirana i puštena u rad, ponovno se pojavila pogreška bita zaokruživanja. Štoviše, s povećanjem broja toplih poslužitelja, greška se počela pojavljivati ​​sve češće. Istovremeno, prodavač nije imao što pokazati, jer nije bilo konkretnih dokaza.

Tijekom sljedeće analize situacije pojavila se teorija da bi problem mogao biti povezan s OS-om. Napisali smo jednostavan program koji poziva funkciju u beskonačnoj petlji fesetround, pamti trenutno stanje i provjerava ga kroz stanje mirovanja, a to se radi u mnogim konkurentskim nitima. Nakon što smo odabrali parametre za spavanje i broj niti, počeli smo dosljedno reproducirati kvar bita nakon otprilike 5 minuta pokretanja uslužnog programa. Međutim, Red Hat podrška nije ga uspjela reproducirati. Testiranje drugih naših poslužitelja pokazalo je da su samo oni s određenim procesorima osjetljivi na grešku. U isto vrijeme, prelazak na novi kernel riješio je problem. Na kraju smo jednostavno zamijenili OS, a pravi uzrok buga ostao je nejasan.

I odjednom je prošle godine na Habréu objavljen članak “Kako sam pronašao grešku u procesorima Intel Skylake" Situacija opisana u njemu bila je vrlo slična našoj, no autor je istragu produžio i iznio teoriju da je pogreška bila u mikrokodu. A kada se Linux kerneli ažuriraju, proizvođači također ažuriraju mikrokod.

Daljnji razvoj sustava

Iako smo se riješili greške, ova nas je priča natjerala da preispitamo arhitekturu sustava. Uostalom, nismo bili zaštićeni od ponavljanja takvih grešaka.

Sljedeći principi bili su osnova za daljnja poboljšanja rezervacijskog sustava:

  • Ne možeš nikome vjerovati. Poslužitelji možda neće ispravno raditi.
  • Rezervacija većine.
  • Osiguravanje konsenzusa. Kao logičan dodatak većinskom rezervatu.
  • Mogući su dvostruki kvarovi.
  • Vitalnost. Nova shema vruće pripravnosti ne bi trebala biti lošija od prethodne. Trgovanje bi se trebalo odvijati bez prekida do posljednjeg poslužitelja.
  • Blago povećanje latencije. Svaki zastoj povlači za sobom velike financijske gubitke.
  • Minimalna mrežna interakcija kako bi latencija bila što niža.
  • Odabir novog glavnog poslužitelja u nekoliko sekundi.

Niti jedno rješenje dostupno na tržištu nije nam odgovaralo, a Raft protokol je bio tek u povojima pa smo kreirali svoje rješenje.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Umrežavanje

Osim rezervacijskog sustava, krenuli smo s modernizacijom mrežne interakcije. I/O podsustav sastojao se od mnogo procesa, koji su imali najgori utjecaj na podrhtavanje i latenciju. Sa stotinama procesa koji rukuju TCP vezama, bili smo prisiljeni stalno se prebacivati ​​između njih, a na skali mikrosekunde to je prilično dugotrajna operacija. Ali najgore je to što kada je proces primio paket za obradu, poslao ga je u jedan SystemV red čekanja i zatim čekao događaj iz drugog SystemV reda čekanja. Međutim, kada postoji veliki broj čvorova, dolazak novog TCP paketa u jednom procesu i primitak podataka u redu čekanja u drugom predstavljaju dva konkurentska događaja za OS. U tom slučaju, ako nema dostupnih fizičkih procesora za oba zadatka, jedan će biti obrađen, a drugi će biti stavljen u red čekanja. Nemoguće je predvidjeti posljedice.

U takvim situacijama može se koristiti dinamička kontrola prioriteta procesa, ali to će zahtijevati korištenje resursno intenzivnih sistemskih poziva. Kao rezultat toga, prebacili smo se na jednu nit koristeći klasični epoll, što je uvelike povećalo brzinu i smanjilo vrijeme obrade transakcije. Također smo se riješili odvojenih mrežnih komunikacijskih procesa i komunikacije kroz SystemV, značajno smanjili broj sistemskih poziva i počeli kontrolirati prioritete operacija. Samo na I/O podsustavu bilo je moguće uštedjeti oko 8-17 mikrosekundi, ovisno o scenariju. Ova jednonitna shema od tada se koristi nepromijenjena; jedna epoll nit s marginom dovoljna je za opsluživanje svih veza.

Obrada transakcija

Sve veće opterećenje našeg sustava zahtijevalo je nadogradnju gotovo svih njegovih komponenti. No, nažalost, stagnacija u rastu takta procesora posljednjih godina više nije omogućila direktno skaliranje procesa. Stoga smo odlučili Engine proces podijeliti na tri razine, a najopterećeniji je sustav za provjeru rizika koji procjenjuje dostupnost sredstava na računima i kreira same transakcije. No, novac može biti u različitim valutama i trebalo je smisliti na temelju čega treba podijeliti obradu zahtjeva.

Logično rješenje je podijeliti ga po valutama: jedan poslužitelj trguje u dolarima, drugi u funtama, a treći u eurima. Ali ako se s takvom shemom pošalju dvije transakcije za kupnju različitih valuta, pojavit će se problem desinkronizacije novčanika. Ali sinkronizacija je teška i skupa. Stoga bi bilo ispravno shardati odvojeno po novčanicima i posebno po instrumentima. Usput, većina zapadnih burzi nema zadatak provjeravati rizike tako akutno kao mi, pa se to najčešće radi izvan mreže. Morali smo implementirati online provjeru.

Objasnimo na primjeru. Trgovac želi kupiti 30 USD, a zahtjev ide na provjeru valjanosti transakcije: provjeravamo je li trgovcu dopušten ovaj način trgovanja i ima li potrebna prava. Ako je sve u redu, zahtjev ide u sustav provjere rizika, tj. provjeriti dostatnost sredstava za sklapanje transakcije. Postoji napomena da je traženi iznos trenutno blokiran. Zahtjev se zatim prosljeđuje trgovinskom sustavu koji odobrava ili ne odobrava transakciju. Recimo da je transakcija odobrena - tada sustav provjere rizika označava da je novac deblokiran, a rublje se pretvara u dolare.

Općenito, sustav za provjeru rizika sadrži složene algoritme i izvodi veliku količinu kalkulacija koje zahtijevaju velike resurse, a ne provjerava samo „stanje računa“, kako bi se moglo činiti na prvi pogled.

Kada smo proces Enginea počeli dijeliti na razine, naišli smo na problem: kod koji je u to vrijeme bio dostupan aktivno je koristio isti niz podataka u fazama validacije i verifikacije, što je zahtijevalo ponovno pisanje cijele baze koda. Kao rezultat toga, posudili smo tehniku ​​za obradu instrukcija od modernih procesora: svaka od njih je podijeljena u male faze i nekoliko akcija se izvodi paralelno u jednom ciklusu.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Nakon male prilagodbe koda, napravili smo cjevovod za paralelnu obradu transakcija, u kojem je transakcija podijeljena u 4 faze cjevovoda: mrežna interakcija, validacija, izvršenje i objava rezultata

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Pogledajmo primjer. Imamo dva obradna sustava, serijski i paralelni. Prva transakcija stiže i šalje se na provjeru u oba sustava. Druga transakcija stiže odmah: u paralelnom sustavu odmah se pokreće, au sekvencijalnom sustavu stavlja se u red čekanja da prva transakcija prođe kroz trenutnu fazu obrade. Odnosno, glavna prednost cjevovodne obrade je ta što brže obrađujemo red čekanja transakcije.

Tako smo došli do sustava ASTS+.

Istina, ni s pokretnim trakama nije sve tako glatko. Recimo da imamo transakciju koja utječe na nizove podataka u susjednoj transakciji; ovo je tipična situacija za razmjenu. Takva se transakcija ne može izvršiti u cjevovodu jer može utjecati na druge. Ova situacija se naziva data hazard, a takve se transakcije jednostavno zasebno obrađuju: kada ponestane "brzih" transakcija u redu čekanja, cjevovod se zaustavlja, sustav obrađuje "sporu" transakciju, a zatim ponovno pokreće cjevovod. Srećom, udio takvih transakcija u ukupnom toku je vrlo mali, pa se cjevovod zaustavlja tako rijetko da ne utječe na ukupnu izvedbu.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Zatim smo počeli rješavati problem sinkronizacije tri niti izvršenja. Rezultat je bio sustav temeljen na prstenastom međuspremniku s ćelijama fiksne veličine. U ovom sustavu sve ovisi o brzini obrade, podaci se ne kopiraju.

  • Svi dolazni mrežni paketi ulaze u fazu dodjele.
  • Postavljamo ih u niz i označavamo kao dostupne za stupanj #1.
  • Druga transakcija je stigla, opet je dostupna za fazu broj 1.
  • Prva nit za obradu vidi dostupne transakcije, obrađuje ih i premješta u sljedeću fazu druge niti za obradu.
  • Zatim obrađuje prvu transakciju i označava odgovarajuću ćeliju deleted — sada je dostupan za novu upotrebu.

Cijeli red čekanja se obrađuje na ovaj način.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Obrada svake faze traje jedinice ili desetke mikrosekundi. A ako koristimo standardne sheme sinkronizacije OS-a, tada ćemo izgubiti više vremena na samu sinkronizaciju. Zato smo počeli koristiti spinlock. Međutim, ovo je vrlo loš oblik u sustavu u stvarnom vremenu, a RedHat strogo ne preporučuje da se to radi, pa primjenjujemo spinlock na 100 ms, a zatim se prebacujemo na semaforski način rada kako bismo eliminirali mogućnost zastoja.

Kao rezultat toga, postigli smo učinak od oko 8 milijuna transakcija u sekundi. I doslovno dva mjeseca kasnije u članak o LMAX Disruptoru vidjeli smo opis sklopa s istom funkcionalnošću.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Sada može postojati nekoliko niti izvršenja u jednoj fazi. Sve su transakcije obrađene jedna po jedna, redoslijedom kojim su primljene. Kao rezultat toga, vrhunska izvedba porasla je s 18 tisuća na 50 tisuća transakcija u sekundi.

Sustav upravljanja burzovnim rizikom

Ne postoji granica savršenstvu, a ubrzo smo ponovno krenuli s modernizacijom: u okviru ASTS+ sustave upravljanja rizicima i poravnanja počeli smo premještati u autonomne komponente. Razvili smo fleksibilnu modernu arhitekturu i novi hijerarhijski model rizika i pokušali koristiti klasu gdje god je to bilo moguće fixed_point umjesto double.

Ali odmah se pojavio problem: kako svu poslovnu logiku koja radi dugi niz godina uskladiti i prenijeti u novi sustav? Zbog toga je prva verzija prototipa novog sustava morala biti napuštena. Druga verzija, koja trenutno radi u produkciji, temelji se na istom kodu, koji radi iu dijelu za trgovanje i dijelu rizika. Tijekom razvoja, najteže je bilo spojiti dvije verzije. Naš kolega Evgeniy Mazurenok izvodio je ovu operaciju svaki tjedan i svaki put je psovao jako dugo.

Prilikom odabira novog sustava odmah smo morali riješiti problem interakcije. Prilikom odabira podatkovne sabirnice bilo je potrebno osigurati stabilan jitter i minimalnu latenciju. InfiniBand RDMA mreža bila je najprikladnija za to: prosječno vrijeme obrade je 4 puta manje nego u 10 G Ethernet mrežama. Ali ono što nas je doista oduševilo je razlika u percentilima - 99 i 99,9.

Naravno, InfiniBand ima svoje izazove. Prvo, drugačiji API - ibverbs umjesto socketa. Drugo, gotovo da nema široko dostupnih rješenja za razmjenu poruka otvorenog koda. Pokušali smo napraviti vlastiti prototip, ali pokazalo se da je to jako teško pa smo odabrali komercijalno rješenje - Confinity Low Latency Messaging (ranije IBM MQ LLM).

Tada se pojavio zadatak pravilne podjele sustava rizika. Ako jednostavno uklonite Risk Engine i ne stvorite srednji čvor, tada se transakcije iz dva izvora mogu miješati.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Takozvana rješenja Ultra Low Latency imaju način preraspodjele: transakcije iz dva izvora mogu se posložiti u željeni redoslijed po primitku; to se provodi pomoću zasebnog kanala za razmjenu informacija o narudžbi. Ali mi još ne koristimo ovaj način rada: komplicira cijeli proces, au nizu rješenja uopće nije podržan. Osim toga, svakoj bi se transakciji morale dodijeliti odgovarajuće vremenske oznake, au našoj je shemi ovaj mehanizam vrlo teško ispravno implementirati. Stoga smo koristili klasičnu shemu s brokerom poruka, odnosno s dispečerom koji distribuira poruke između Risk Enginea.

Drugi problem odnosio se na pristup klijenta: ako postoji nekoliko Risk Gatewaya, klijent se mora povezati na svaki od njih, a to će zahtijevati promjene na sloju klijenta. Željeli smo pobjeći od ovoga u ovoj fazi, tako da trenutni dizajn Risk Gatewaya obrađuje cijeli tok podataka. Ovo uvelike ograničava maksimalnu propusnost, ali uvelike pojednostavljuje integraciju sustava.

Dupliciranje

Naš sustav ne bi trebao imati niti jednu točku kvara, odnosno sve komponente moraju biti duplicirane, uključujući i broker poruka. Ovaj problem smo riješili pomoću CLLM sustava: on sadrži RCMS klaster u kojem dva dispečera mogu raditi u master-slave modu, a kada jedan zakaže, sustav se automatski prebacuje na drugog.

Rad s rezervnim podatkovnim centrom

InfiniBand je optimiziran za rad kao lokalna mreža, odnosno za povezivanje rack mount opreme, a InfiniBand mreža ne može se postaviti između dva geografski raspoređena podatkovna centra. Stoga smo implementirali most/dispečer, koji se povezuje sa pohranom poruka putem običnih Ethernet mreža i prosljeđuje sve transakcije drugoj IB mreži. Kada trebamo migrirati iz podatkovnog centra, možemo odabrati s kojim ćemo podatkovnim centrom sada raditi.

Rezultati

Sve navedeno nije napravljeno odjednom, bilo je potrebno nekoliko iteracija razvoja nove arhitekture. Prototip smo izradili u mjesec dana, no trebalo je više od dvije godine da ga dovedemo u radno stanje. Pokušali smo postići najbolji kompromis između povećanja vremena obrade transakcija i povećanja pouzdanosti sustava.

Budući da je sustav bio intenzivno ažuriran, implementirali smo oporavak podataka iz dva neovisna izvora. Ako pohrana poruka iz nekog razloga ne radi ispravno, zapisnik transakcija možete uzeti iz drugog izvora - iz Risk Enginea. Ovo se načelo poštuje u cijelom sustavu.

Između ostalog, uspjeli smo sačuvati klijentski API tako da ni brokeri ni bilo tko drugi ne bi zahtijevao značajne prerade za novu arhitekturu. Morali smo promijeniti neka sučelja, ali nije bilo potrebe za značajnijim promjenama u operativnom modelu.

Aktualnu verziju naše platforme nazvali smo Rebus - kao skraćenica za dvije najzapaženije inovacije u arhitekturi, Risk Engine i BUS.

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

U početku smo htjeli dodijeliti samo klirinški dio, ali rezultat je bio ogroman distribuirani sustav. Klijenti sada mogu komunicirati s Trade Gatewayom, Clearing Gatewayom ili s oba.

Što smo na kraju postigli:

Evolucija arhitekture sustava trgovanja i kliringa Moskovske burze. 2. dio

Smanjena razina latencije. S malim volumenom transakcija, sustav radi isto kao i prethodna verzija, ali istovremeno može izdržati puno veće opterećenje.

Vrhunska izvedba povećana je s 50 tisuća na 180 tisuća transakcija u sekundi. Daljnji porast je otežan jedinom strujom slaganja naloga.

Postoje dva načina za daljnje poboljšanje: paralelno usklađivanje i promjena načina rada s pristupnikom. Sada svi Gatewayi rade prema shemi replikacije, koja pod takvim opterećenjem prestaje normalno funkcionirati.

Na kraju, mogu dati nekoliko savjeta onima koji dovršavaju poslovne sustave:

  • U svakom trenutku budite spremni na najgore. Problemi uvijek nastaju neočekivano.
  • Obično je nemoguće brzo preurediti arhitekturu. Pogotovo ako trebate postići maksimalnu pouzdanost na više pokazatelja. Što je više čvorova, to je više resursa potrebno za podršku.
  • Sva prilagođena i vlasnička rješenja zahtijevat će dodatne resurse za istraživanje, podršku i održavanje.
  • Ne odgađajte rješavanje problema pouzdanosti sustava i oporavka nakon kvarova; uzmite ih u obzir u početnoj fazi projektiranja.

Izvor: www.habr.com

Dodajte komentar