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:
- OOM Killer dipicu jauh lebih lambat daripada saat ketika nilai
heapTotal
dan heapUsed
secara signifikan lebih tinggi dari batas memori. - 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?

