Manajemen proses

PM2, modul cluster, dan graceful shutdown.

Daftar isi

Aplikasi Node berjalan sebagai satu proses yang dijalankan oleh satu event loop, sehingga pada mesin dengan banyak inti CPU hanya satu inti yang terpakai. Modul cluster bawaan Node mengatasi hal ini dengan menelurkan beberapa proses worker dari satu proses primary, sehingga beban permintaan terbagi pada beberapa inti. Manajer proses eksternal seperti PM2 menambahkan fitur restart otomatis, pembatasan memori, serta log rotation di atas mekanisme cluster. Setelah membaca topik ini, pembaca dapat menjalankan modul cluster secara manual, mengonfigurasi PM2 melalui ecosystem.config.js, serta menerapkan graceful shutdown yang menutup koneksi dengan rapi saat sinyal terminasi diterima.

Modul cluster#

Modul cluster memungkinkan beberapa proses Node berbagi port yang sama. Proses primary memanggil cluster.fork() untuk menelurkan worker, dan setiap worker menjalankan modul http (atau https) yang mendengarkan pada port yang sama. Kernel kemudian membagi koneksi masuk ke seluruh worker melalui penyeimbangan bebas round-robin (default Node pada sistem non-Windows). Worker berjalan sebagai proses independen dengan event loop-nya sendiri, sehingga satu worker yang crash tidak menurunkan yang lain.

js cluster.js
// tidak-dijalankan: proses cluster berjalan tanpa batas, jalankan lewat bash dengan SIGTERM
const cluster = require('node:cluster');
const http = require('node:http');

if (cluster.isPrimary) {
  console.log('primary naik');
  cluster.fork();

  const turunkan = (sinyal) => {
    console.log(`primary menerima ${sinyal}, teruskan ke worker`);
    for (const id in cluster.workers) {
      cluster.workers[id].process.kill(sinyal);
    }
  };
  process.on('SIGTERM', () => turunkan('SIGTERM'));
  process.on('SIGINT', () => turunkan('SIGINT'));

  cluster.on('exit', (worker, code) => {
    console.log(`worker ${worker.id} berhenti dengan kode ${code}`);
    if (Object.keys(cluster.workers).length === 0) {
      console.log('primary turun');
      process.exit(0);
    }
  });
} else {
  const server = http.createServer((req, res) => {
    res.end(`halo dari worker ${cluster.worker.id}\n`);
  });
  server.listen(0, () => {
    console.log(`worker ${cluster.worker.id} mendengarkan`);
  });

  const tutup = () => {
    console.log(`worker ${cluster.worker.id} menutup server`);
    server.close(() => process.exit(0));
  };
  process.on('SIGTERM', tutup);
  process.on('SIGINT', tutup);
}
bash
# jalankan cluster.js di latar belakang
node cluster.js &
# simpan pid proses primary
PID=$!
# tunggu worker mendengarkan
sleep 1
# kirim SIGTERM untuk graceful shutdown
kill -TERM $PID
# tunggu proses primary selesai
wait $PID 2>/dev/null
text Keluaran
primary naik
worker 1 mendengarkan
primary menerima SIGTERM, teruskan ke worker
worker 1 menutup server
worker 1 berhenti dengan kode 0
primary turun

Proses primary menerima SIGTERM dari luar, kemudian meneruskan sinyal itu ke setiap worker melalui cluster.workers[id].process.kill(sinyal). Setiap worker menutup server HTTP dengan server.close(), yang menghentikan koneksi baru sambil membiarkan koneksi yang sedang berlangsung selesai, lalu memanggil process.exit(0). Primary memantau peristiwa exit pada cluster, dan ketika tidak ada worker tersisa, primary turun dengan process.exit(0).

Diagram berikut memvisualkan hubungan primary dengan beberapa worker, serta alur penerusan SIGTERM dari primary ke setiap worker.

Modul cluster: primary dan workerprimarycluster.fork()worker 1http serverworker 2http serverworker Nhttp serverforkforkSIGTERM diteruskanPrimary memantau exit worker, worker melayani permintaan HTTP secara mandiri.
Modul cluster: primary menelurkan worker dan meneruskan SIGTERM

Pada production, jumlah worker biasanya sama dengan jumlah inti CPU, yang dapat diperoleh dari os.availableParallelism(). Nilai ini lebih akurat memperhitungkan beban sebenarnya daripada os.cpus().length karena availableParallelism memperhitungkan batasan cgroup pada container dan perbedaan antara inti logis serta inti fisik akibat hyperthreading. Pemanggilan cluster.fork() dalam loop sebanyak nilai tersebut menghasilkan sejumlah worker yang seimbang dengan kapasitas mesin.

Graceful shutdown#

Graceful shutdown adalah penutupan proses yang memberi waktu bagi operasi yang sedang berjalan untuk selesai sebelum process.exit dipanggil. Pada server HTTP, penutupan dilakukan dengan server.close() yang berhenti menerima koneksi baru namun menyelesaikan permintaan yang sedang diproses. Tanpa graceful shutdown, koneksi yang sedang aktif dapat terputus di tengah respons, sehingga klien menerima error koneksi atau data tidak lengkap.

Penanganan sinyal dilakukan dengan memasang penerima pada process untuk SIGTERM dan SIGINT. SIGTERM dikirim oleh manajer proses seperti PM2 atau systemd saat restart atau stop, sedangkan SIGINT dikirim saat pengguna menekan Ctrl-C di terminal. Keduanya sebaiknya memicu prosedur shutdown yang sama, yaitu berhenti menerima permintaan baru, menyelesaikan permintaan yang sedang jalan, menutup koneksi basis data, lalu memanggil process.exit.

PM2 dan ecosystem.config.js#

PM2 adalah manajer proses eksternal yang menambahkan fitur di atas modul cluster. Fitur tersebut meliputi restart otomatis saat worker crash, pembatasan memori melalui max_memory_restart, log rotation, serta reload tanpa downtime dengan mengganti worker satu per satu. Konfigurasi PM2 ditulis pada berkas ecosystem.config.js sehingga seluruh pengaturan tetap berada di kontrol versi.

js ecosystem.config.js
// tidak-dijalankan: PM2 tidak tersedia di sandbox, jalankan dengan `pm2 start ecosystem.config.js`
module.exports = {
  apps: [
    {
      name: 'catatan-suhu',
      script: './server.js',
      instances: 'max',
      exec_mode: 'cluster',
      max_memory_restart: '300M',
      env: {
        NODE_ENV: 'production',
        PORT: 8080,
        LOG_LEVEL: 'info'
      },
      env_staging: {
        NODE_ENV: 'staging',
        PORT: 8081,
        LOG_LEVEL: 'debug'
      },
      graceful_timeout: 4000,
      listen_timeout: 3000,
      shutdown_with_message: false
    }
  ]
};

Berkas ecosystem.config.js di atas mengatur aplikasi catatan-suhu pada mode cluster dengan jumlah worker sebanyak inti CPU (instances: 'max'). Batas max_memory_restart membuat PM2 me-restart worker yang memori resident set-nya melampaui 300 MB, sehingga kebocoran memori kecil tidak menjatuhkan seluruh aplikasi. Variabel lingkungan diatur per environment melalui blok env dan env_staging, sehingga perpindahan antar lingkungan dilakukan dengan argumen --env staging tanpa mengubah berkas konfigurasi.

Field graceful_timeout menentukan durasi yang diberikan PM2 kepada worker untuk menutup diri sebelum SIGKILL dikirim, sedangkan listen_timeout menentukan batas waktu worker untuk mulai mendengarkan port sebelum dianggap gagal. Kedua nilai ini perlu lebih besar dari estimasi durasi shutdown dan startup aplikasi, agar reload tanpa downtime berjalan mulus.

Kesalahan umum#

Kesalahan yang sering muncul adalah memasang penerima SIGTERM yang langsung memanggil process.exit(0) tanpa memanggil server.close(). Akibatnya, koneksi yang sedang aktif diputus seketika, dan klien menerima error socket hang up. Perbaikannya adalah selalu memanggil server.close() lebih dahulu dan memanggil process.exit pada callbacknya.

Kesalahan lain adalah mengabaikan worker yang crash pada modul cluster. Tanpa penerima peristiwa exit yang memanggil cluster.fork(), jumlah worker aktif berkurang seiring waktu, sehingga kapasitas server menurun. Penambahan cluster.on('exit', () => cluster.fork()) membuat primary menelurkan worker baru setiap kali satu crash, dengan catatan penyebab crash sebaiknya dicatat untuk investigasi.

Ringkasan#

  • Modul cluster membuat satu proses primary menelurkan beberapa worker yang berbagi port yang sama, sehingga beberapa inti CPU terpakai. Primary memantau cluster.on('exit') dan worker melayani permintaan HTTP secara mandiri.
  • Graceful shutdown dilakukan dengan memasang penerima SIGTERM dan SIGINT pada process, memanggil server.close() untuk menyelesaikan permintaan aktif, lalu process.exit pada callback.
  • PM2 menambahkan fitur restart otomatis, pembatasan memori, dan reload tanpa downtime. Konfigurasinya ditulis pada ecosystem.config.js dengan variabel lingkungan per environment.
  • process.exit(0) tanpa menutup server memutus koneksi aktif, sehingga selalu dipanggil setelah server.close() selesai.

Referensi#