Dalam artikel ini, saya akan berkongsi pendekatan saya untuk menjejak dan membetulkan penggunaan memori yang tinggi dalam Node.js.
Baru-baru ini saya mendapat tiket dengan tajuk "baiki isu kebocoran memori di perpustakaan x". Penerangan termasuk papan pemuka Datadog yang menunjukkan sedozen perkhidmatan yang mengalami penggunaan memori yang tinggi dan akhirnya ranap dengan ralat OOM (kehabisan ingatan), dan semuanya mempunyai perpustakaan x yang sama.
Saya telah diperkenalkan baru-baru ini kepada pangkalan kod (<2 minggu) yang menjadikan tugasan itu mencabar dan juga bernilai dikongsi.
Saya mula bekerja dengan dua maklumat:
Di bawah ialah papan pemuka yang dipautkan ke tiket:
Perkhidmatan sedang berjalan pada Kubernetes dan jelas sekali bahawa perkhidmatan mengumpul memori dari semasa ke semasa sehingga mereka mencapai had memori, ranap (menuntut semula ingatan) dan bermula semula.
Dalam bahagian ini, saya akan berkongsi cara saya menangani tugasan yang sedang dijalankan, mengenal pasti punca penggunaan memori yang tinggi dan kemudian membetulkannya.
Memandangkan saya masih baru dalam pangkalan kod, saya mula-mula ingin memahami kod, perkara yang dilakukan oleh perpustakaan berkenaan dan cara ia sepatutnya digunakan, dengan harapan dengan proses ini lebih mudah untuk mengenal pasti masalah. Malangnya, tiada dokumentasi yang betul tetapi daripada membaca kod dan mencari cara perkhidmatan menggunakan perpustakaan, saya dapat memahami intipatinya. Ia adalah perpustakaan yang melilit aliran redis dan mendedahkan antara muka yang mudah untuk pengeluaran dan penggunaan acara. Selepas menghabiskan satu setengah hari membaca kod, saya tidak dapat memahami semua butiran dan cara data mengalir disebabkan oleh struktur dan kerumitan kod (banyak warisan kelas dan rxjs yang saya tidak biasa).
Jadi saya memutuskan untuk berhenti membaca dan cuba mengesan masalah sambil memerhati kod dalam tindakan dan mengumpul data telemetri.
Memandangkan tiada data pemprofilan tersedia (cth. pemprofilan berterusan) yang akan membantu saya menyiasat lebih lanjut, saya memutuskan untuk meniru isu tersebut secara setempat dan cuba menangkap profil memori.
Saya menemui beberapa cara untuk menangkap profil memori dalam Node.js:
Tanpa petunjuk tentang tempat untuk mencari, saya memutuskan untuk menjalankan bahagian yang saya fikir adalah bahagian paling "intensif data" perpustakaan, pengeluar dan pengguna aliran redis. Saya membina dua perkhidmatan ringkas yang akan menghasilkan dan menggunakan data daripada aliran redis dan saya meneruskan dengan menangkap profil memori dan membandingkan hasilnya dari semasa ke semasa. Malangnya, selepas beberapa jam menghasilkan beban kepada perkhidmatan dan membandingkan profil, saya tidak dapat melihat sebarang perbezaan dalam penggunaan memori dalam mana-mana dua perkhidmatan, semuanya kelihatan normal. Perpustakaan itu mendedahkan sekumpulan antara muka yang berbeza dan cara berinteraksi dengan aliran redis. Ia menjadi jelas kepada saya bahawa ia akan menjadi lebih rumit daripada yang saya jangkakan untuk meniru isu itu, terutamanya dengan pengetahuan khusus domain terhad saya tentang perkhidmatan sebenar.
Jadi persoalannya ialah, bagaimana saya boleh mencari masa dan keadaan yang sesuai untuk menangkap kebocoran memori?
Seperti yang dinyatakan sebelum ini, cara paling mudah dan paling mudah untuk menangkap profil ingatan ialah melakukan pemprofilan berterusan pada perkhidmatan sebenar yang terjejas, pilihan yang saya tidak ada. Saya mula menyiasat cara sekurang-kurangnya memanfaatkan perkhidmatan pementasan kami (mereka menghadapi penggunaan memori tinggi yang sama) yang membolehkan saya menangkap data yang saya perlukan tanpa usaha tambahan.
Saya mula mencari cara untuk menyambungkan Chrome DevTools kepada salah satu pod yang sedang berjalan dan menangkap petikan timbunan dari semasa ke semasa. Saya tahu kebocoran memori berlaku dalam pementasan, jadi jika saya dapat menangkap data itu, saya berharap saya akan dapat melihat sekurang-kurangnya beberapa titik panas. Saya terkejut, ada cara untuk melakukannya.
Proses untuk melakukan ini
kubectl exec -it <nodejs-pod-name> -- kill -SIGUSR1 <node-process-id> </p> <p><em>Lagi tentang isyarat Node.js di Acara Isyarat</em></p> <p>Jika berjaya, anda sepatutnya melihat log daripada perkhidmatan anda:<br> </p> <pre class="brush:php;toolbar:false">Debugger listening on ws://127.0.0.1:9229/.... For help, see: https://nodejs.org/en/docs/inspector
kubectl port-forward <nodejs-pod-name> 9229
jika tidak, maka pastikan tetapan penemuan sasaran anda disediakan dengan betul
Kini anda boleh mula merakam syot kilat lebih masa (tempoh bergantung pada masa yang diperlukan untuk kebocoran memori berlaku) dan membandingkannya. Chrome DevTools menyediakan cara yang sangat mudah untuk melakukan ini.
Anda boleh mendapatkan lebih banyak maklumat tentang petikan memori dan Alat Pembangun Chrome di petikan timbunan rekod
Apabila membuat syot kilat, semua kerja lain dalam urutan utama anda dihentikan. Bergantung pada kandungan timbunan, ia mungkin mengambil masa lebih daripada satu minit. Syot kilat terbina dalam ingatan, jadi ia boleh menggandakan saiz timbunan, mengakibatkan mengisi keseluruhan memori dan kemudian ranap apl.
Jika anda akan mengambil gambar timbunan dalam pengeluaran, pastikan proses yang anda ambil daripadanya boleh ranap tanpa menjejaskan ketersediaan aplikasi anda.
Daripada dokumen Node.js
Jadi kembali kepada kes saya, memilih dua syot kilat untuk membandingkan dan mengisih mengikut delta, saya mendapat apa yang anda boleh lihat di bawah.
Kita dapat melihat delta positif terbesar berlaku pada pembina rentetan yang bermakna perkhidmatan itu telah mencipta banyak rentetan antara dua syot kilat tetapi ia masih digunakan. Sekarang persoalannya ialah di mana ia dicipta dan siapa yang merujuknya. Nasib baik syot kilat yang ditangkap mengandungi maklumat ini juga dipanggil Retainers.
Semasa menggali gambar dan senarai rentetan yang tidak pernah susut, saya melihat corak rentetan yang menyerupai id. Mengklik padanya, saya dapat melihat objek rantai yang merujuknya - aka Retainers. Ia adalah tatasusunan yang dipanggil sentEvents daripada nama kelas yang boleh saya kenali daripada kod perpustakaan. Tadaaa kita ada penyebabnya, satu-satunya senarai id yang semakin meningkat yang pada ketika ini saya andaikan tidak pernah dikeluarkan. Saya merakam sekumpulan gambar lebih masa dan ini adalah satu-satunya tempat yang terus muncul semula sebagai hotspot dengan delta positif yang besar.
Dengan maklumat ini, daripada cuba memahami kod sepenuhnya, saya perlu menumpukan pada tujuan tatasusunan, apabila ia diisi dan apabila dibersihkan. Terdapat satu tempat di mana kod itu menolak item ke tatasusunan dan satu lagi tempat kod itu mengeluarkannya yang mengecilkan skop pembetulan.
Adalah selamat untuk mengandaikan bahawa tatasusunan tidak dikosongkan apabila sepatutnya. Melangkau butiran kod, apa yang pada asasnya berlaku ialah ini:
Bolehkah anda melihat ke mana arahnya? ? Apabila perkhidmatan menggunakan perpustakaan hanya untuk menghasilkan acara, sentEvents masih akan diisi dengan semua acara tetapi tiada laluan kod (pengguna) untuk mengosongkannya.
Saya menampal kod untuk hanya menjejak acara pada pengeluar, mod pengguna dan digunakan untuk pementasan. Walaupun dengan beban pementasan adalah jelas bahawa tampung membantu mengurangkan penggunaan memori yang tinggi dan tidak memperkenalkan sebarang regresi.
Apabila tampung digunakan pada pengeluaran, penggunaan memori telah dikurangkan secara drastik dan kebolehpercayaan perkhidmatan telah dipertingkatkan (tiada lagi OOM).
Kesan sampingan yang bagus ialah pengurangan 50% dalam bilangan pod yang diperlukan untuk mengendalikan trafik yang sama.
Ini adalah peluang pembelajaran yang hebat untuk saya mengenai penjejakan isu memori dalam Node.js dan seterusnya membiasakan diri saya dengan alatan yang tersedia.
Saya fikir adalah lebih baik untuk tidak memikirkan butiran setiap alat kerana ia patut diberi jawatan berasingan, tetapi saya harap ini adalah titik permulaan yang baik untuk sesiapa sahaja yang berminat untuk mengetahui lebih lanjut tentang topik ini atau menghadapi isu yang serupa.
Atas ialah kandungan terperinci Menjejaki penggunaan memori yang tinggi dalam Node.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!