Ugrás a fő tartalomra
OpenAI

2026. június 30.

Mérnöki tevékenység

Core dump epidemiológia: egy 18 éves hiba javítása

Populációszintű elemzés használata nehezen felderíthető összeomlások hibakereséséhez az adatinfrastruktúránkban.

Betöltés…

Az OpenAI modelljei és ügynökei egyre inkább skálázható adatinfrastruktúrára támaszkodnak, hogy következtetéskor – amikor a modellek az Ön kérdésén gondolkodnak – releváns adatokat találjanak. E szolgáltatások egy része C++-ban íródott; a rendszer alacsony szintű vezérlése így maximális teljesítményt és minimális memóriahasználatot tesz lehetővé. Ezek a hatékonysági előnyök fontosak a skálázás során, de a C++ memóriabiztonságának hiánya miatt a hibák összeomlást okozhatnak, ha rossz vagy nem létező memóriacímre írnak.

Néhány hónapja összeomlásokat láttunk a Rockset szolgáltatáson belül; ez a ChatGPT adatinfrastruktúrájának egyedi része, amely sok adatbővítményhez és a beszélgetésekben való kereséshez kulcsfontosságú. Ezekben az összeomlásokban egy normál C++ függvény látszólag befejeződött, majd hamis címre tért vissza, ezért a kernel leállította a programot, mert az utasításmutató már nem kódra mutatott. Néha a veremkeret visszatérési címének helye NULL volt. Máskor maga a veremmutató CPU-regiszter tűnt 8 bájttal eltoltnak, mintha a %rsp valahogy csökkent volna a normál végrehajtás közben. Mindkét esetben visszatéréskor történt az összeomlás.

Ezek nem szokványos hibamódok alkalmazáskódban. Elvileg előfordulhat egy eltévedt írás, amely csak egy mentett visszatérési címet talál el, de rendkívül valószínűtlen. Még furcsább egy olyan hiba, amely 8 bájttal félreigazítja a %rsp-t inline assembly, setcontext vagy longjmp nélkül (egyiket sem használjuk), mert a fordított kód ezt a regisztert közvetlenül csak a függvény prologusában és epilogusában módosítja. Minden hipotézis ellen, amely nekünk (vagy a ChatGPT‑nek) eszünkbe jutott, erős bizonyíték szólt, így a hiba lehetetlennek tűnt.

Amiről azt hittük, egyetlen probléma, végül két, véletlenül egyszerre felfedezett, egymástól független hibának bizonyult. Az első: csendes hardverkorrupció egy Azure-gazdagépen, ahol a CPU egyszerűen rosszul számolt. A második: egy 18 éves versenyhelyzet a GNU libunwindben, egy észrevétlen hiba egy széles körben használt nyílt forrású könyvtárban.

Ez a bejegyzés arról szól, hogyan azonosítottunk és javítottunk látszólag megmagyarázhatatlan összeomlásokat epidemiológusként gondolkodva, és jó minőségű adatkészletet építve az összeomlások teljes populációjáról.

Első hibakeresési kísérlet: néhány core dump alapos vizsgálata

Először nézzük meg közelebbről a Rocksetet. Ez egy felhőnatív adatrendszer kereséshez és valós idejű analitikához, amelyet az OpenAI-nál sok belső esetre használunk, például szinkronizáló csatlakozókhoz (a Rocksetet az OpenAI 2024-ben vásárolta fel). A streamelt frissítések naprakészen tartják egy munkaterület tudásbázisának indexét, hogy a ChatGPT releváns információkat kereshessen kérdések megválaszolásakor vagy műveletek végrehajtásakor.

A Rockset végrehajtási rétege C++-ban íródott. A C++ alacsony szintű hozzáférést ad a CPU-hoz, ami jó a teljesítménynek és a hatékonyságnak, de az alkalmazáshibák érvénytelen memóriahozzáféréshez és segfaultokhoz vezethetnek. Ezek felderítéséhez a folly fatal signal handlerét használjuk, hogy összeomláskor veremnyomot naplózzunk, a megfelelő core dumpokat (a program összeomláskori állapotának pillanatképét) pedig későbbi elemzésre Azure blobtárhelyre töltjük fel. A Rockset lekérdezésfeldolgozó levelei mind replikáltak, ami csökkenti egy összeomlás ügyféloldali hatását. Ugyanakkor minden segfault olyan hibát jelez, amelyet a megbízhatósági és minőségi céljainkhoz ki kell javítani.

Kezdetben hagyományos hibakeresési problémaként kezeltük ezeket a core-okat: néhány core dumpot nagyon alaposan megvizsgáltunk, hipotéziseket alkottunk, majd egyenként kizártuk őket.

A legtöbb összeomlás egy DocumentTree::updateDocument nevű metódusban történt. Ezeknél úgy tűnt, hogy az updateDocument meghívott egy ismeretlen X függvényt, a verem X futása közben sérült, majd X nem végrehajtható kódcímre tért vissza. Néhány esetben X épp lepattintott kerete érvényesnek látszott, kivéve hogy a mentett visszatérési címe NULL volt. Más esetekben maga a veremmutató tűnt hibásnak, de a következő érvényes keret továbbra is az updateDocument volt.

Nem tudtuk, mikor sérül meg a verem, ezért óriási volt a keresési tér. Az updateDocument nagy metódus, sok inline-olással, ezért X lehetséges jelöltjeinek száma nyomasztóan nagy volt.

A mi C++ kódunk hibája volt? Fordító- vagy linkelési probléma? Hiba valamelyik futásidejű könyvtárunkban? Linux-kernelhiba a jelek kézbesítése vagy a kontextusváltás körül? Valami még ritkább? Ha ez eltévedt írás volt, miért nem kapta el az ASAN staging környezetünk?

Megpróbáltuk az alkalmazásszintű naplókból azonosítani a probléma összes előfordulását, de a veremsérülési hibákat pusztán naplók alapján nehéz osztályozni, mert a naplózott veremnyomok maguk is sérültek vagy hiányoznak. Nem tudtunk olyan naplólekérdezést készíteni, amelyben ne lett volna egyszerre hamis pozitív és hamis negatív találat. Kézzel több core-t is átnéztünk, és találtunk további példákat, de ez túl munkaigényes volt egy megbízható adatkészlethez.

A vizsgálat ezen pontján (tévesen) kizártuk a hardverhibát, mert több régióban és több hardvertípuson láttunk összeomlásokat, ezért továbbra is kizárólag szoftveres okokat kerestünk. Néhány napig nagyon mélyre ástuk magunkat egyetlen félreigazított-%rsp összeomlásban, a verem- és regisztertartalom alapján rekonstruálva az összeomlás előtti történetet. Ez adott néhány lehetséges nyomot, de mivel nem engedtük el azt a kezdeti következtetésünket, hogy minden hiba oka ugyanaz, nem vitt ki a zsákutcából.

Nyomok a veremből

Mielőtt eljutnánk a vizsgálat fordulópontjához, érdemes elmagyarázni, milyen információt nyertünk ki a core-fájlokból.

A Rocksetet -fno-omit-frame-pointer opcióval fordítjuk, így az aktív veremkeret mindig elérhető a következőn keresztül: %rbp, a hívók pedig keretmutatókból álló láncolt listát alkotnak.

Linux x86_64 alatt az AMD64 System V ABI a %rsp alatt 128 bájtot red zone-ként is lefoglal. Ez a terület elérhető a userspace kódnak, és ami fontos: az ABI-szerződés részeként a kernel megígéri, hogy jel kézbesítésekor nem írja felül.

A red zone központi szerepet játszott egy visszatérés utáni összeomlás hibakeresésében, mert megőriz némi információt a visszatérés előtti állapotból. Amikor SIGSEGV váltódik ki, a folly fatal signal handlere az összeomló szál vermén fut. A már nem aktív veremkereteket (mert a függvényük visszatért) a signal handler felülírja, az utolsó 128 bájt kivételével. Ezért mondhatunk olyasmit, hogy „X épp lepattintott veremkerete érvényesnek tűnt, kivéve egy NULL visszatérési címet.” A red zone megőriz néhány inaktív keretet, vagy néha csak egy inaktív keret végét.

Veremdiagram sérült veremkeretekkel, amelyek felülírhatják a visszatérési címeket és összeomlásokat okozhatnak.

Találtunk egy félreigazított veremhez kapcsolódó összeomlást, amelyben minden érintett függvény nagyon kicsi volt. Így láttuk, hogy a %rsp egy viszonylag egyszerű függvény futása közben igazítódott félre, és utána még több hívás sikerült. A program csak akkor omlott össze, amikor az aktív függvény végül megpróbált visszatérni. Egyik kódút sem használt kivételeket, inline assemblyt, setcontextet vagy longjmp-t, így ha a veremmutató valóban úgy változott, ahogy a core sugallta, semmilyen hihető userspace kódbeli hiba nem magyarázta.

Ez a kernel felé terelt minket.

A Rockset a legtöbb programnál agresszívebben használ jeleket. A lekérdezés-végrehajtás sok könnyű feladatra bomlik, amelyek adatot cserélnek. Ez fontos a nagy QPS-terhelések hatékony kezeléséhez, de megnehezíti a lekérdezésenkénti CPU-elszámolást, mert sok lekérdezés munkája ugyanarra a szálkészletre multiplexálódik.

A megoldásunk a coarse_thread_cputime_clock, amely a clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) olcsó közelítése, elég gyors ahhoz, hogy minden feladathatáron mintát vegyünk. A timer_create API-val időszakos jelkézbesítés ütemezhető az idő múlásának többféle fogalma alapján, beleértve a CPU-idő felhalmozódását. Úgy ütemezünk egy jelet (SIGUSR2), hogy néhány milliszekundum CPU-időnként kézbesüljön; ekkor a signal handler frissít egy szálszintű értéket. Bár sok feladat futás közben nem látja előrelépni a durva órát, az összes delta összege torzítatlan becslést ad egy lekérdezés tényleges CPU-idejére.

Mivel ilyen gyakran kézbesítünk jeleket, hihetőnek tűnt egy ritka kernelhiba a kontextusváltás vagy jelkézbesítés körül. Időt töltöttünk hibajelentések, kernel-forráskód és Azure-specifikus kernelpatchek olvasásával. Stressztesztekkel is próbálkoztunk. Nem találtunk semmit, ami kapcsolódónak tűnt.

Ekkor úgy döntöttünk, hátralépünk, és más megközelítést próbálunk.

Orvos vagy epidemiológus?

Egy ilyen probléma hibakeresésére két fő módszer van.

Az egyik, hogy orvosként járunk el: egy betegre figyelünk, sok tesztet futtatunk, és részletes bizonyítékokból próbálunk diagnosztizálni egyetlen esetet.

A másik inkább epidemiológusi szemlélet: a teljes populációt nézzük, és olyan mintázatokat keresünk, amelyeket egyetlen eset nem fedhet fel. Egy adott kiadásnál kezdődött a hiba? Korrelál egy hardver-SKU-val (az adott CPU- és szervermodellel), egy régióval vagy egy kernelverzióval? Lehet, hogy az egy szindrómának látszó jelenségben több külön klaszter rejtőzik?

Többnyire orvos módban voltunk. A döntő váltás az volt, hogy jó minőségű populációs adatokat kell gyűjtenünk.

Az adatok tisztítása

Korábbi próbálkozásaink azért buktak el a probléma összes példányának automatikus megtalálására, mert szöveges keresést próbáltunk használni a naplókban. Maguk a core dumpok sokkal több információt tartalmaznak, de kézzel átnézni őket nem skálázódott. Úgy döntöttünk, megéri pipeline-t építeni, amely automatikusan elemzi a core dumpokat.

A ChatGPT‑vel írattunk egy szkriptet, amely letöltötte minden core-fájl prefixét, kinyerte a regisztereket, a naplók alapján kiszűrte az ismert hamis pozitívakat, és automatikusan return-to-null, misaligned-stack vagy other címkével látta el az összeomlást. Ezután a szkriptet párhuzamosan lefuttattuk az előző év összes éles Rockset core dumpján.

Ez volt a fordulópont.

Amint tiszta adatkészletünk lett, a korrelációk azonnal megjelentek. Amit egyetlen furcsa hibának kezeltünk, valójában két külön összeomlási populáció volt.

A return-to-null core-ok sok klaszter és földrajzi régió között oszlottak meg. Gyakoriságuk nemrég nőtt, de nem volt éles kezdődátum és tiszta infrastruktúrahatár.

A félreigazított veremmel járó összeomlások teljesen más képet mutattak. Mind egy régióból jöttek, egyértelmű kezdődátumuk volt, és soha nem történtek régóta futó csomópontokon. Bár több Azure VM-et (felhőben hosztolt virtuális gépet) érintettek, a mintázat úgy nézett ki, mintha egy hibás hardverű fizikai gép okozna gondot annak a VM-nek, amely épp rákerült.

Pontdiagram az összeomlási arányokról klaszterenként és időben, amelyen a legtöbb összeomlás a 2., 3. és 6. klaszterben koncentrálódik, az időszak vége felé pedig kiugrás látható az 1. klaszterben.

Ekkor jöttünk rá, hogy fejben két hibát mostunk össze. Mivel mindkét hibából származó ellenpéldákat kevertünk, nem találtunk egyetlen koherens magyarázatot.

1. hiba: rossz gazdagép

A Kubernetes-csomópontok és időbélyegek tiszta listájával a félreigazított veremmel járó összeomlásokat egyetlen fizikai gazdagépre tudtuk visszavezetni, amelyet könnyű volt tiltólistára tenni.

A regisztersérülést ezen a gazdagépen kontrollált környezetben többhetes stresszteszt után sem tudtuk reprodukálni. Miután azonban a problémás gazdagépet kivontuk a szolgálatból, a félreigazított veremmel járó összeomlások eltűntek.

A rossz gazdagép eltávolítása nem végleges megoldás abban az értelemben, hogy nem akadályozza meg ugyanennek a problémának az újbóli előfordulását. A szoftvert viszont módosíthatjuk úgy, hogy ha hasonló gond újra felmerül, könnyen észlelhető és kezelhető legyen. Továbbfejlesztettük a fatal signal handlerünket, hogy tartalmazza a regiszterállapotot, így az ismétlődést pusztán a naplókból is észlelhetjük (core dump nélkül). Úgy módosítottuk a vezérlősíkot, hogy a VM-eket általában újrahasználjuk, ne újra létrehozzuk; így az infrastruktúrarétegünk szintjén sokkal könnyebb a rossz csomópontok felismerése. A runbookjainkat (és a csapat mentális modelljeit) is frissítettük, hogy ezt a lehetőséget is tartalmazzák.

Miután a rossz gazdagépből eredő összeomlásokat leválasztottuk, a megmaradó return-to-null core-okról sokkal könnyebb volt gondolkodni. Korábban kizártuk a kivétel-unwindingot, mert úgy hittük, vannak ellenpéldáink: olyan kódutakban történt összeomlások, ahol biztosan nem használtunk kivételeket. De ezek az ellenpéldák mind a hardverkorrupciós klaszterből származtak.

Amikor ezt észben tartva újranéztük a megmaradt core-okat, kiderült, hogy a következtetésünk pontosan fordított volt: az összeomlások mind kivétel-unwinding közben történtek.

A kivételkezelés dinamikus vezérlésátadás

Amikor a C++ kivételt dob, a futtatókörnyezetnek ki kell derítenie, melyik catch blokk kapja meg, és mely destruktoroknak vagy takarító handlereknek kell közben lefutniuk. A fordító kibocsátja ezeket a metaadatokat, de a tényleges illesztés futásidőben, dinamikusan történik.

A kivétel-unwindingot valójában nem a throw-t meghívó függvény végzi, hanem a létrejövő fordított kód által hívott segédfüggvények. Ezek a futásidejű rutinok megvizsgálják a vermet, lekérik a veremben talált függvények metaadatait, dinamikusan keresnek takarító handlereket és catch blokkokat, majd átadják a vezérlést e helyek egyikére. A vezérlésátadás magában foglalja az összes köztes veremkeret felgöngyölítését is (a segédfüggvények kereteit is).

Működését tekintve ez sokkal közelebb áll egy longjmp-hez vagy fiber-váltáshoz, mint egy normál híváshoz és visszatéréshez. Vissza kell állítani a callee-save regisztereket, valamint a veremkeret-regisztereket: %rbp és %rsp.

A binárisunk két olyan könyvtárhoz linkel, amelyek tartalmazzák a C++ kivétel-unwindingot végző függvények implementációit: a libgcc-hez és a GNU libunwindhez. A dinamikus linker a GNU libunwind definícióit választotta. Ez meglepett minket; a szimbólumverziózási szabályok miatt arra számítottunk, hogy a libgcc implementációja nyer, de a futó binárisok vizsgálata mást mutatott.

Egy utolsó feltételezés visszavonása

Ekkor megváltozott a munkahipotézisünk, mert elengedtünk egy újabb feltételezést, amelyet akkor tettünk, amikor még azt hittük, csak egy hiba van.

Lehet, hogy nem egy szokványos függvény NULL-ra visszatérését láttuk. Lehet, hogy unwind-átadást láttunk – gyakorlatilag setcontext-szerű regiszter-visszaállítást –, ahol a cél utasításmutató NULL lett, mielőtt a vezérlés átadódott volna. Más szóval nem a verembeli visszatérési címhely volt hibás, hanem az unwind könyvtárból származó adat.

Ez drámaian leszűkítette a problémát. Vagy a GNU libunwind számolta ki rosszul a célállapotot, vagy jól számolta ki, de valami megsértette, mielőtt alkalmazni lehetett volna.

Elolvastuk a GNU libunwind forrását, és azt találtuk, hogy a veremben szintetizál egy ucontext_t-t, kitölti a takarító handler keretéhez kívánt regiszterállapotot, majd a struktúrára mutató pointert átadja egy belső assembly rutinnak: _Ux86_64_setcontext.

Ekkor már minden darab a helyén volt.

A szintetizált ucontext_t az egyik olyan veremkeretben él, amelyet az _Ux86_64_setcontext a futása közben felgöngyölít. Vajon az _Ux86_64_setcontext a struktúrából olvas azután, hogy megváltoztatja a %rsp-t, amikor a struktúra már nem része az aktív veremnek? Ez sérülékennyé tenné egy jelkézbesítés általi felülírással szemben, például a gyakori SIGUSR2-nkkel.

2. hiba: a libunwind hibája

A válasz igen volt.

Íme az általunk használt GNU libunwind-verzió _Ux86_64_setcontext rutinjának utolsó hat utasítása; ezek többnyire mov-utasítások, amelyek memóriából töltenek egy célregiszterbe:

Egyszerű szöveg

1
74: mov UC_MCONTEXT_GREGS_RSP(%rdi),%rsp
2
75:
3
76: /* push the return address on the stack */
4
77: mov UC_MCONTEXT_GREGS_RIP(%rdi),%rcx
5
78: push %rcx
6
79:
7
80: mov UC_MCONTEXT_GREGS_RCX(%rdi),%rcx
8
81: mov UC_MCONTEXT_GREGS_RDI(%rdi),%rdi
9
82: retq

(%rdi a veremben allokált ucontext_t-re mutat, a UC_MCONTEXT_*-makrók pedig csak arra a fix eltolásra terjeszkednek ki, ahol az adott regiszter tárolódik.)

Az első utasítás a versenyablak kezdete. Frissíti a %rsp-t, hogy az aktív verem új aljára mutasson. Amint ez megtörténik, a %rdi által mutatott struktúra már nem része az aktív veremnek (vagy red zone-nak), és a kernel számára már nem tiltott terület.

Ez általában nem okoz gondot, de ha egy jel pontosan a megfelelő (rossz?) pillanatban érkezik, a kernel a jelkeretet a %rsp-128 címen építi fel. Ez felülírhatja a %rdi által mutatott memóriát.

Ha ez azelőtt történik, hogy a következő utasítás kiolvasná a következőt: UC_MCONTEXT_GREGS_RIP(%rdi), akkor a visszaállított utasításmutató megsérülhet. A mi összeomlásainkban NULL lett.

Ez volt a hiba.

Miért tűntek a core-ok szokványos rossz visszatéréseknek?

Ez az assembly magyarázza az egyik megfigyelést is, amely összezavart minket: miért volt NULL X függvény előző veremkeretének visszatérési címhelyén?

A setcontext feladata az összes regiszter, köztük a következő: %rdi visszaállítása, ezért a vezérlésátadás utolsó pillanatában nem használhatja ezt a regisztert az UC_MCONTEXT_GREGS_RIP(%rdi) olvasására. Ehelyett korábban olvassa ki az értéket, elmenti a verembe, visszaállít még néhány regisztert, majd a retq-val olvassa ki a mentett értéket és adja át a vezérlést.

Ami a core-okban úgy látszott, hogy „egy függvény NULL-ra tért vissza”, valójában ez volt: „az unwinder cél-visszatérési címet szintetizált a verembe, de ez a cél megsérült, mielőtt az átadás befejeződött.” Azt feltételeztük, hogy a visszatérési címhely sérülése csak helyben történhet, mert nem ismertünk olyan helyet, ahol (sérülékeny) adatot szándékosan írnának a visszatérési cím helyére.

Egyetlen utasításnyi versenyablak

A hibát az teszi abszurdnak tűnővé, hogy milyen keskeny ez a versenyablak. Ilyen versenyhelyzetben a külső eseménynek (a jelnek) egy másik szál két lépése között kell bekövetkeznie. Minél közelebb vannak egymáshoz ezek a lépések, annál kisebb a versenyhelyzet esélye.

Ebben az esetben a sérülékeny ablak szó szerint egy utasítás széles! A jelnek azután kell megérkeznie, hogy a %rsp megváltozott, de még azelőtt, hogy a következő utasítás betöltené a %rip-et. Egy modern, szuperskaláris, soron kívüli végrehajtású CPU ciklusonként több ilyen egyszerű utasítást is futtathat, így a versenyablak nagyjából száz pikoszekundum.

Amikor megtaláltuk ezt a versenyt, az első reakciónk az volt, hogy túl ritka lehet a megfigyelt összeomlási arány magyarázatához. A flottában naponta több mint egy tucat return-to-null összeomlást láttunk. Tényleg magyarázhatja ezt egy egyutasításos verseny a kivételtakarítás során?

Fermi-becsléshez fordultunk. Ha a sérülékeny ablak nagyságrendje 101010^{-10} másodperc, és a SIGUSR2 minden 10210^{-2} másodperc CPU-időnként érkezik, akkor minden kivételtakarító handlernek vagy catch blokknak nagyjából 10810^{-8} esélye van elveszíteni a versenyt.

A Rockset belső ingest backpressure mechanizmusának részeként használ kivételeket. Egyetlen túlterhelt gazdagép másodpercenként nagyságrendileg 10410^{4} kivételt dobhat. Ez azt jelenti, hogy egy backpressure-t használó gazdagép átlagos hibaközi ideje 10410^{4} másodperc, vagyis néhány óránként egy összeomlás. Flottaszinten ez bőven elég a megfigyelt összeomlási gyakoriság magyarázatához.

Miért most jelent meg a libunwind hibája?

A GNU libunwind hibája régi – több mint 18 éves, már az első, C++ kivétel-unwindingot támogató x86_64 verzióban is jelen volt.

Akkor miért most bukkant fel?

Az összeomlási arány nagyjából arányos a dobott kivételek és a kézbesített jelek számával. Attól is függ, mennyi vermet fogyaszt a signal handler.

A Rockset mindhárom tengelyen szokatlan. A normál túlterhelés-kezelés részeként nagy arányban dobunk kivételeket; a coarse_thread_cputime_clock miatt szokatlanul gyakran kézbesítünk SIGUSR2-t; idén korábban pedig a SIGUSR2 handler több vermet kezdett használni, mert hozzáadtunk egy timer_getoverrun hívást az összevont jelek elszámolásához.

Úgy tűnik, az utolsó változtatás fontos volt. Ha a handler elég kevés vermet használ, lehet, hogy nem éri el és nem írja felül az elavult ucontext_t memóriát. A változtatás előtt egyáltalán nem figyeltünk meg ilyen összeomlásokat. A változtatás után az arány alacsony maradt, amíg néhány, a backpressure mechanizmust terhelő használati esetnél fel nem futtattuk a terhelést.

Más szóval a libunwind hibája mindig ott volt, de a kivételi arány, jelarány és handler-veremhasználat szorzata csak nemrég lépte át azt a küszöböt, ahol üzemeltetésileg láthatóvá vált.

Ez a mechanizmus azt az egybeesést is magyarázza, hogy a hardverhiba és a libunwind hibája is főleg a DocumentTree::updateDocument belsejében okozott összeomlást. A libunwindből eredő összeomlások erősen e metódus felé torzultak, mert mindig aktív, amikor kivételt dobunk az ingest backpressure alkalmazásához. A %rsp-félreigazításos összeomlásoknál is erős volt ez a kiválasztódás, mert a hibás hardveres csomópont olyan SKU volt, amelyet tömeges ingesthez használunk, és CPU-ideje nagy részét ebben a metódusban tölti.

Azonnali enyhítésként GNU libunwindről a libgcc unwinderére váltottunk. Ez önmagában is jó csere volt: a libgcc implementációjában sok munka csökkentette a zárolási versengést, ami nagy VM-ekre skálázáskor fontos.

A GNU libunwindhez upstreameltünk egy önálló reprodukálót és egy javítást(új ablakban nyílik meg), és ellenőriztük, hogy a többi unwinderben nincs hasonló probléma.

A populációszintű diagnózis ereje

Ez a hibakeresési út sokat tanított nekünk a dinamikus linkelés, a DWARF unwind metaadatok, a Linux jelkézbesítés, a System V ABI és a C++ kivételkezelő gépezet részleteiről, de a fő tanulság ennél egyszerűbb volt.

A legfontosabb lépés nem az ügyes assembly-olvasás vagy a részletek mély ismerete volt. Hanem egy jó minőségű adatkészlet felépítése. E nélkül az adatkészlet nélkül két külön jelenséget mostunk volna össze egyetlen történetté, és ebből próbáltuk volna kitalálni a kiutat. Amint pontos és teljes populációs adataink lettek, a probléma szerkezete nyilvánvalóvá vált: az egyik összeomlási populáció egy hibás gazdagéphez, a másik a libunwind egyik versenyhelyzetéhez tartozott. Amikor jobb lett az adat, könnyebb lett a hibakeresés.

Az olyan infrastruktúrarendszereknél, mint a Rockset, ez sokat számít. Ez a vizsgálat megerősítette elkötelezettségünket a mély instrumentáció, az automatizált vizsgálatok és az üzemeltetési eszközeink folyamatos fejlesztése mellett. A megbízhatóság nem csak arról szól, hogy kijavítjuk a hibákat, miután megtörténtek: arról is, hogy megépítjük az adatokat, munkafolyamatokat és készségeket, amelyek a lehetetlennek tűnő problémákat diagnosztizálhatóvá és megoldhatóvá teszik.

Szerzők

By Nathan Bronson és Member of Technical Staff