Langkau ke kandungan utama
OpenAI

30 Jun 2026

Kejuruteraan

Epidemiologi core dump: membaiki pepijat 18 tahun

Menggunakan analisis peringkat populasi untuk menyahpepijat ranap yang rumit dalam infrastruktur data kami.

Memuat…

Model dan ejen OpenAI semakin bergantung pada infrastruktur data berskala untuk mencari data relevan pada masa inferens: ketika model sedang memikirkan soalan anda. Sebahagian perkhidmatan ini ditulis dalam C++, yang kawalan aras rendahnya terhadap sistem membolehkan kami memaksimumkan prestasi dan meminimumkan penggunaan memori. Manfaat kecekapan itu penting apabila kami menskala, tetapi ketiadaan keselamatan memori dalam C++ bermakna pepijat boleh menyebabkan ranap dengan menulis ke alamat memori yang salah atau tidak wujud.

Beberapa bulan lalu kami melihat beberapa ranap dari dalam perkhidmatan Rockset, bahagian tersuai dalam infrastruktur data ChatGPT kami yang penting untuk banyak pemalam data dan carian dalam perbualan. Dalam setiap ranap ini, fungsi C++ biasa kelihatan selesai lalu kembali ke alamat palsu, menyebabkan kernel menghentikan program kerana penuding arahan tidak lagi menunjuk kepada kod. Kadangkala slot alamat kembali dalam bingkai tindanan ialah NULL. Kadangkala daftar CPU penuding tindanan itu sendiri kelihatan tersasar 8 bait, seolah-olah %rsp entah bagaimana dikurangkan di tengah pelaksanaan biasa. Dalam kedua-dua kes, ranap berlaku ketika kembali.

Ini bukan mod kegagalan biasa bagi kod aplikasi. Tulisan tersasar yang tepat mengenai alamat kembali tersimpan memang mungkin, tetapi amat tidak mungkin. Pepijat yang menyengetkan %rsp sebanyak 8 tanpa melibatkan assembly sebaris, setcontext, atau longjmp (semuanya tidak kami gunakan) lebih pelik, kerana kod terkompil hanya melaras daftar itu secara langsung dalam prolog dan epilog fungsi. Setiap hipotesis yang kami (atau ChatGPT) fikirkan mempunyai bukti kuat yang menentangnya, jadi pepijat itu kelihatan mustahil.

Apa yang kami sangka satu masalah akhirnya rupa-rupanya dua pepijat tidak berkaitan yang kebetulan ditemui pada masa sama. Pertama, kerosakan perkakasan senyap pada satu hos Azure, apabila CPU langsung tidak mengira dengan betul. Kedua, keadaan perlumbaan berusia 18 tahun dalam GNU libunwind, pepijat yang tidak disedari dalam pustaka sumber terbuka yang digunakan meluas.

Siaran ini menceritakan cara kami mengenal pasti dan membaiki ranap yang kelihatan tidak dapat dijelaskan dengan berfikir seperti ahli epidemiologi dan membina set data berkualiti tentang seluruh populasi ranap.

Percubaan nyahpepijat pertama: meneliti beberapa core dump dengan teliti

Pertama, mari kita selami Rockset. Ia sistem data natif awan untuk carian dan analitik masa nyata yang kami gunakan bagi banyak kegunaan dalaman di OpenAI, seperti penyambung segerak (Rockset diambil alih oleh OpenAI pada 2024). Kemas kini penstriman digunakan untuk mengekalkan indeks terkini bagi pangkalan pengetahuan ruang kerja supaya ChatGPT boleh mencari maklumat relevan ketika menjawab soalan atau melakukan tindakan.

Lapisan pelaksanaan Rockset ditulis dalam C++. Bahasa C++ menyediakan akses aras rendah kepada CPU, bagus untuk prestasi dan kecekapan, tetapi ini bermakna pepijat aplikasi boleh membawa kepada akses memori tidak sah dan segfault. Untuk menjejakinya, kami menggunakan pengendali isyarat maut folly untuk merekod jejak tindanan apabila ranap berlaku, dan memuat naik core dump yang berkaitan (petikan keadaan program ketika ranap) ke storan blob Azure untuk analisis kemudian. Semua daun pemprosesan pertanyaan Rockset direplikasi, yang meminimumkan kesan ranap kepada klien. Namun, setiap segfault bersamaan dengan pepijat yang perlu dibaiki bagi memenuhi sasaran kebolehpercayaan dan kualiti kami.

Pendekatan awal kami ialah menganggap core ini seperti masalah nyahpepijat biasa: periksa beberapa core dump dengan sangat teliti, bina hipotesis, dan singkirkannya satu demi satu.

Kebanyakan ranap berlaku dalam kaedah yang dipanggil DocumentTree::updateDocument. Dalam ranap ini, updateDocument kelihatan telah memanggil fungsi tidak diketahui X, tindanan rosak ketika X aktif, kemudian X kembali ke alamat yang bukan kod boleh laksana. Dalam sesetengah kes, bingkai X yang baru dikeluarkan kelihatan sah kecuali alamat kembalinya yang tersimpan ialah NULL. Dalam kes lain, penuding tindanan itu sendiri kelihatan salah, tetapi bingkai sah seterusnya masih nampaknya updateDocument.

Kami tidak tahu bila tindanan mula rosak, jadi ruang carian menjadi sangat besar. updateDocument ialah kaedah besar yang banyak diinliningkan, jadi bilangan calon untuk X terlalu banyak.

Adakah ini pepijat dalam kod C++ kami? Isu pengkompil atau pautan? Masalah dalam salah satu pustaka runtime kami? Pepijat kernel Linux sekitar penghantaran isyarat atau penukaran konteks? Sesuatu yang lebih jarang? Jika ini tulisan tersasar, mengapa persekitaran pementasan ASAN kami tidak menangkapnya?

Kami cuba menggunakan log peringkat aplikasi untuk mengenal pasti semua kejadian masalah ini, tetapi pepijat kerosakan tindanan sukar dikelaskan daripada log sahaja kerana jejak tindanan yang dilog itu sendiri rosak atau hilang. Kami tidak dapat membina pertanyaan log yang bebas daripada positif palsu dan negatif palsu. Kami memeriksa lebih banyak core secara manual dan menemui beberapa contoh tambahan, tetapi proses itu terlalu memakan tenaga untuk menghasilkan set data yang boleh dipercayai.

Pada tahap siasatan ini, kami (tersilap) menolak pepijat perkakasan, kerana kami melihat ranap merentas beberapa rantau dan jenis perkakasan, jadi kami masih mencari punca perisian sahaja. Selama beberapa hari, kami menyelami satu ranap %rsp tersalah jajaran, membina semula sejarah pra-ranap menggunakan kandungan tindanan dan daftar. Ini menghasilkan beberapa petunjuk, tetapi kerana kami tidak melepaskan kesimpulan awal bahawa semua pepijat berpunca sama, ia tidak membantu kami bergerak.

Petunjuk daripada tindanan

Sebelum sampai ke titik perubahan siasatan kami, penting untuk menjelaskan jenis maklumat yang kami ekstrak daripada fail core.

Rockset dikompil dengan -fno-omit-frame-pointer, jadi bingkai tindanan aktif sentiasa boleh dicapai melalui %rbp, dan pemanggil membentuk senarai berpaut penuding bingkai.

Pada Linux x86_64, AMD64 System V ABI juga menyimpan 128 bait di bawah %rsp sebagai zon merah. Rantau itu tersedia untuk kod ruang pengguna dan, yang penting, kernel berjanji tidak menimpanya ketika menghantar isyarat, sebagai sebahagian kontrak ABI.

Red zone menjadi pusat nyahpepijat kami bagi ranap selepas kembali, kerana ia mengekalkan sebahagian maklumat sebelum kembali. Apabila SIGSEGV dicetuskan, pengendali isyarat maut folly berjalan pada tindanan utas yang ranap. Bingkai tindanan yang tidak lagi aktif (kerana fungsinya telah kembali) akan ditimpa oleh pengendali isyarat, kecuali 128 bait terakhir. Sebab itu kami boleh berkata seperti “bingkai tindanan X yang baru dikeluarkan kelihatan sah, kecuali alamat kembali NULL.” Red zone mengekalkan sebahagian bingkai tidak aktif, atau kadangkala hanya hujung satu bingkai tidak aktif.

Rajah tindanan yang menunjukkan bingkai tindanan rosak yang boleh menimpa alamat kembali dan menyebabkan ranap.

Kami menemui satu ranap tindanan tersalah jajaran yang melibatkan fungsi-fungsi sangat kecil. Ini membolehkan kami melihat bahawa %rsp menjadi tersalah jajaran semasa pelaksanaan fungsi yang agak mudah, dan lebih banyak panggilan berjaya selepas itu. Program hanya ranap apabila fungsi aktif akhirnya cuba kembali. Tiada laluan kod itu menggunakan pengecualian, himpunan sebaris, setcontext, atau longjmp, jadi jika penuding tindanan benar-benar berubah seperti yang dicadangkan core, tiada pepijat munasabah dalam kod ruang pengguna dapat menjelaskannya.

Itu menolak kami ke arah kernel.

Rockset menggunakan isyarat dengan lebih agresif daripada kebanyakan program. Pelaksanaan pertanyaan dipecahkan kepada banyak tugas ringan yang bertukar data. Ini penting untuk mengendalikan beban kerja QPS tinggi dengan cekap, tetapi menjadikan perakaunan CPU per pertanyaan janggal kerana kerja banyak pertanyaan dimultiplekskan ke kolam utas yang sama.

Penyelesaian kami ialah sesuatu yang kami panggil coarse_thread_cputime_clock, yang menganggar clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) dengan cukup murah untuk disampel pada setiap sempadan tugas. API timer_create boleh digunakan untuk menjadualkan penghantaran isyarat berkala berdasarkan beberapa pengertian peredaran masa, termasuk pengumpulan masa CPU. Kami menjadualkan isyarat (SIGUSR2) dihantar setiap beberapa milisaat masa CPU, lalu pengendali isyarat mengemas kini nilai setempat utas. Walaupun banyak tugas tidak melihat jam kasar bergerak semasa dilaksanakan, menjumlahkan semua delta menghasilkan anggaran tidak berat sebelah bagi masa CPU sebenar untuk pertanyaan.

Kerana kami menghantar isyarat begitu kerap, pepijat kernel yang jarang berlaku sekitar penukaran konteks atau penghantaran isyarat terasa munasabah. Kami meluangkan masa membaca laporan pepijat, kod sumber kernel, dan tampalan kernel khusus Azure. Kami mencuba ujian tekanan. Kami tidak dapat menemui apa-apa yang kelihatan berkaitan.

Pada ketika itu kami memutuskan untuk berundur seketika dan mencuba pendekatan lain.

Doktor atau ahli epidemiologi?

Ada dua cara umum untuk menyahpepijat masalah seperti ini.

Satu ialah bertindak seperti doktor: fokus pada seorang pesakit, jalankan banyak ujian, dan cuba mendiagnosis satu kes daripada bukti terperinci.

Satu lagi ialah bertindak lebih seperti ahli epidemiologi: lihat seluruh populasi dan tanya sama ada ada pola yang tidak dapat didedahkan oleh satu kes. Adakah pepijat bermula pada keluaran tertentu? Adakah ia berkorelasi dengan satu SKU perkakasan (model CPU dan pelayan tertentu), satu rantau, atau satu versi kernel? Adakah beberapa kelompok berbeza tersembunyi dalam sesuatu yang kelihatan seperti satu sindrom?

Sebelum ini, kami kebanyakannya berfikir seperti doktor. Perubahan utama berlaku apabila kami memutuskan bahawa kami perlu mengumpulkan data populasi yang berkualiti tinggi.

Membersihkan data

Percubaan kami sebelum ini untuk mencari semua kejadian masalah secara automatik gagal kerana kami cuba menggunakan carian teks pada log. Core dump itu sendiri mempunyai lebih banyak maklumat, tetapi melihatnya secara manual tidak dapat diskalakan. Kami memutuskan untuk melaburkan usaha membina saluran paip yang boleh menganalisis core dump secara automatik.

Kami meminta ChatGPT menulis skrip yang memuat turun awalan setiap fail core, mengekstrak daftar, menapis positif palsu yang diketahui menggunakan log, dan melabel ranap secara automatik sebagai return-to-null, misaligned-stack, atau lain-lain. Kemudian kami menjalankan skrip itu secara selari pada setiap core dump Rockset produksi daripada tahun sebelumnya.

Inilah titik perubahannya.

Sebaik sahaja kami mempunyai set data yang bersih, korelasi muncul serta-merta. Apa yang kami anggap satu pepijat pelik sebenarnya dua populasi ranap yang berasingan.

Core return-to-null tersebar merentas banyak kelompok dan rantau geografi. Kekerapannya meningkat baru-baru ini, tetapi tiada tarikh mula yang jelas dan tiada sempadan infrastruktur yang bersih.

Ranap misaligned-stack kelihatan sama sekali berbeza. Semuanya datang dari satu rantau, mempunyai tarikh mula yang jelas, dan tidak pernah berlaku pada nod yang telah berjalan lama. Walaupun melibatkan beberapa VM Azure (mesin maya yang dihoskan di awan), polanya kelihatan seperti satu mesin fizikal dengan perkakasan rosak yang menimbulkan masalah kepada mana-mana VM yang kebetulan ditempatkan padanya.

Plot titik kadar ranap mengikut kelompok dari masa ke masa, menunjukkan kebanyakan ranap tertumpu pada kelompok 2, 3 dan 6, dengan lonjakan pada kelompok 1 hampir di hujung tempoh.

Itulah saat kami sedar kami telah mencampuradukkan dua pepijat dalam fikiran. Kerana kami mencampurkan contoh balas daripada kedua-dua pepijat, kami tidak dapat menemukan satu penjelasan yang koheren.

Pepijat #1: hos rosak

Dengan senarai bersih nod Kubernetes dan cap masa, kami dapat menjejak ranap tindanan tersalah jajaran kembali kepada satu hos fizikal, yang mudah dimasukkan ke senarai sekat.

Kami tidak dapat menghasilkan semula kerosakan daftar pada hos itu dalam persekitaran terkawal, walaupun selepas beberapa minggu ujian tekanan. Namun, sebaik sahaja hos bermasalah itu dikeluarkan daripada perkhidmatan, ranap tindanan tersalah jajaran hilang.

Mengalih keluar hos rosak bukan penyelesaian kekal, dalam erti kata ia tidak mencegah kejadian baharu masalah yang sama. Namun, kami boleh mengubah perisian supaya jika isu serupa berulang, ia mudah dikesan dan dikendalikan. Kami menambah baik pengendali isyarat maut agar menyertakan keadaan daftar supaya kami boleh mengesan pengulangan hanya daripada log (tanpa perlu core dump). Kami mengubah satah kawalan supaya VM biasanya digunakan semula dan bukannya dikitar semula, yang menjadikan pengesanan nod rosak jauh lebih mudah pada lapisan infrastruktur kami. Kami juga mengemas kini runbook kami (dan model mental pasukan kami) untuk memasukkan kemungkinan ini.

Dengan ranap hos rosak diasingkan, core return-to-null yang tinggal menjadi jauh lebih mudah untuk difikirkan. Sebelum ini kami menolak unwinding pengecualian kerana menyangka kami ada contoh balas: ranap dalam laluan kod yang pasti tidak menggunakan pengecualian. Tetapi semua contoh balas itu datang daripada kelompok kerosakan perkakasan.

Apabila kami meneliti semula core yang tinggal dengan perkara itu dalam fikiran, kami mendapati kesimpulan ini tepat terbalik: semua ranap berlaku semasa unwinding pengecualian.

Pengendalian pengecualian ialah pemindahan kawalan dinamik

Apabila C++ melontar pengecualian, runtime perlu mengetahui blok catch mana yang patut menerimanya dan destruktor atau pengendali pembersihan mana yang perlu berjalan sepanjang jalan. Pengkompil memancarkan metadata ini, tetapi pemadanan sebenar berlaku secara dinamik pada runtime.

Unwinding pengecualian sebenarnya tidak dilakukan oleh fungsi yang memanggil throw, tetapi oleh fungsi pembantu yang dipanggil oleh kod terkompil yang terhasil. Rutin runtime itu memeriksa tindanan, mengambil metadata tentang fungsi yang ditemui pada tindanan, mencari pengendali pembersihan dan blok catch secara dinamik, lalu memindahkan kawalan ke salah satu lokasi itu. Memindahkan kawalan termasuk membuka semua bingkai tindanan di antaranya (termasuk bingkai fungsi pembantu).

Secara operasi, ini jauh lebih dekat kepada longjmp atau pertukaran fiber daripada panggilan dan kembali biasa. Daftar callee-save mesti dipulihkan, begitu juga daftar bingkai tindanan %rbp dan %rsp.

Binari kami memaut kepada dua pustaka yang mengandungi pelaksanaan fungsi untuk melakukan unwinding pengecualian C++: libgcc dan GNU libunwind. Definisi GNU libunwind ialah yang dipilih oleh pemaut dinamik. Itu mengejutkan kami; kami menjangka pelaksanaan libgcc akan menang kerana peraturan pemversian simbol; namun, pemeriksaan binari yang sedang berjalan menunjukkan sebaliknya.

Membatalkan satu andaian terakhir

Pada tahap ini hipotesis kerja kami berubah, apabila kami melonggarkan satu lagi andaian yang dibuat ketika kami menyangka hanya ada satu pepijat.

Mungkin yang kami lihat bukan fungsi biasa kembali ke NULL. Mungkin yang kami lihat ialah pemindahan unwind—secara efektif pemulihan daftar gaya setcontext—dengan penuding arahan destinasi menjadi NULL sebelum kawalan dipindahkan. Dengan kata lain, data salah daripada pustaka unwind, bukan slot alamat kembali yang salah pada tindanan.

Itu mengecilkan masalah dengan ketara. Sama ada GNU libunwind mengira keadaan destinasi yang salah, atau ia mengira keadaan yang betul dan sesuatu merosakkannya sebelum dapat digunakan.

Kami membaca sumber GNU libunwind dan mendapati ia mensintesis ucontext_t pada tindanan, mengisi keadaan daftar yang diingini untuk bingkai pengendali pembersihan, lalu menyerahkan penuding kepada struct itu kepada rutin assembly dalaman: _Ux86_64_setcontext.

Pada ketika ini kami mempunyai semua kepingannya.

ucontext_t tersintesis itu berada dalam salah satu bingkai tindanan yang di-unwind oleh _Ux86_64_setcontext, semasa fungsi itu dilaksanakan. Adakah _Ux86_64_setcontext membaca daripada struct selepas ia mengubah %rsp, ketika struct itu bukan lagi sebahagian tindanan aktif? Itu akan menjadikannya rentan ditimpa oleh penghantaran isyarat, seperti SIGUSR2 kami yang kerap.

Pepijat #2: pepijat libunwind

Jawapannya ya.

Berikut ialah enam arahan terakhir _Ux86_64_setcontext dalam versi GNU libunwind yang kami gunakan, kebanyakannya arahan mov yang memuatkan daripada memori ke daftar destinasi:

Teks Biasa

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 menunjuk kepada ucontext_t yang diperuntukkan pada tindanan, dan makro UC_MCONTEXT_* hanya berkembang kepada ofset tetap tempat daftar tertentu disimpan.)

Arahan pertama ialah permulaan tetingkap perlumbaan. Ia mengemas kini %rsp supaya menunjuk kepada dasar baharu tindanan aktif. Sebaik sahaja ini berlaku, struct yang ditunjuk oleh %rdi bukan lagi sebahagian tindanan aktif (atau red zone), dan ia tidak lagi terlarang kepada kernel.

Biasanya ini tidak menimbulkan masalah, tetapi jika isyarat tiba tepat pada detik yang betul (salah?), kernel akan membina bingkai isyarat pada %rsp-128. Itu boleh menimpa memori yang ditunjuk oleh %rdi.

Jika itu berlaku sebelum arahan seterusnya membaca UC_MCONTEXT_GREGS_RIP(%rdi), penuding arahan yang dipulihkan boleh rosak. Dalam ranap kami, ia menjadi NULL.

Itulah pepijatnya.

Mengapa core menyamar sebagai pulangan buruk biasa

Assembly ini juga menjelaskan satu pemerhatian yang mengelirukan kami: mengapa fungsi X mempunyai NULL dalam slot alamat kembali bingkai tindanan sebelumnya.

setcontext ditulis untuk memulihkan semua daftar, termasuk %rdi, jadi ia tidak boleh menggunakan daftar itu untuk membaca UC_MCONTEXT_GREGS_RIP(%rdi) pada detik akhir pemindahan kawalan. Sebaliknya, ia membaca nilai itu lebih awal, menyimpannya ke tindanan, memulihkan beberapa daftar lagi, kemudian menggunakan retq untuk membaca nilai tersimpan dan memindahkan kawalan.

Apa yang kelihatan dalam core seperti “fungsi kembali ke NULL” sebenarnya ialah “unwinder mensintesis alamat kembali sasaran pada tindanan, tetapi sasaran itu rosak sebelum pemindahan selesai.” Kami menganggap kerosakan slot alamat kembali mesti berlaku di tempatnya, kerana kami tidak tahu ada tempat data (yang boleh rosak) sengaja ditulis ke slot alamat kembali.

Tetingkap perlumbaan satu arahan

Apa yang menjadikan pepijat ini terasa tidak masuk akal ialah betapa sempitnya tetingkap perlumbaan ini. Dalam keadaan perlumbaan seperti ini, peristiwa luaran (isyarat) perlu berlaku di antara dua langkah yang diambil oleh utas lain. Semakin dekat langkah-langkah itu, semakin kecil kemungkinan keadaan perlumbaan berlaku.

Dalam kes ini, tetingkap rentannya benar-benar selebar satu arahan! Isyarat mesti dihantar selepas %rsp diubah, tetapi sebelum arahan seterusnya memuatkan %rip. Beberapa arahan mudah seperti ini boleh dijalankan setiap kitaran pada CPU moden superskalar luar tertib, jadi tetingkap perlumbaan kira-kira seratus pikosaat.

Apabila kami menemui perlumbaan ini, reaksi pertama kami ialah ia pasti terlalu jarang untuk menjelaskan kadar ranap yang diperhatikan. Kami melihat lebih sedozen ranap return-to-null sehari di seluruh fleet. Bolehkah perlumbaan satu arahan semasa pembersihan pengecualian benar-benar menjelaskan semua itu?

Kami beralih kepada anggaran Fermi. Jika tetingkap rentan berada pada orde 101010^{-10} saat dan SIGUSR2 tiba setiap 10210^{-2} saat masa CPU, maka setiap pengendali pembersihan pengecualian atau blok catch mempunyai kira-kira kebarangkalian 10810^{-8} untuk kalah perlumbaan.

Rockset menggunakan pengecualian sebagai sebahagian mekanisme backpressure ingest dalaman. Satu hos terlebih beban boleh melontar pada orde 10410^{4} pengecualian sesaat. Ini bermakna purata masa antara kegagalan bagi hos yang menggunakan backpressure ialah 10410^{4} saat, atau satu ranap setiap beberapa jam. Pada skala fleet, itu lebih daripada cukup untuk menjelaskan kekerapan ranap yang diperhatikan.

Mengapa pepijat libunwind muncul sekarang?

Pepijat GNU libunwind ini lama—lebih 18 tahun, wujud dalam versi x86_64 pertama yang menyokong unwinding pengecualian C++.

Jadi mengapa ia muncul sekarang?

Kadar ranap lebih kurang berkadar dengan bilangan pengecualian yang dilontar dan isyarat yang dihantar. Ia juga bergantung pada jumlah tindanan yang digunakan pengendali isyarat.

Rockset luar biasa pada ketiga-tiga paksi. Kami melontar pengecualian pada kadar tinggi sebagai sebahagian kawalan beban lampau biasa; kami menghantar SIGUSR2 dengan luar biasa kerap kerana coarse_thread_cputime_clock; dan awal tahun ini kami membuat pengendali SIGUSR2 menggunakan lebih banyak tindanan dengan menambah panggilan kepada timer_getoverrun, supaya kami boleh mengira isyarat yang digabungkan.

Perubahan terakhir itu nampaknya penting. Jika pengendali menggunakan tindanan yang cukup sedikit, ia mungkin tidak mencapai dan menimpa memori ucontext_t yang lapuk. Sebelum perubahan itu, kami langsung tidak melihat ranap ini. Selepas perubahan itu, kadarnya kekal rendah sehingga kami menaikkan beban untuk beberapa kegunaan yang menekan mekanisme backpressure.

Dengan kata lain, pepijat libunwind sentiasa ada, tetapi hasil darab kadar pengecualian, kadar isyarat, dan penggunaan tindanan pengendali kami baru-baru ini sahaja melepasi ambang hingga kelihatan secara operasi.

Mekanisme ini juga menjelaskan kebetulan bahawa pepijat perkakasan dan pepijat libunwind kebanyakannya ranap di dalam DocumentTree::updateDocument. Ranap daripada libunwind sangat berat ke arah kaedah ini, kerana ia sentiasa aktif pada titik kami melontar pengecualian untuk menggunakan backpressure ingest. Ia juga sangat terpilih untuk ranap salah jajaran %rsp kerana nod perkakasan rosak itu daripada SKU yang kami gunakan untuk ingest pukal, yang menghabiskan majoriti masa CPU dalam kaedah itu.

Mitigasi segera kami ialah beralih daripada GNU libunwind kepada unwinder libgcc. Itu sendiri pertukaran yang baik: pelaksanaan libgcc telah mendapat manfaat daripada banyak kerja untuk mengurangkan pertelingkahan kunci, yang penting ketika menskala ke VM besar.

Kami juga menghantar ke hulu reproducer serba lengkap dan pembaikan(dibuka dalam tetingkap baru) kepada GNU libunwind, dan mengesahkan bahawa unwinder lain tidak mempunyai isu serupa.

Kuasa diagnosis peringkat populasi

Perjalanan nyahpepijat ini banyak mengajar kami tentang butiran khusus pemautan dinamik, metadata unwind DWARF, penghantaran isyarat Linux, System V ABI, dan jentera pengecualian C++. Tetapi pelajaran utamanya lebih mudah daripada semua itu.

Langkah paling penting bukanlah pembacaan assembly yang bijak atau pengetahuan mendalam tentang butiran. Ia ialah membina set data berkualiti tinggi. Tanpa set data ini, kami mencampurkan dua fenomena berbeza menjadi satu cerita dan cuba berfikir keluar daripada kekeliruan itu. Sebaik sahaja kami mempunyai data populasi yang tepat dan lengkap, struktur masalah menjadi jelas: satu populasi ranap milik hos rosak, dan satu lagi milik perlumbaan dalam libunwind. Apabila data menjadi lebih baik, nyahpepijat menjadi lebih mudah.

Bagi sistem infrastruktur seperti Rockset, itu sangat penting. Siasatan ini mengukuhkan komitmen kami terhadap instrumentasi mendalam, siasatan automatik, dan penambahbaikan berterusan pada alat operasi kami. Kebolehpercayaan bukan sekadar membaiki pepijat selepas ia berlaku—ia tentang membina data, aliran kerja, dan kemahiran yang menukar masalah mustahil menjadi masalah yang boleh didiagnosis dan diselesaikan.

Pengarang

By Nathan Bronson, Member of Technical Staff