NodeJS Cluster Module

Mehmet Demirel
7 min readSep 26, 2023

--

Herkese merhaba,

Bu gün Node.js uygulamalarınızın iş yükünü yönetme şeklini iyileştirme konusunda size hızlı bir kazanç sağlayabilecek bir yaklaşıma göz atacağız. Başlamadan önce NodeJS’in nasıl çalıştığına dair bir kaç bilgiye sahip olalım.

NodeJS, Google’ın geliştirdiği JavaScript kodunu makine diline çeviren C++ ile yazılmış V8 Engine ’i kullanır. V8 Engine’i tarayıcı dışında çalıştırır, bu da NodeJS’in çok performanslı olmasını sağlar. JavaScript’i sunucu tarafında çalıştırabilme yeteneği ile öne çıkan NodeJS, bu sayede tarayıcı dışında da çalışabilir. Dosya sistemi erişimi gibi işlemleri gerçekleştirebilir ve ağ üzerinden iletişim kurabilir.

Node.js, single-process( tek işlemci) olarak çalışır. Bu demek oluyor ki, varsayılan olarak her gelen istek için ayrı Worker(işçi/iş parçacığı) oluşturmaz. Bunun yerine, mevcut işlem içerisinde isteklerin işlenmesini sağlar. Yani, bir Worker oluşturup onun üzerinden gelen tüm istekleri işlemez.

Bu durum, temelde Node.js’in event-driven(olay-tabanlı) yapısına dayanır. Gelen herhangi bir istek, bir olay olarak kabul edilir ve bu olayı işlemek için gerekli olan işlemci kaynağı ayrılmaz. Bu sayede, tek bir işlem içinde aynı anda birçok isteğe yanıt verebilir.

Eğer yeni bir JavaScript geliştiricisi iseniz veya konuya hakim değilseniz Event Loop’un nasıl çalıştığını bilmeniz gerekir. Bu makaleme göz atarak bilgi edinebilirsiniz.

Varsayılan olarak, Node.js, makinenin bütün CPU çekirdeklerini tam olarak kullanmaz. Bu, eğer işlemcide birden fazla çekirdek varsa, potansiyel olarak performans kaybına sebep olabilir. Cluster module devreye girmesi tam da burada önemli bir rol oynar. Cluster module, birden çok Worker oluşturarak bu Worker’ların farklı CPU çekirdeklerini kullanmasını sağlar, böylece uygulamanın daha etkili ve performanslı çalışmasını mümkün kılar.

NodeJS’in single-thread ile çalıştığını biliyoruz. Peki single-thread bizim işlemlerimizi görmesi açısından yeterli midir? Aslında evet çok fazla serverımıza istek gelmediği ve çok fazla yük bindirmediğimiz zamanlarda single-thread bizim işimizi görecektir. Ancak uygulama küçük dahi olsa kullanıcı sayımız arttığında doğru olarak bununla birlikte isteklerimiz artacaktır. İşte burada önce serverımız daha sonra biz geliştiriciler zorlanmaya başlayacağız.

Peki çok basit bir şekilde biz bu sorunu çözümleyebilir miyiz? Evet geçici olarak bir çözüme kavuşturabiliriz. Özellikle monolitik uygulamalarımız için biçilmiş bir kaftan. Cluster module kullanarak uygulamamızı birden fazla örnekleyebiliriz ve çalıştırabiliriz.

Cluster Module Nedir?

Node.js Cluster module, uygulamalarınızın performansını artırmak için önemli bir rol oynar. Bu module, yük dengeleyici olarak çalışır, yani aynı anda paylaşılan bir bağlantı noktasında çalışan Child Processes’e (alt/çocuk işlemci) iş yükünü dağıtır. Özellikle, Node.js’in engelleyici (blocking) kodlara iyi yanıt vermediği durumlarda etkilidir. Eğer yalnızca bir işlemci varsa ve bu işlemci ağır ve CPU yoğun bir işlemle meşgulse, diğer istekler bu işlemin tamamlanmasını beklemek zorunda kalır.

Fakat, birden fazla işlemle bu durumu aşmak mümkündür. Bir işlemci göreceli olarak CPU yoğun bir işlemle meşgulken, diğer işlemler gelen diğer istekleri alarak ve diğer kullanılabilir CPU’ları kullanarak yükü dengeleyebilir. İşte cluster module gücü burada yatıyor — işçiler yükü paylaşıyor ve uygulama durmuyor.

Bu module ayrıca, ana sürecin (master/main process) yükünü çocuk süreçlere (child processes) dağıtmasını sağlar. Bunun iki yolu bulunmaktadır:

  • Round-Robin Yöntemi: Bu varsayılan olarak kullanılan bir yöntemdir. Birden fazla kaynağın (genellikle işlemcilerin veya işlem parçacıklarının) eşit şekilde ve sırayla kullanılmasını sağlayan bir planlama algoritmasıdır. Her bir kaynak sırayla görev alır ve eşit miktarda iş yükü alır. Örneğin, bir işlemci grubunuz varsa ve her biri bir görevi işleyebiliyorsa, round-robin planlama algoritması bu görevleri sırayla atar. Her bir işlemci, sırası geldiğinde bir görevi alır, işlemeye başlar ve tamamlandığında bir sonraki göreve geçer.
  • Soketi Dinleyerek İş Gönderme Yolu: Bu yöntemde, main process bir soketi dinler. Interested workers (ilgili işciler) iş gönderir. İşçiler daha sonra gelen istekleri işler. Bu, ana sürecin belirli bir iletişim noktasındaki (genellikle bir ağ bağlantısı) gelen verileri bekleyerek kabul etmesini ifade eder. Bu yöntem, dış dünyadan gelen istekleri dinlemek için yaygın olarak kullanılır.

İkinci yöntem olan “Soketi Dinleyerek İş Gönderme Yolu”, temel round-robin yaklaşımından daha fazla teknik bilgi gerektirir. Bu yöntemde, ana süreç bir iletişim noktasındaki (genellikle bir ağ bağlantısı) gelen verileri bekler ve Interested workers gönderir. İşçiler, gelen istekleri işleyerek yanıt üretir. Bu, ana sürecin dış dünyadan gelen istekleri dinlemek için belirli bir iletişim noktasındaki verileri bekleyerek kabul etmesini ifade eder. Yani, bu yöntem daha fazla konfigürasyon ve ağ iletişimi bilgisi gerektirir.

Özetle, “Soketi Dinleyerek İş Gönderme Yolu” yöntemi, daha gelişmiş ve özelleştirilebilir bir yaklaşım sunar, ancak temel round-robin yaklaşımından daha fazla bilgi ve tecrübe gerektirir.

Nasıl Kullanılır?

Bir express uygulaması oluşturaracağız. Bu sunucu Event Loop’u kasıtlı olarak engelleyecek ve ağır çalışacaktır. İlk kod örneğiniş Cluster Module olmadan yazacağız.

Event Loop’u anlamak adına bu makaleme göz atabilirsiniz.

Uygulamamızı oluşturmaya başlayalım. Ben paket yöneticisi olarak yarn kullanıyorum siz tercihinize göre bunu değiştirebilirsiniz.

Öncelikle cluster-module adında bir klasör oluşturdum ve adım adım şunları uyguladım;

yarn init -y
yarn add express
yarn add autocannon
#or
npm init -y
npm install express
npm install autocannon

Autocannon, HTTP/1.1, HTTP/2 ve WebSocket protokollerini destekleyen bir yük testi aracıdır. Bu, HTTP uygulamalarının yüksek trafik yüklerine dayanıklılığını test etmek için kullanılır. Biz uygulamamızda test yerine yük bindirmek amaçlı kullanacağız.

Ardından cluster-app.js ve non-cluster-app.js adında 2 adet JavaScript dosyası oluşturdum ve sırayla şu kodları ekledim;

non-cluster-app.js

const express = require('express');
const app = express();
const port = 5001;

app.get('/', (req, res) => {
console.time('Request received');
const baseNumber = 7;
let result = 0;
for (let i = Math.pow(baseNumber, 7); i >= 0; i--) {
result += Math.atan(i) * Math.tan(i);
}
res.send({
result
})
console.log(`Process: ${process.pid} finished the request`)
console.timeEnd('Request received');
});

app.listen(port, () =>
console.log(`Example app http://localhost:${port}`)
);

cluster-app.js

const express = require('express');
const app = express();
const port = 5001;
const cluster = require('node:cluster');
const {availableParallelism} = require('node:os');

const numCPUs = availableParallelism();

const startServer = () => {
app.get('/', (req, res) => {
console.time('Request received');
const baseNumber = 7;
let result = 0;
for (let i = Math.pow(baseNumber, 7); i >= 0; i--) {
result += Math.atan(i) * Math.tan(i);
}
res.send({
result
})
console.log(`Process: ${process.pid} finished the request`)
console.timeEnd('Request received');
});
app.listen(port, () => {
console.log(`Example app http://localhost:${port}`);
});
}

if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
startServer();
}

package.json

{
"name": "cluster-module",
"version": "1.0.0",
"dependencies": {
"autocannon": "^7.12.0",
"express": "^4.18.2"
},
"scripts": {
"start:cluster": "node cluster-app.js",
"start:non-cluster": "node non-cluster-app.js",
"load": "autocannon http://localhost:5001 -d 10 -c 30 -w 3"
}
}

Uygulamalarımızı ayağa kaldırıp incelemek için hazır durumdayız. Öncelikle Cluster module kullanmadığımız serverı ayağa kaldıralım.

yarn start:non-cluster
#or
npm run start:non-cluster
non cluster

Cluster module kullanmadığımız uygulamamız ayakta ve yük bindirmeye hazır durumda. Yük bindirmeye başlayalım ve sonuçları elde etmeye başlayalım.

yarn load
#or
npm run load

Bize burada process id’si 20216olan tek bir işlemci eşlik ediyor. Yük testi aracı ile göndermiş olduğumuz bütün istekler20216 process id’li çekirdek ile işleniyor ve cevap döndürülüyor.

Şimdi Cluster module kullandığımız serverı ayağa kaldıralım.

yarn start:cluster
#or
npm run start:cluster
Benim cihazım 8 core 16 thread. Uygulamam totalde 16 örnekleme ile birlikte çalıştı. Bu sizin(sunucu veya şahsi cihazınız) işlemcinize göre değişkenlik gösterebilir.

Cluster module kullandığımız uygulamamız ayakta ve yük bindirmeye hazır durumda. Yük bindirmeye başlayalım ve sonuçları elde etmeye başlayalım.

yarn load
#or
npm run load

Cihazım, 8 core ve 16 thread. Uygulamamız, benim cihazımda toplamda 16 farklı örnekleme ile çalışıyor. Ancak, farklı cihazlarda bu sayılar değişebilir.

Cluster module, uygulamamızın arka planda bir V8 örneği oluşturur. Bu örnekler arasında iletişimi sağlamak için IPC (Inter-process communication — İşlem Arası İletişim) kullanır. IPC, işlemciler arasında veri ve bilgi alışverişini kolaylaştıran bir tekniktir. Bu sayede işlemciler arasında veri paylaşımı sağlanır.

Bu sistem, uygulamamızın daha etkili ve verimli çalışmasını mümkün kılar. Özellikle çoklu işlemci çekirdeklerini etkili bir şekilde kullanarak iş yükünü dengeler. Bu da uygulamanın daha hızlı ve akıcı çalışmasını sağlar.

IPC hakkında daha fazla bilgi için buraya göz atabilirsiniz.

Avantajlar:

  1. Performans Artışı: Uygulamanın çoklu işlemci çekirdekleri(multiple processor cores)’ni etkili bir şekilde kullanmasını sağlar. Bu da uygulamanın daha hızlı çalışmasını ve daha fazla işlem yapabilmesini mümkün kılar.
  2. Yük Dengeleme: Gelen istekleri farklı işlemcilere yönlendirerek yükü dengeler. Bu sayede, her bir işlemci çekirdeği eşit şekilde kullanılır ve uygulamanın performansı artar.
  3. Yüksek Erişilebilirlik: Birden fazla işlemci çekirdeği kullanılarak uygulamanın çalıştırılması, tek bir çekirdekli sistemlere göre daha yüksek bir erişilebilirlik sağlar. Eğer bir işlemci çekirdeği hata alırsa, diğerleri çalışmaya devam eder.
  4. Paralel İşlemler: Uygulama farklı iş parçacıklarında (thread) çalışabilir. Bu, birden fazla işi aynı anda yapabilmesini sağlar.

Dezavantajlar:

  1. Daha Fazla Bellek Kullanımı: Her bir işlemci çekirdeği için ayrı bir Node.js örneği oluşturulduğunda, bellek kullanımı artar. Bu, çok sayıda işlemci çekirdeği ile çalışırken önemli bir faktör olabilir.
  2. İletişim Karmaşıklığı: IPC kullanılması, işlemciler arasında veri alışverişi için ek bir katman ekler. Bu, ekstra işlemci kaynağı kullanımına ve düşükte olsa gecikme(ms) olarak geri dönebilir.
  3. Kod Karmaşıklığı: Kodun belirli kısımlarının değiştirilmesini gerektirir. Bu, bazen mevcut bir uygulamanın yeniden yapılandırılmasını ve uygun şekilde düzeltilmesini gerektirebilir.
  4. Hata Yönetimi: Cluster module doğru şekilde yapılandırılması ve kullanılması, hata yönetimini zorlaştırabilir. İşlemciler arası iletişim hataları ve iş parçacıklarının senkronizasyonu gibi konular dikkatle ele alınmalıdır.

Node.js Cluster module, doğru şekilde kullanıldığında uygulamanızın performansını ciddi şekilde artırabilir. Böylece uygulamanız daha hızlı çalışır ve daha fazla işlem yapabilir.

Bununla birlikte, Cluster module uygulamanız için uygun olup olmadığını değerlendirmek çok önemli. Eğer özellikle büyük, karmaşık projeleriniz varsa, bu modülün kullanımı gerçekten büyük bir fark yaratabilir. Ancak her proje farklıdır, bu nedenle ihtiyaçlarınıza göre bu modülün size uygun olup olmadığını değerlendirmeniz önemlidir.

Ayrıca, monolitik projelerin performansını daha da artırmak için Cluster module’ün kullanılması büyük bir adım olabilir. Bunun dışında farklı örnekleme yöntemleride mevcut. Her birinin avantajları ve dezavantajları bulunuyor. Bu nedenle, projenizin ihtiyaçlarına en uygun yöntemi belirlemek için dikkatli bir değerlendirme yapmanız önemlidir.

Buraya kadar okuduğunuz için teşekkür ederim. Olabildiğince bir çok konuya değinmek istedim ve umarım sizin ilginizi çekmiştir. Bir sonraki yazımda görüşmek dileğiyle :).

Kaynak koda buradan ulaşabilirsiniz.

--

--