Node.js: mengelola memori yang tersedia untuk aplikasi yang berjalan dalam wadah

Saat menjalankan aplikasi Node.js dalam wadah Docker, pengaturan memori tradisional tidak selalu berfungsi seperti yang diharapkan. Bahannya, terjemahan yang kami terbitkan hari ini, didedikasikan untuk menemukan jawaban atas pertanyaan mengapa demikian. Ini juga akan memberikan rekomendasi praktis untuk mengelola memori yang tersedia untuk aplikasi Node.js yang berjalan dalam wadah.



Ulasan rekomendasi


Misalkan aplikasi Node.js berjalan dalam wadah dengan batas memori yang ditetapkan. Jika kita berbicara tentang Docker, maka opsi --memory dapat digunakan untuk mengatur batas ini. Hal serupa mungkin terjadi ketika bekerja dengan sistem orkestrasi wadah. Dalam hal ini, disarankan bahwa ketika memulai aplikasi Node.js, gunakan opsi --max-old-space-size . Ini memungkinkan Anda untuk menginformasikan platform tentang berapa banyak memori yang tersedia untuk itu, dan juga mempertimbangkan fakta bahwa jumlah ini harus kurang dari batas yang ditetapkan pada tingkat wadah.

Ketika aplikasi Node.js berjalan di dalam wadah, atur kapasitas memori yang tersedia untuknya sesuai dengan nilai puncak penggunaan memori aktif oleh aplikasi. Ini dilakukan jika batas memori kontainer dapat dikonfigurasi.

Sekarang mari kita bicara tentang masalah menggunakan memori dalam wadah secara lebih rinci.

Batas Memori Docker


Secara default, wadah tidak memiliki batasan sumber daya dan dapat menggunakan sebanyak mungkin memori yang diizinkan oleh sistem operasi. Perintah docker run memiliki opsi baris perintah yang memungkinkan Anda menetapkan batas terkait penggunaan memori atau sumber daya prosesor.

Perintah peluncuran kontainer mungkin terlihat seperti ini:

 docker run --memory <x><y> --interactive --tty <imagename> bash 

Harap perhatikan hal berikut:

  • x adalah batas jumlah memori yang tersedia untuk wadah, dinyatakan dalam satuan y .
  • y dapat mengambil nilai b (byte), k (kilobyte), m (megabita), g (gigabita).

Berikut adalah contoh perintah peluncuran kontainer:

 docker run --memory 1000000b --interactive --tty <imagename> bash 

Di sini, batas memori diatur ke 1000000 byte.

Untuk memeriksa batas memori yang ditetapkan pada level wadah, Anda dapat, dalam wadah, jalankan perintah berikut:

 cat /sys/fs/cgroup/memory/memory.limit_in_bytes 

Mari kita bicara tentang perilaku sistem ketika menentukan batas memori aplikasi Node.js menggunakan kunci --max-old-space-size . Dalam hal ini, batas memori ini akan sesuai dengan batas yang ditetapkan pada level wadah.

Apa yang disebut "ruang lama" dalam nama kuncinya adalah salah satu fragmen tumpukan yang dikendalikan oleh V8 (tempat di mana objek JavaScript "lama" ditempatkan). Kunci ini, jika Anda tidak masuk ke detail yang kami sentuh di bawah ini, mengontrol ukuran tumpukan maksimum. Rincian tentang sakelar baris perintah Node.js dapat ditemukan di sini .

Secara umum, ketika suatu aplikasi mencoba menggunakan lebih banyak memori daripada yang tersedia dalam wadah, operasinya dihentikan.

Dalam contoh berikut (file aplikasi disebut test-fatal-error.js ), objek MyRecord ditempatkan ke dalam array list , dengan interval 10 milidetik. Hal ini menyebabkan pertumbuhan tumpukan yang tidak terkendali, mensimulasikan kebocoran memori.

 'use strict'; const list = []; setInterval(()=> { const record = new MyRecord(); list.push(record); },10); function MyRecord() { var x='hii'; this.name = x.repeat(10000000); this.id = x.repeat(10000000); this.account = x.repeat(10000000); } setInterval(()=> { console.log(process.memoryUsage()) },100); 

Harap perhatikan bahwa semua contoh program yang akan kami diskusikan di sini ditempatkan di gambar Docker, yang dapat diunduh dari Docker Hub:

 docker pull ravali1906/dockermemory 

Anda dapat menggunakan gambar ini untuk percobaan independen.

Selain itu, Anda dapat mengemas aplikasi dalam wadah Docker, mengumpulkan gambar dan menjalankannya dengan batas memori:

 docker run --memory 512m --interactive --tty ravali1906/dockermemory bash 

Di sini ravali1906/dockermemory adalah nama dari gambar tersebut.

Sekarang Anda dapat memulai aplikasi dengan menentukan batas memori untuk itu yang melebihi batas wadah:

 $ node --max_old_space_size=1024 test-fatal-error.js { rss: 550498304, heapTotal: 1090719744, heapUsed: 1030627104, external: 8272 } Killed 

Di sini, --max_old_space_size mewakili batas memori yang ditunjukkan dalam megabita. Metode process.memoryUsage() memberikan informasi tentang penggunaan memori. Nilai dinyatakan dalam byte.

Aplikasi pada suatu saat dihentikan secara paksa. Ini terjadi ketika jumlah memori yang digunakan melintasi batas tertentu. Apa perbatasan ini? Apa batasan jumlah memori yang bisa kita bicarakan?

Perilaku yang diharapkan dari aplikasi yang berjalan dengan kuncinya adalah - max-old-space-size


Secara default, ukuran heap maksimum di Node.js (hingga versi 11.x) adalah 700 MB pada platform 32-bit, dan 1400 MB pada platform 64-bit. Anda dapat membaca tentang pengaturan nilai-nilai ini di sini .

Secara teori, jika Anda menggunakan kunci --max-old-space-size untuk --max-old-space-size batas memori yang melebihi batas memori kontainer, Anda dapat mengharapkan aplikasi dihentikan oleh mekanisme keamanan kernel Linux OOM Killer.

Pada kenyataannya, ini mungkin tidak terjadi.

Perilaku sebenarnya dari aplikasi yang berjalan dengan kunci adalah ukuran max-old-space-size


Aplikasi, segera setelah peluncuran, tidak mengalokasikan semua memori yang batasnya ditentukan menggunakan --max-old-space-size . Ukuran tumpukan JavaScript tergantung pada kebutuhan aplikasi. Anda bisa menilai berapa banyak memori yang digunakan aplikasi berdasarkan nilai bidang heapUsed dari objek yang dikembalikan oleh metode process.memoryUsage() . Bahkan, kita berbicara tentang memori yang dialokasikan di heap untuk objek.

Sebagai hasilnya, kami menyimpulkan bahwa aplikasi akan dihentikan secara paksa jika ukuran tumpukan lebih besar dari batas yang ditetapkan oleh kunci - --memory ketika wadah mulai.

Tetapi dalam kenyataannya ini mungkin juga tidak terjadi.

Saat membuat profil aplikasi Node.js yang intensif sumber daya yang berjalan dalam wadah dengan batas memori yang diberikan, pola berikut dapat diamati:

  1. OOM Killer dipicu jauh lebih lambat daripada saat ketika nilai heapTotal dan heapUsed secara signifikan lebih tinggi dari batas memori.
  2. OOM Killer tidak merespons melebihi batas.

Penjelasan tentang perilaku aplikasi Node.js dalam wadah


Sebuah wadah mengawasi satu indikator penting dari aplikasi yang berjalan di atasnya. Ini adalah RSS (ukuran yang ditetapkan penduduk). Indikator ini mewakili bagian tertentu dari memori virtual aplikasi.

Selain itu, itu adalah bagian dari memori yang dialokasikan untuk aplikasi.

Tapi itu belum semuanya. RSS adalah bagian dari memori aktif yang dialokasikan untuk aplikasi.

Tidak semua memori yang dialokasikan untuk aplikasi mungkin aktif. Faktanya adalah bahwa "memori yang dialokasikan" tidak perlu dialokasikan secara fisik sampai proses mulai benar-benar menggunakannya. Selain itu, sebagai tanggapan atas permintaan alokasi memori dari proses lain, sistem operasi dapat membuang bagian tidak aktif dari memori aplikasi ke file halaman dan mentransfer ruang kosong ke proses lain. Dan ketika aplikasi lagi membutuhkan potongan memori ini, mereka akan diambil dari file swap dan dikembalikan ke memori fisik.

Metrik RSS menunjukkan jumlah memori yang aktif dan tersedia untuk aplikasi di ruang alamatnya. Dialah yang mempengaruhi keputusan penutupan paksa aplikasi.

Bukti


▍ Contoh No. 1. Aplikasi yang mengalokasikan memori untuk buffer


Contoh berikut, buffer_example.js , memperlihatkan program yang mengalokasikan memori untuk buffer:

 const buf = Buffer.alloc(+process.argv[2] * 1024 * 1024) console.log(Math.round(buf.length / (1024 * 1024))) console.log(Math.round(process.memoryUsage().rss / (1024 * 1024))) 

Agar jumlah memori yang dialokasikan oleh program melebihi batas yang ditetapkan saat wadah diluncurkan, pertama jalankan wadah dengan perintah berikut:

 docker run --memory 1024m --interactive --tty ravali1906/dockermemory bash 

Setelah itu, jalankan program:

 $ node buffer_example 2000 2000 16 

Seperti yang Anda lihat, sistem tidak menyelesaikan program, meskipun memori yang dialokasikan oleh program melebihi batas kontainer. Ini terjadi karena fakta bahwa program tidak bekerja dengan semua memori yang dialokasikan. RSS sangat kecil, tidak melebihi batas memori kontainer.

▍ Contoh No. 2. Aplikasi mengisi buffer dengan data


Dalam contoh berikut, buffer_example_fill.js , memori tidak hanya dialokasikan, tetapi juga diisi dengan data:

 const buf = Buffer.alloc(+process.argv[2] * 1024 * 1024,'x') console.log(Math.round(buf.length / (1024 * 1024))) console.log(Math.round(process.memoryUsage().rss / (1024 * 1024))) 

Jalankan wadah:

 docker run --memory 1024m --interactive --tty ravali1906/dockermemory bash 

Setelah itu, jalankan aplikasi:

 $ node buffer_example_fill.js 2000 2000 984 

Ternyata, bahkan sekarang aplikasi tidak berakhir! Mengapa Faktanya adalah bahwa ketika jumlah memori aktif mencapai batas yang ditetapkan ketika wadah dimulai, dan ada ruang dalam file halaman, beberapa halaman lama dalam memori proses dipindahkan ke file halaman. Memori yang dirilis tersedia untuk proses yang sama. Secara default, Docker mengalokasikan ruang untuk file swap yang sama dengan batas memori yang ditetapkan menggunakan flag --memory . Dengan ini, kita dapat mengatakan bahwa proses memiliki 2 GB memori - 1 GB dalam memori aktif, dan 1 GB dalam file halaman. Yaitu, karena kenyataan bahwa aplikasi dapat menggunakan memori sendiri, isi yang sementara dipindahkan ke file halaman, ukuran indeks RSS berada dalam batas kontainer. Hasilnya, aplikasi terus bekerja.

▍ Contoh No. 3. Aplikasi yang mengisi buffer dengan data yang berjalan dalam wadah yang tidak menggunakan file halaman


Berikut adalah kode yang akan kami coba di sini (ini adalah file buffer_example_fill.js sama):

 const buf = Buffer.alloc(+process.argv[2] * 1024 * 1024,'x') console.log(Math.round(buf.length / (1024 * 1024))) console.log(Math.round(process.memoryUsage().rss / (1024 * 1024))) 

Kali ini, jalankan wadah, secara eksplisit mengatur fitur bekerja dengan file swap:

 docker run --memory 1024m --memory-swap=1024m --memory-swappiness=0 --interactive --tty ravali1906/dockermemory bash 

Luncurkan aplikasi:

 $ node buffer_example_fill.js 2000 Killed 

Lihat pesan Killed ? Ketika nilai kunci --memory-swap sama dengan nilai kunci --memory , ini memberitahu wadah bahwa itu tidak boleh menggunakan file --memory . Selain itu, secara default, kernel sistem operasi tempat wadah itu sendiri beroperasi dapat membuang sejumlah halaman memori anonim yang digunakan oleh wadah tersebut ke file halaman. --memory-swappiness ke 0 , kami menonaktifkan fitur ini. Akibatnya, ternyata file paging tidak digunakan di dalam wadah. Proses berakhir ketika metrik RSS melebihi batas memori wadah.

Rekomendasi umum


Ketika aplikasi Node.js diluncurkan dengan kunci --max-old-space-size , nilainya melebihi batas memori yang ditetapkan ketika wadah diluncurkan, mungkin tampak bahwa Node.js "tidak memperhatikan" batas kontainer. Tetapi, seperti dapat dilihat dari contoh sebelumnya, alasan yang jelas untuk perilaku ini adalah kenyataan bahwa aplikasi tidak menggunakan seluruh volume tumpukan yang ditentukan dengan --max-old-space-size flag.

Ingatlah bahwa aplikasi tidak akan selalu berperilaku sama jika menggunakan lebih banyak memori daripada yang tersedia dalam wadah. Mengapa Faktanya adalah bahwa memori aktif proses (RSS) dipengaruhi oleh banyak faktor eksternal yang tidak dapat dipengaruhi oleh aplikasi itu sendiri. Mereka bergantung pada beban pada sistem dan pada karakteristik lingkungan. Misalnya, ini adalah fitur aplikasi itu sendiri, tingkat paralelisme dalam sistem, fitur penjadwal sistem operasi, fitur pengumpul sampah, dan sebagainya. Selain itu, faktor-faktor ini, dari peluncuran ke peluncuran, dapat berubah.

Rekomendasi tentang pengaturan ukuran tumpukan Node.js untuk kasus-kasus ketika Anda dapat mengontrol opsi ini, tetapi tidak dengan pembatasan memori tingkat kontainer


  • Jalankan aplikasi Node.js minimum dalam wadah dan ukur ukuran RSS statis (dalam kasus saya, untuk Node.js 10.x, ini sekitar 20 Mb).
  • Tumpukan Node.js tidak hanya berisi old_space, tetapi juga yang lain (seperti new_space, code_space, dan sebagainya). Oleh karena itu, jika Anda mempertimbangkan konfigurasi standar platform, Anda harus mengandalkan fakta bahwa program tersebut akan membutuhkan sekitar 20 MB lebih banyak memori. Jika pengaturan standar telah berubah, perubahan ini juga harus diperhitungkan.
  • Sekarang kita perlu mengurangi nilai yang diperoleh (misalkan akan menjadi 40 MB) dari jumlah memori yang tersedia dalam wadah. Apa yang tersisa adalah nilai yang, tanpa takut --max-old-space-size program kehabisan memori, dapat ditentukan sebagai nilai kunci - --max-old-space-size .

Rekomendasi untuk mengatur batas memori kontainer untuk kasus-kasus di mana parameter ini dapat dikontrol, tetapi parameter aplikasi Node.js tidak


  • Jalankan aplikasi dalam mode yang memungkinkan Anda untuk mengetahui nilai puncak dari memori yang dikonsumsi.
  • Analisis skor RSS. Khususnya, di sini, bersama dengan metode process.memoryUsage() , perintah top Linux mungkin berguna.
  • Asalkan dalam wadah di mana ia direncanakan untuk menjalankan aplikasi, tidak ada tetapi tidak akan dieksekusi, nilai yang diperoleh dapat digunakan sebagai batas memori kontainer. Agar aman, disarankan untuk menambah setidaknya 10%.

Ringkasan


Dalam Node.js 12.x, beberapa masalah yang dibahas di sini diselesaikan dengan menyesuaikan ukuran tumpukan secara adaptif, yang dilakukan sesuai dengan jumlah RAM yang tersedia. Mekanisme ini juga berfungsi saat menjalankan aplikasi Node.js dalam wadah. Tetapi pengaturan mungkin berbeda dari pengaturan standar. Ini, misalnya, terjadi ketika kunci --max_old_space_size digunakan ketika memulai aplikasi. Untuk kasus seperti itu, semua hal di atas tetap relevan. Ini menunjukkan bahwa siapa pun yang menjalankan aplikasi Node.js dalam wadah harus berhati-hati dan bertanggung jawab tentang pengaturan memori. Selain itu, pengetahuan tentang batas standar pada penggunaan memori, yang agak konservatif, dapat meningkatkan kinerja aplikasi dengan sengaja mengubah batas-batas ini.

Pembaca yang budiman! Apakah Anda kehabisan masalah memori saat menjalankan aplikasi Node.js dalam wadah Docker?



Source: https://habr.com/ru/post/id454522/


All Articles