Негізгі мазмұнға өту
OpenAI

2026 ж. 30 маусым

Инженерия

Core dump-тар эпидемиологиясы: 18 жылдық қатені түзету

Дерек инфрақұрылымымыздағы күрделі істен шығуларды диагностикалау үшін популяциялық деңгейдегі талдауды қолдану.

Жүктелуде…

OpenAI модельдері мен агенттері inference кезінде, яғни модельдер сұрағыңызды ойластырып жатқанда, қажет деректерді табу үшін масштабталатын дерек инфрақұрылымына көбірек сүйенеді. Бұл қызметтердің кейбірі C++ тілінде жазылған: оның жүйені төмен деңгейде басқару мүмкіндігі өнімділікті арттырып, жадты пайдалануды азайтуға көмектеседі. Масштаб өскен сайын мұндай тиімділік маңызды, бірақ C++ тілінде жад қауіпсіздігі жоқ болғандықтан, қателер дұрыс емес немесе жоқ жад адрестеріне жазып, істен шығуға әкелуі мүмкін.

Бірнеше ай бұрын ChatGPT дерек инфрақұрылымымыздың арнайы бөлігі — көптеген дерек плагиндері мен әңгімелер бойынша іздеу үшін маңызды Rockset қызметінің ішінен бірнеше істен шығуды байқадық. Осы істен шығулардың әрқайсысында кәдімгі C++ функциясы аяқталып, кейін жарамсыз адреске қайтқандай көрінді; instruction pointer енді кодқа нұсқамағандықтан, kernel бағдарламаны тоқтатты. Кейде stack frame ішіндегі қайтару адресінің ұясы NULL болатын. Кейде stack pointer CPU регистрінің өзі 8 байтқа ығысқандай көрінді, %rsp қалыпты орындалу ортасында әлдебір жолмен кемітілгендей. Екі жағдайда да істен шығу қайту кезінде болды

Бұл қолданба коды үшін қалыпты істен шығу режимдері емес. Кездейсоқ жазудың тек сақталған қайтару адресіне түсуі мүмкін, бірақ өте екіталай. Inline assembly, setcontext немесе longjmp (біз бұлардың ешқайсысын қолданбаймыз) қатыспай-ақ %rsp-ті 8 байтқа қате туралайтын қате одан да оғаш: компиляцияланған код бұл регистрді тек функцияның прологы мен эпилогында тікелей өзгертеді. Біз (немесе ChatGPT) ойлап тапқан әр гипотезаға қарсы мықты дәлелдер болды, сондықтан бұл қате мүмкін емес сияқты көрінді.

Бір мәселе деп ойлағанымыз ақырында бір уақытта кездейсоқ табылған өзара байланысы жоқ екі қате болып шықты. Біріншісі — бір Azure host-тағы үнсіз аппараттық бүліну: CPU есептеуді дұрыс орындамаған. Екіншісі — GNU libunwind ішіндегі 18 жылдық race condition, кең қолданылатын ашық бастапқы кодты кітапханадағы байқалмай қалған қате.

Бұл жазбада түсініксіз болып көрінген істен шығулардың эпидемиолог сияқты ойлап, барлық істен шығу жағдайлары туралы сапалы деректер жиынын құру арқылы қалай анықтап, түзеткеніміз баяндалады.

Диагностикалаудағы алғашқы әрекет: бірнеше core dump-ты мұқият тексеру

Алдымен, Rockset-ке тереңірек тоқталайық. Бұл — OpenAI ішінде синхрондау коннекторлары сияқты көптеген ішкі қолдану жағдайларына пайдаланатын, іздеу мен нақты уақыттағы аналитикаға арналған cloud-native дерек жүйесі. Rockset-ті OpenAI 2024 жылы сатып алған. Streaming жаңартулар жұмыс кеңістігінің білім базасы индексін өзекті күйде ұстайды, сондықтан ChatGPT сұрақтарға жауап бергенде немесе әрекеттер орындағанда қажет ақпаратты іздей алады.

Rockset-тің орындау қабаты C++ тілінде жазылған. C++ тілі CPU-ға төмен деңгейлі қолжетімділік береді, бұл өнімділік пен тиімділікке пайдалы, бірақ қолданбадағы қателер жарамсыз жадқа қол жеткізуге және segfault-тарға әкелуі мүмкін дегенді білдіреді. Бұларды қадағалау үшін істен шығу болғанда stack trace жазатын folly fatal signal handler-ін қолданамыз, ал сәйкес core dump-тарды (бағдарлама істен шыққан сәттегі күйінің суреті) кейінгі талдау үшін Azure blob storage-қа жүктейміз. Rockset-тің query processing leaf түйіндерінің бәрі репликацияланған, бұл істен шығудың клиентке әсерін азайтады. Дегенмен әрбір segfault сенімділік пен сапа мақсаттарымызға жету үшін түзетілуі тиіс қатені білдіреді.

Бастапқы тәсіліміз бұл core dump-тарды дәстүрлі диагностикалау мәселесі ретінде қарау болды: бірнеше core dump-ты өте мұқият тексеру, гипотеза құру және оларды бір-бірлеп жоққа шығару.

Істен шығулардың көбі DocumentTree::updateDocument деп аталатын method ішінде болды. Бұл істен шығуда updateDocument белгісіз X функциясын шақырғандай, X белсенді кезде stack бүлінгендей, содан соң X executable code емес адреске қайтқандай көрінді. Кейбір жағдайларда X-тің жаңа ғана pop болған frame-і жарамды көрінді, тек сақталған қайтару адресі NULL болды. Басқа жағдайларда stack pointer-дің өзі қате көрінді, бірақ келесі жарамды frame бәрібір updateDocument сияқты еді.

Stack қашан бүлініп жатқанын білмедік, сондықтан іздеу кеңістігі өте үлкен болды. updateDocument — көп inline болатын үлкен method, сондықтан X-ке кандидаттар саны тым көп еді.

Бұл біздің C++ кодымыздағы қате ме? Компилятор немесе байланыстыру мәселесі ме? Runtime кітапханаларымыздың біріндегі проблема ма? Signal delivery немесе context switching маңындағы Linux kernel қатесі ме? Одан да сирек нәрсе ме? Егер бұл кездейсоқ жазу болса, ASAN staging environment оны неге ұстамады?

Мәселенің барлық көріністерін анықтау үшін application-level log-тарды қолдануға тырыстық, бірақ stack-corruption қателерін тек log арқылы жіктеу қиын: log-қа жазылған stack trace-тердің өзі бүлінген немесе жоқ болады. Жалған оң нәтиже де, жалған теріс нәтиже де бермейтін log query құра алмадық. Қосымша core-ларды қолмен қарап, тағы бірнеше мысал таптық, бірақ бұл процесс сенімді деректер жиынын жасауға тым көп еңбек қажет етті.

Тергеудің осы кезеңінде біз аппараттық қатесін (қате түрде) жоққа шығардық, өйткені істен шығуларда бірнеше region мен бірнеше аймақ пен бірнеше жабдық түрі бойынша істен шығуларды көргендіктен, тек бағдарламалық себептерді іздей бердік. Бірнеше күн бойы бір ғана misaligned-%rsp істен шығуына қатты үңіліп, stack пен register мазмұны арқылы істен шығу алдындағы тарихты қайта құрдық. Бұл кейбір ықтимал белгілер берді, бірақ барлық қателердің себебі бір деген бастапқы қорытындымыздан бас тартпағандықтан, тығырықтан шыға алмадық.

Stack-тегі белгілер

Тергеудегі бетбұрысқа өтпес бұрын, core файлдарынан қандай ақпарат алғанымызды түсіндіру маңызды.

Rockset -fno-omit-frame-pointer арқылы compile етіледі, сондықтан белсенді stack frame әрқашан %rbp арқылы қолжетімді, ал caller-лер frame pointer-лердің linked list-ін құрайды.

Linux x86_64 жүйесінде AMD64 System V ABI %rsp астындағы 128 байтты red zone ретінде де резервтейді. Бұл аймақ userspace code үшін қолжетімді және, маңыздысы, ABI келісімінің бөлігі ретінде kernel signal жеткізгенде бұл аймақтың үстінен жазбайтынына кепілдік береді.

Red zone қайтудан кейінгі істен шығуды диагностикалауда негізгі рөл атқарды, өйткені ол қайту алдындағы кейбір ақпаратты сақтайды. SIGSEGV іске қосылғанда, folly fatal signal handler істен шығу болған thread-тің stack-інде орындалады. Енді белсенді емес stack frame-дер (функциялары қайтып кеткендіктен) signal handler арқылы clobber болады, тек соңғы 128 байттан басқасы. Сондықтан «X-тің жаңа ғана pop болған stack frame-і жарамды көрінді, тек return address NULL болды» деп айта аламыз. Red zone белсенді емес frame-дердің бір бөлігін, кейде тек бір inactive frame-нің соңын сақтайды.

Қайтару адрестерінің үстінен жазып, істен шығу тудыруы мүмкін бүлінген stack frame-дерді көрсететін stack диаграммасы.

Қатысқан барлық функциялары өте шағын болған бір misaligned-stack істен шығу таптық. Бұл %rsp салыстырмалы түрде қарапайым функция орындалып жатқанда misaligned болғанын және кейін тағы бірнеше call сәтті өткенін көруге мүмкіндік берді. Бағдарлама белсенді функция ақыры қайтуға тырысқанда ғана істен шығу болды. Бұл code path-тердің ешқайсысы exception, inline assembly, setcontext немесе longjmp қолданбаған; сондықтан stack pointer core көрсеткендей шынымен өзгерсе, userspace code ішіндегі ешбір қисынды қате оны түсіндіре алмады.

Бұл бізді kernel-ді ықтимал себеп ретінде қарастыруға итермеледі.

Rockset signal-дарды көп бағдарламаларға қарағанда жиірек әрі қарқындырақ қолданады. Query execution дерек алмасатын көптеген lightweight task-тарға бөлінеді. Бұл high-QPS workload-тарды тиімді өңдеу үшін маңызды, бірақ әр query бойынша CPU есебін қиындатады, өйткені көптеген query жұмысы бір thread pool-ға multiplex жасалады.

Біздің шешім — coarse_thread_cputime_clock деп атайтын нәрсе: ол clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) мәнін әр task boundary-де sample алуға жеткілікті арзан түрде жуықтайды. timer_create API-ін уақыт өтуінің бірнеше түсінігіне, соның ішінде CPU time жиналуына сүйеніп, periodical signal delivery жоспарлау үшін қолдануға болады. CPU time-ның әр бірнеше миллисекундында signal (SIGUSR2) жеткізілетіндей жоспарлаймыз; сол кезде signal handler thread-local мәнді жаңартады. Көп task орындалып жатқанда coarse clock алға жылжымағанын көрсе де, барлық delta-ларды қосу query үшін нақты CPU time-ның unbiased бағасын береді.

Signal-дарды өте жиі жеткізетіндіктен, context switching немесе signal delivery маңындағы сирек kernel қате ықтимал көрінді. Қате report-тарды, kernel source code-ты және Azure-ге тән kernel patch-терін оқуға уақыт жұмсадық. Stress test-тер жасап көрдік. Қатысы бар сияқты ештеңе таба алмадық.

Сол кезде бір қадам шегініп, басқа тәсілді сынауды ұйғардық.

Дәрігер ме, эпидемиолог па?

Мұндай мәселені диагностикалаудың екі жалпы жолы бар.

Бірі — дәрігер сияқты әрекет ету: бір пациентке назар аударып, көп сынақ жүргізу және егжей-тегжейлі дәлелдер арқылы бір жағдайға диагноз қоюға тырысу.

Екіншісі — эпидемиологқа көбірек ұқсау: бүкіл популяцияға қарап, бір жағдай аша алмайтын заңдылықтар бар-жоғын сұрау. Қате нақты бір релизден басталды ма? Ол бір hardware SKU-мен, бір аймақпен немесе бір kernel нұсқасымен байланысты ма? Ол бір hardware SKU-мен (нақты CPU және server модель), бір region-мен немесе бір kernel version-мен correlate ете ме? Бір syndrome сияқты көрінетін нәрсенің ішінде бірнеше бөлек cluster жасырынып тұр ма?

Біз көбіне дәрігер режимінде болғанбыз. Негізгі бетбұрыс — сапалы population data жинау керек деп шешкеніміз.

Деректерді тазалау

Мәселенің барлық жағдайларын автоматты түрде табуға бұрынғы талпыныстарымыз сәтсіз болды, өйткені log-тар бойынша мәтіндік іздеуге сүйендік. Core dump-тардың өзінде ақпарат әлдеқайда көп, бірақ оларды қолмен талдау масштабталмайтын еді. Core dump-тарды автоматты түрде талдайтын pipeline құруға күш салуды шештік.

ChatGPT‑ге әр core файлдың бастапқы бөлігін жүктеп алып, register-лерді шығаратын, log арқылы белгілі false positive-терді сүзетін және істен шығуды return-to-null, misaligned-stack немесе other деп автоматты түрде белгілейтін скрипт жаздырдық. Содан кейін бұл скриптті өткен жылғы production Rockset core dump-тарының бәріне параллель түрде іске қостық.

Бұл бетбұрыс болды.

Таза деректер жиыны пайда болған бойда корреляциялар бірден көрінді. Бір оғаш қате деп қарап жүргеніміз шын мәнінде істен шығу жағдайларының екі бөлек тобы болып шықты.

Return-to-null core dump-тары көптеген кластер мен географиялық аймаққа таралған еді. Олардың жиілігі соңғы кезде артқан, бірақ нақты басталу күні де, айқын инфрақұрылымдық шекарасы да болмады.

Misaligned-stack істен шығулары мүлде басқаша көрінді. Олардың бәрі бір region-нан шықты, нақты start date болды және ұзақ уақыт жұмыс істеп тұрған түйіндерде ешқашан болмады. Олар бірнеше Azure VM-ді (бұлтта орналасқан virtual machine-дер) қамтыса да, заңдылық бір физикалық машинадағы ақаулы жабдық сол машинаға түскен кез келген VM-ге мәселе тудырып жатқандай көрінді.

Уақыт бойынша кластерлердегі істен шығу жиілігін көрсететін нүктелік график: істен шығулардың көбі 2, 3 және 6-кластерлерде шоғырланған, ал кезең соңында 1-кластерде күрт өсім байқалады.

Сол сәтте біз ойымызда екі қатені араластырып жүргенімізді түсіндік. Екі қатеден келген қарсы мысалдарды араластырып жүргендіктен, біртұтас әрі үйлесімді түсіндірме таба алмадық.

№1 қате: ақаулы host

Kubernetes түйіндерінің таза тізімі мен timestamp-тар қолымызда болған соң, misaligned-stack істен шығуларының ізін бір физикалық host-қа дейін жеткіздік; оны denylist-ке қосу оңай болды.

Бірнеше апта stress testing жасағаннан кейін де, сол host-тағы register corruption-ды бақыланатын ортада reproduce ете алмадық. Алайда проблемалы host қолданыстан шығарылғаннан кейін, misaligned-stack істен шығулары жоғалды.

Ақаулы host-ты алып тастау тұрақты шешім емес, яғни бұл сол мәселенің қайта пайда болуына толық тосқауыл қоймайды. Дегенмен ұқсас мәселе қайталанса, оны оңай анықтап, өңдеу үшін software-ді өзгерте аламыз. Fatal signal handler-імізді register state қосатындай жақсарттық, сондықтан recurrence-ті тек log-тардан анықтай аламыз (core dump қажет емес). Control plane-ді VM-дер әдетте қайта жасалмай, reused болатындай өзгерттік; бұл infrastructure stack деңгейімізде bad-node анықтауды әлдеқайда жеңілдетеді. Сондай-ақ runbook-тарымызды (және team-нің mental модельдерін) осы possibility-ді қамтитындай жаңарттық.

Bad-host істен шығулары бөлінген соң, қалған return-to-null core-ларды түсіндіру әлдеқайда жеңілдеді. Бұрын exception unwinding-ті жоққа шығарғанбыз, өйткені counterexample-дарымыз бар деп ойладық: exception мүлде қолданылмаған code path-тердегі істен шығулар. Бірақ бұл counterexample-дардың бәрі hardware-corruption cluster-інен еді.

Қалған core-ларды осыны ескеріп қайта қарағанда, қорытындымыз дәл керісінше екенін таптық: істен шығулардың бәрі exception unwinding кезінде болып жатқан.

Ерекше жағдайларды өңдеу — басқаруды динамикалық беру

C++ exception тастағанда, runtime оны қай catch block қабылдауы керегін және жолай қай destructor немесе cleanup handler іске қосылуы керегін анықтауы тиіс. Compiler бұл metadata-ны шығарады, бірақ нақты matching runtime кезінде dynamic түрде жүреді.

Exception unwinding-ті throwшақыратын функцияның өзі емес, нәтижесінде жасалған compiled code шақыратын helper function-дар орындайды. Бұл runtime routine-дер stack-ті тексереді, stack-тен табылған функциялар туралы metadata алады, cleanup handler-лер мен catch block-тарды dynamic түрде іздейді, содан кейін control-ды сол орындардың біріне transfer етеді. Control transfer арадағы барлық stack frame-дерді (helper function frame-дерін қоса) unwind етуді қамтиды.

Operationally, бұл normal call және return-ға қарағанда longjmp немесе fiber switch-ке әлдеқайда жақын. Callee-save register-лер, сондай-ақ stack frame register-лері %rbp және %rsp қалпына келтірілуі керек.

Біздің binary C++ exception unwinding орындайтын функциялардың implementation-дарын қамтитын екі library-ге link жасайды: libgcc және GNU libunwind. Dynamic linker GNU libunwind definitions-тарын таңдаған. Бұл бізді таңғалдырды; symbol versioning rule-дарына байланысты libgcc implementation жеңеді деп күткен едік, бірақ running binary-лерді тексеру бұлай емес екенін көрсетті.

Соңғы бір болжамнан бас тарту

Осы кезде жұмыс гипотезамыз өзгерді: бір ғана қате бар деп ойлаған кезде жасаған тағы бір болжамды жұмсарттық.

Мүмкін, біз кәдімгі функцияның NULL-ға қайтуын көрмеген шығармыз. Мүмкін, control transfer болмай тұрып destination instruction pointer NULL болып кеткен unwind transfer-ді — іс жүзінде setcontext-style register restore-ды — көрген шығармыз. Басқаша айтқанда, stack-тегі incorrect return address slot емес, unwind library-ден келген incorrect data.

Бұл мәселені күрт тарылтты. Не GNU libunwind wrong destination state есептеді, не right state есептелгенімен, қолданылмай тұрып бір нәрсе оны corrupt етті.

GNU libunwind source-ын оқып, оның stack-те ucontext_t синтездейтінін, cleanup handler frame-і үшін desired register state-ті толтыратынын, содан кейін сол struct-қа pointer-ді internal assembly routine-ге беретінін таптық: _Ux86_64_setcontext.

Осы кезде бізде барлық бөлшек болды.

Синтезделген ucontext_t _Ux86_64_setcontext сол функция орындалып жатқанда unwind ететін stack frame-дердің бірінде тұрады. _Ux86_64_setcontext %rsp-ті өзгерткеннен кейін, struct active stack-тің бөлігі болмай қалған сәтте, struct-тан оқи ма? Бұл оны біздің жиі келетін SIGUSR2 сияқты signal delivery арқылы clobber болуға осал етер еді.

№2 қате: libunwind қатесі

Жауап — иә.

Міне, біз қолданған GNU libunwind version-ындағы _Ux86_64_setcontext соңғы алты instruction-ы; олар негізінен жадтан destination register-ге load жасайтын mov instruction-дарынан тұрады:

Қарапайым мәтін

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 stack-те allocated ucontext_t-ке point етеді, ал UC_MCONTEXT_* macro-лары белгілі бір register сақталатын fixed offset-ке ғана expand болады.)

Бірінші instruction — race window-дың басы. Ол %rsp-ті active stack-тің жаңа төменгі жағын point ететіндей update етеді. Бұл болған бойда %rdi көрсететін struct active stack-тің (немесе red zone-ның) бөлігі болмайды және kernel үшін енді off-limits емес.

Әдетте бұл мәселе тудырмайды, бірақ signal дәл сол қолайлы (дәлірек айтқанда, қолайсыз) сәтте келсе, kernel signal frame-ді %rsp-128 адресінде құрады. Бұл %rdi нұсқайтын жадтың үстінен жазуы мүмкін.

Егер бұл келесі instruction UC_MCONTEXT_GREGS_RIP(%rdi) оқымай тұрып болса, restored instruction pointer corrupt болуы мүмкін. Біздің істен шығуларда ол NULL болды.

Қате осы жерде еді.

Core dump-тар неге кәдімгі қате қайтару сияқты көрінді

Бұл assembly бізді шатастырған бір бақылауды да түсіндіреді: неге X функциясының алдындағы stack frame-дегі қайтару адресі слотында NULL болды.

setcontext барлық register-лерді, соның ішінде %rdi-ді қалпына келтіру үшін жазылған, сондықтан control transfer-дің соңғы сәтінде UC_MCONTEXT_GREGS_RIP(%rdi) оқу үшін бұл register-ді қолдана алмайды. Оның орнына ол мәнді ертерек оқып, stack-ке сақтайды, тағы бірнеше register-ді қалпына келтіреді, содан кейін сақталған мәнді оқып, control transfer жасау үшін retq қолданады.

Core dump-тарда «функция NULL-ға қайтты» болып көрінген нәрсе шын мәнінде «unwinder stack ішінде мақсатты қайтару адресін синтездеді, бірақ transfer аяқталмай тұрып сол адрес бүлінді» дегенді білдірді. Біз return address слоты тек өз орнында бүлінеді деп ойладық, өйткені (бүлінуге болатын) data return address слотының ішіне әдейі жазылатын орындарды білмедік.

Бір instruction-ға ғана созылатын race window

Бұл қатенің абсурд болып көрінуінің себебі — race window-ының өте тарлығы. Мұндай race condition кезінде сыртқы оқиға, яғни signal, басқа thread орындайтын екі қадамның арасында болуы керек. Бұл қадамдар бір-біріне неғұрлым жақын болса, race condition-нің болу ықтималдығы соғұрлым аз.

Бұл жағдайда осал window тура бір instruction-ға ғана созылады! Signal %rsp өзгергеннен кейін, бірақ келесі instruction %rip-ті жүктемей тұрып жеткізілуі керек. Қазіргі superscalar out-of-order CPU бір cycle ішінде мұндай бірнеше simple instruction орындай алады, сондықтан race window шамамен жүз пикосекунд.

Бұл race-ті тапқанда, алғашқы ойымыз оның байқалған істен шығу rate-ті түсіндіруге тым сирек болуы мүмкін екені болды. Бүкіл fleet бойынша күніне оннан астам return-to-null істен шығу көріп жүрдік. Exception cleanup кезіндегі бір instruction-дық race мұны шынымен түсіндіре ала ма?

Біз Ферми бағалауына жүгіндік. Егер осал window 101010^{-10} секунд шамасында, ал SIGUSR2 CPU time-ның әр 10210^{-2} секунды сайын келсе, онда әр exception cleanup handler немесе catch block race-тен ұтылу ықтималдығы шамамен 10810^{-8} болады.

Rockset exception-дарды ішкі ingest backpressure mechanism бөлігі ретінде қолданады. Бір overloaded host секундына шамамен 10410^{4} exception throw ете алады. Бұл backpressure қолданатын host үшін failure арасындағы mean time 10410^{4} секунд, яғни бірнеше сағатта бір істен шығу дегенді білдіреді. Fleet scale-де бұл байқалған істен шығу жиілігі түсіндіруге толық жеткілікті.

Libunwind қатесі неге дәл қазір көрінді?

GNU libunwind қатені ескі — 18 жылдан асқан, C++ exception unwinding қолдаған алғашқы x86_64 нұсқасында-ақ болған.

Ендеше ол неге қазір көрінді?

Істен шығу rate шамамен қанша exception тасталатынына және қанша signal жеткізілетініне пропорционал. Ол signal handler қанша stack тұтынатынына да тәуелді.

Rockset осы үш өлшемнің бәрінде ерекше. Қалыпты overload control бөлігі ретінде exception-дарды жоғары жиілікпен тастаймыз; coarse_thread_cputime_clock себебінен SIGUSR2-ні әдеттен тыс жиі жеткіземіз; ал биыл merged signal-дарды есепке алу үшін timer_getoverrun шақыруын қосып, SIGUSR2 handler-ін көбірек stack қолданатын еттік.

Соңғы өзгеріс маңызды болған сияқты. Егер handler жеткілікті аз stack қолданса, ол ескірген ucontext_t жад аймағына жетіп, оның үстінен жазбауы мүмкін. Бұл өзгеріске дейін біз мұндай істен шығуларды мүлде байқамадық. Өзгерістен кейін жиілік төмен деңгейде қалды, тек backpressure механизмін қатты жүктеген кейбір қолдану жағдайлары үшін жүктемені арттырғанға дейін.

Басқаша айтқанда, libunwind қатені әрқашан болған, бірақ exception rate, signal rate және handler stack usage көбейтіндісі операциялық тұрғыдан байқалатын шектен жақында ғана асты.

Бұл механизм аппараттық қате мен libunwind қатенінің екеуі де көбіне DocumentTree::updateDocument ішінде істен шығу болуын түсіндіреді. ibunwind істен шығулары осы method жағына қатты ығысқан, өйткені ingest backpressure қолдану үшін exception throw ететін сәтте ол әрқашан active болады. Ол %rsp-misalignment істен шығулары үшін де қатты таңдалды, себебі bad hardware түйіні bulk ingest үшін қолданатын SKU еді, ал ол CPU time-ның басым бөлігін осы method ішінде өткізеді.

Біздің жедел жұмсарту шарамыз GNU libunwind-тен libgcc unwinder-іне ауысу болды. Бұл өз алдына ұтымды шешім еді: libgcc implementation-ы lock contention-ды азайтуға бағытталған көп жұмыстың пайдасын көрген, ал бұл үлкен VM-дерге масштабтағанда маңызды.

Сондай-ақ GNU libunwind-ке дербес reproducer мен fix(жаңа терезеде ашылады)-ті upstream-ге жібердік әрі басқа unwinder-лерде ұқсас мәселе жоқ екенін тексердік.

Популяциялық деңгейдегі диагноздың күші

Бұл диагностикалау жолы бізге dynamic linking, DWARF unwind metadata, Linux signal delivery, System V ABI және C++ exception machinery туралы көптеген нақты егжей-тегжейді үйретті. Бірақ негізгі сабақ бұлардың бәрінен қарапайым болды.

Ең маңызды қадам assembly-ді шебер оқу немесе егжей-тегжейін терең білу емес еді. Ол — сапалы деректер жиынын құру болды. Бұл деректер жиыны болмағанда, біз екі бөлек құбылысты бір оқиғаға біріктіріп, шатасудан логика арқылы шығуға тырысып жүрдік. Дәл әрі толық популяциялық деректер болғанда, мәселенің құрылымы айқын көрінді: бір істен шығу тобы ақаулы host-қа, екіншісі libunwind-тегі race-ке тиесілі еді. Дерек жақсарған сайын debugging жеңілдеді.

Rockset сияқты инфрақұрылымдық жүйелер үшін бұл өте маңызды. Бұл зерттеу терең instrumentation, автоматтандырылған investigation және operational tooling-ті үздіксіз жақсартуға деген ұстанымымызды күшейтті. Сенімділік қателерді болғаннан кейін түзетумен ғана шектелмейді; ол мүмкін емес көрінетін проблемаларды диагностикалауға және шешуге болатын мәселелерге айналдыратын деректерді, workflow-ларды және skill-дерді құру.

Авторлар

By Nathan Bronson және Member of Technical Staff