7 Cara Meningkatkan Performa Node.js Dalam Skala Besar - CRUDPRO

7 Cara Meningkatkan Performa Node.js Dalam Skala Besar

7 Cara Meningkatkan Performa Node.js Dalam Skala Besar

Kinerja adalah salah satu aspek terpenting dari pengembangan aplikasi web. Aplikasi yang cepat akan membuat penggunanya, pengembang, dan stakeholders kepentingan bisnis senang, sementara aplikasi yang lambat pasti membuat ketiga pihak frustasi.

Pada artikel ini, kami akan mempertimbangkan beberapa praktik yang harus Anda terapkan untuk menskalakan server Node.js Anda. Server Anda kemudian akan dapat menangani beban kerja lalu lintas yang tinggi tanpa pengalaman pengguna yang menurun.

Dengan mengikuti semua tip kinerja yang terbukti dan teruji dalam posting ini, Anda akan dapat meningkatkan kecepatan dan kinerja produk Anda, memberikan keunggulan yang dibutuhkan untuk berhasil di pasar.

1. Profil dan Pantau Aplikasi Anda

Sebelum Anda mencoba meningkatkan kinerja sistem, penting untuk mengukur tingkat kinerja saat ini. Dengan cara ini, Anda akan mengetahui inefisiensi dan strategi yang tepat untuk diterapkan guna mendapatkan hasil yang diinginkan. Mengukur tingkat kinerja aplikasi saat ini mungkin memerlukan berbagai jenis pengujian, seperti berikut ini:

  • Pengujian beban: mengacu pada praktik mensimulasikan penggunaan sistem yang diharapkan dan mengukur responsnya saat beban kerja meningkat.
  • Stress testing: dirancang untuk mengukur bagaimana suatu sistem bekerja di luar batas kondisi kerja normal. Tujuannya adalah untuk menentukan seberapa banyak sistem dapat menangani sebelum gagal dan bagaimana upaya untuk pulih dari kegagalan.
  • Pengujian spike: membantu menguji perilaku aplikasi saat menerima peningkatan atau penurunan beban yang drastis.
  • Pengujian skalabilitas: digunakan untuk menemukan titik di mana aplikasi berhenti melakukan penskalaan dan mengidentifikasi alasan di baliknya.
  • Pengujian volume: menentukan apakah suatu sistem dapat menangani data dalam jumlah besar.
  • Pengujian daya tahan: membantu mengevaluasi perilaku aplikasi perangkat lunak di bawah beban berkelanjutan untuk waktu yang lama, untuk mengetahui masalah seperti kebocoran memori.

Melakukan beberapa atau semua tes di atas akan memberi Anda beberapa metrik penting, seperti:

  • waktu respons
  • latency rata-rata
  • tingkat kesalahan
  • permintaan setiap detik
  • throughput
  • penggunaan CPU dan memori
  • pengguna bersama

dan banyak lagi.

Setelah menerapkan pengoptimalan tertentu, jangan lupa jalankan kembali pengujian untuk memverifikasi bahwa perubahan Anda memiliki efek yang diinginkan pada kinerja sistem.

Penting juga untuk menggunakan alat Pemantauan Kinerja Aplikasi (APM) untuk mengawasi kinerja sistem dalam produksi. Solusi pemantauan yang berbeda dapat menangani hal ini untuk Anda. Kami menyukai pemantauan kinerja Node.js dengan AppSignal.

Sangat mudah untuk mengintegrasikannya ke dalam aplikasi Anda (cukup jalankan npx @appsignal/cli install), dan ini akan secara otomatis melacak beberapa metrik kinerja seperti waktu respons dan throughput bersama log kesalahan, ketersediaan sistem, metrik host, dan banyak lagi. Anda dapat menggunakan wawasan yang diperoleh dari data untuk mengambil langkah proaktif untuk meningkatkan kinerja sistem atau dengan cepat mengidentifikasi akar penyebab masalah tertentu sehingga Anda dapat segera mengatasinya sebelum diketahui oleh pengguna Anda.

2. Kurangi Latensi Melalui Caching

Caching sisi server adalah salah satu strategi paling umum untuk meningkatkan kinerja aplikasi web. Tujuan utamanya adalah untuk meningkatkan kecepatan pengambilan data, baik dengan menghabiskan lebih sedikit waktu untuk menghitung data tersebut atau melakukan I/O (seperti mengambil data tersebut melalui jaringan atau dari database).

Cache adalah lapisan penyimpanan berkecepatan tinggi yang digunakan sebagai penyimpanan sementara untuk data yang sering diakses. Anda tidak perlu mengambil data dari sumber utama data (biasanya jauh lebih lambat) setiap kali diminta.

Caching paling efektif untuk data yang tidak terlalu sering berubah. Jika aplikasi Anda menerima banyak permintaan untuk data tidak berubah yang sama, menyimpannya dalam cache pasti akan meningkatkan respons permintaan tersebut secara signifikan. Anda juga dapat menyimpan hasil tugas intensif komputasi di cache, asalkan dapat digunakan kembali untuk permintaan lainnya. Hal ini mencegah sumber daya server macet secara tidak perlu dengan mengulangi pekerjaan untuk menghitung data tersebut.

Kandidat umum lainnya untuk caching adalah permintaan API yang masuk ke sistem eksternal. Misalkan respons dapat digunakan kembali dengan andal untuk permintaan selanjutnya. Dalam hal ini, masuk akal untuk menyimpan permintaan API di lapisan cache untuk menghindari permintaan jaringan tambahan dan biaya lain yang terkait dengan API yang dimaksud.

Cara yang relatif mudah untuk mengimplementasikan caching di aplikasi Node.js adalah melalui solusi caching dalam proses seperti node-cache. Ini melibatkan penempatan data yang digunakan secara aktif dalam memori, yang dapat diambil lebih cepat. Masalah utama dengan cache dalam proses adalah bahwa cache terkait dengan proses aplikasi, sehingga jarang cocok untuk alur kerja terdistribusi (terutama saat menyimpan objek yang dapat diubah ke cache). Dalam pengaturan tersebut, Anda dapat menggunakan solusi caching terdistribusi seperti Redis atau Memcached. Ini berjalan secara independen dari aplikasi dan lebih praktis saat menskalakan aplikasi ke beberapa server.

3. Gunakan Batas Waktu Saat Berurusan dengan Operasi I/O

Saat membuat aplikasi Node.js, waktu tunggu adalah salah satu hal yang paling mudah terjadi kesalahan. Server Anda mungkin berbicara dengan layanan eksternal lain yang mungkin juga memanggil layanan lain. Jika satu layanan dalam rangkaian lambat atau tidak responsif, ini akan mengakibatkan pengalaman yang lambat bagi pengguna akhir Anda. Bahkan jika Anda tidak mengalami masalah ini selama pengembangan, Anda tidak dapat menjamin bahwa dependensi Anda akan selalu merespons secepat biasanya, itulah mengapa konsep batas waktu menjadi penting.

Batas waktu adalah waktu tunggu maksimum yang ditetapkan berdasarkan permintaan. Ini mewakili berapa lama klien siap menunggu tanggapan dari layanan eksternal. Jika respon tidak diterima dalam batas yang ditentukan, maka koneksi akan dibatalkan sehingga aplikasi tidak hang selamanya. Banyak library populer untuk membuat permintaan HTTP di Node.js (seperti axios) tidak menetapkan batas waktu default, artinya API jarak jauh apa pun dapat membuat aplikasi Anda menunggu sumber daya yang diminta tanpa batas. Anda harus menyetel batas waktu permintaan untuk mencegah hal ini terjadi:

const axios = require("axios");
 
axios.defaults.timeout === 1000; // global timeout of 1s

Dalam cuplikan di atas, batas waktu 1000ms (1s) ditetapkan sebagai default untuk semua permintaan HTTP yang dibuat melalui axios. Ini menjamin bahwa setiap permintaan tidak akan memakan waktu lebih lama dari waktu tersebut, bahkan jika API tidak responsif. Anda juga dapat menyetel nilai batas waktu pada setiap permintaan saat default global tidak sesuai:

axios
  .get("https://example.com/api", { timeout: 2000 })
  .then((response) => {
    console.log(response);
  })
  .catch((error) => {
    console.log(error);
  });

Perhatikan bahwa nilai batas waktu axios adalah batas waktu baca, yang berbeda dari batas waktu koneksi. Yang terakhir adalah waktu di mana koneksi TCP harus dibuat, sedangkan yang pertama menentukan berapa lama klien akan menunggu respons setelah koneksi dibuat.

Biasanya, batas waktu koneksi jauh lebih rendah daripada batas waktu baca. Klien dapat mencoba server lain atau API alternatif jika satu layanan membutuhkan waktu terlalu lama untuk menerima koneksi. Ini masih memberi cukup waktu bagi server untuk menghasilkan respons setelah koneksi diterima.

Saat ini, axios tidak mendukung pengaturan batas waktu koneksi secara terpisah dari batas waktu baca, yang dapat membatasi dalam beberapa skenario. Jika Anda memerlukan fungsionalitas ini, Anda dapat mencoba library yang didapat - ini memungkinkan untuk spesifikasi batas waktu baca dan koneksi yang terpisah.

Sebelum memilih nilai waktu tunggu, Anda dapat memantau waktu respons untuk API yang Anda sambungkan menggunakan alat khusus atau melacak panggilan API Anda dengan mencatatnya. Ini akan memungkinkan Anda membuat keputusan berdasarkan informasi untuk semua layanan eksternal yang berinteraksi dengan program Anda. Anda juga harus memiliki strategi coba lagi untuk layanan penting untuk memperhitungkan pelambatan sementara. Grafik di bawah menunjukkan seberapa rata-rata waktu respons untuk titik akhir dapat dipantau di AppSignal.

4. Jangan Melayani Aset Statis dengan Node.js

Untuk memastikan kinerja terbaik untuk server Node.js Anda, hindari menggunakannya untuk menyajikan aset statis seperti JavaScript, CSS, atau file gambar dari aplikasi Anda. Node.js tidak dirancang dengan mempertimbangkan kasus penggunaan ini, jadi melayani aset dari aplikasi utama menghabiskan sumber daya yang berharga dan menahan komputasi bisnis yang penting. Offload tugas melayani file statis ke server web seperti Nginx, yang dapat melakukan pengoptimalan yang tidak masuk akal untuk dilakukan Node.js. Pengujian ini menunjukkan bahwa Nginx dua kali lebih cepat dalam mengirimkan aset statis dibandingkan Node.js (menggunakan middleware statis Express).

Opsi lain untuk menyajikan file statis adalah menyiapkan proxy CDN seperti Amazon CloudFront untuk meng-cache konten statis Anda dan menyajikannya sedekat mungkin dengan pengguna akhir. Ini membebaskan server Node.js untuk menangani permintaan dinamis saja.

5. Gunakan Clustering untuk Meningkatkan Throughput

Clustering adalah teknik yang digunakan untuk menskalakan server Node.js secara horizontal pada satu mesin dengan memunculkan proses anak (pekerja) yang berjalan secara bersamaan dan berbagi satu port. Ini adalah taktik umum untuk mengurangi waktu henti, pelambatan, dan pemadaman dengan mendistribusikan koneksi yang masuk ke semua proses pekerja yang tersedia sehingga inti CPU yang tersedia digunakan secara maksimal. Karena instance Node.js berjalan pada satu utas, ia tidak dapat memanfaatkan sistem multi-core dengan benar - karenanya diperlukan pengelompokan.

Anda dapat mengelompokkan server Node.js Anda melalui modul cluster di library standar. Berikut ini contoh yang diambil dari dokumentasi resmi:

const cluster = require("cluster");
const http = require("http");
const process = require("process");
const os = require("os");
 
const cpus = os.cpus;
 
const numCPUs = cpus().length;
 
if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
 
  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
 
  cluster.on("exit", (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http
    .createServer((req, res) => {
      res.writeHead(200);
      res.end("hello world\n");
    })
    .listen(8000);
 
  console.log(`Worker ${process.pid} started`);
}

Setelah Anda menjalankan program ini, koneksi yang dikirim ke port 8000 akan dibagi antara proses pekerja. Ini akan mengarah pada manajemen permintaan yang lebih efisien dalam aplikasi:

$ node server.js
Primary 15990 is running
Worker 15997 started
Worker 15998 started
Worker 16010 started
Worker 16004 started

Kelemahan menggunakan modul cluster asli di Node.js adalah jumlah kode yang perlu Anda tulis untuk menelurkan dan mengelola pekerja, dan tidak mungkin mengubah jumlah proses dengan cepat.

Untuk manajemen klaster Node.js yang lebih kuat, gunakan manajer proses PM2 untuk Node.js. Ini menggunakan modul cluster di bawah kap dan menangani pekerja pemijahan, menghentikan atau memulai kembali pekerja, dan mendistribusikan beban yang masuk di antara pekerja. Ini juga menyediakan beberapa alat untuk membantu Anda memantau dan men-tweak kinerja proses pekerja:

6. Skala di Beberapa Mesin dengan Load Balancer

Menskalakan aplikasi Node.js Anda secara horizontal di beberapa mesin serupa dengan menskalakan di beberapa inti di satu mesin. Selama aplikasi Anda dapat dijalankan sebagai proses independen, aplikasi tersebut dapat didistribusikan untuk dijalankan di beberapa mesin. Persyaratan utamanya adalah menggunakan penyeimbang muatan untuk mendistribusikan lalu lintas masuk ke server (serupa dengan cara modul kluster digunakan untuk mengarahkan lalu lintas ke proses pekerja anak). Anda bahkan dapat memiliki beberapa penyeimbang muatan yang mengarah ke kumpulan server yang sama untuk menghindari satu titik kegagalan.

7. Memanfaatkan Worker Threads untuk Tugas-tugas intensif CPU

Utas pekerja menyediakan mekanisme untuk menjalankan tugas intensif CPU dalam aplikasi Node.js tanpa memblokir loop peristiwa utama. Mereka diperkenalkan di Node.js v10.5.0, dan hanya menjadi stabil di rilis v12.0.0.

Utas pekerja dihasilkan oleh utas utama atau induk, dan tanggung jawabnya adalah melakukan tugas secara terpisah dari pekerja lain. Tidak seperti proses atau kluster anak, thread pekerja dapat berbagi memori dengan mentransfer instance ArrayBuffer atau berbagi instance SharedArrayBuffer. Seorang pekerja dan orang tuanya juga dapat berkomunikasi dua arah menggunakan saluran pesan.

Berikut cara membuat thread pekerja menggunakan modul worker_threads dari library standar:

// main.js
const { Worker } = require("worker_threads");
 
// Create a new worker
const worker = new Worker("./worker.js");
 
// Listen for messages from worker
worker.on("message", (result) => {
  console.log(
    `The prime numbers between 2 and ${result.input} are: ${result.primes}`
  );
});
 
worker.on("error", (error) => {
  console.log(error);
});
 
worker.on("exit", (exitCode) => {
  console.log(exitCode);
});
 
// Send messages to the worker
worker.postMessage({ input: 100 });
worker.postMessage({ input: 50 });

Saat main.js dijalankan, thread pekerja baru akan muncul dari file worker.js. Metode postMessage() mengirim pesan ke pekerja, dan pendengar digunakan untuk mendeteksi tanggapan dari pekerja. File worker.js ditunjukkan di bawah ini:

const { parent } = require("worker_threads"); parent.on("message", (data) => { parent.postMessage({ input: data.input, primes: getPrimes(data.input), }); }); function getPrimes(max) { const sieve = [], primes = []; for (let i = 2; i <= max; ++i) { if (!sieve[i]) { primes.push(i); for (let j = i << 1; j <= max; j += i) { sieve[j] = true; } } } return primes; }

Dalam cuplikan di atas, fungsi getPrimes() digunakan untuk menemukan semua bilangan prima antara 2 dan argumen yang ditentukan yang diterima dari induk melalui pendengar pesan. Hasilnya juga dikirim kembali ke induk dengan menggunakan metode postMessage() seperti sebelumnya:

The prime numbers between 2 and 100 are: 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97
The prime numbers between 2 and 50 are: 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47

Tips Tambahan untuk Meningkatkan Performa Node.js

Berikut ini beberapa pengoptimalan mikro yang dapat Anda lakukan di aplikasi Node.js untuk mendapatkan hasil yang lebih baik secara andal:

  • Selalu gunakan Node.js rilis terbaru untuk mendapatkan kinerja terbaik.
  • Perhatikan dependensi Anda, dan pilih library yang paling berperforma baik jika memungkinkan. Kadang-kadang, lebih baik melupakan penambahan dependensi dan alih-alih menulis kode untuk melakukan tugas sendiri.
  • Pastikan bahwa semua operasi I/O independen menggunakan primitif asinkron seperti callback, promise, dan async/wait untuk memastikan alur operasi non-pemblokiran dan meningkatkan latensi downstream.
  • Anda tidak harus mengoptimalkan semuanya. Setelah hotspot aplikasi Anda dioptimalkan dengan baik, hentikan.
  • Hot spot Anda dapat berubah dari waktu ke waktu, jadi pastikan untuk menggunakan beberapa bentuk observasi atau solusi pemantauan untuk melacak perubahan ini.
  • Saat bekerja dengan potongan data yang besar, gunakan aliran Node.js untuk efisiensi memori yang optimal dan pengurangan latensi.
  • Untuk mengurangi beban pengumpul sampah (sehingga mengurangi latensi), hindari alokasi memori di hotspot.
  • Optimalkan kueri database Anda, dan skalakan dengan tepat untuk memastikan bahwa kueri tersebut tidak menjadi hambatan.
  • Jangan menukar kinerja dengan keandalan. Cobalah untuk mencapai keseimbangan antara menyetel kode Anda untuk performa, biaya pengembangan, dan pemeliharaan lanjutan.

Penutup: Skalakan Aplikasi Node.js Anda dengan Meningkatkan Performa

Pada artikel ini, kami telah membahas beberapa tips praktis untuk membantu Anda menskalakan aplikasi Node.js Anda untuk menangani lebih banyak lalu lintas. Sebelum menerapkan pengoptimalan tertentu, pastikan Anda menjalankan uji kinerja komprehensif pada sistem Anda dan gunakan wawasan yang Anda peroleh untuk menentukan tindakan yang harus diambil. Selain itu, gunakan alat pengamatan/pemantauan sehingga Anda dapat melihat dampak perubahan dan melihat regresi dengan cepat dan andal.