1. 클러스터링: 멀티코어 CPU의 잠재력 극대화
Node.js는 기본적으로 단일 스레드에서 실행됩니다. 이는 I/O 바운드 작업에는 적합하지만, CPU를 많이 사용하는 작업에는 비효율적일 수 있습니다. 클러스터링은 이러한 한계를 극복하고 멀티코어 CPU의 잠재력을 최대한 활용할 수 있는 강력한 해법입니다.
1.1 클러스터링의 개념
클러스터링은 여러 개의 Node.js 프로세스(워커)를 생성하여 작업을 분산시키는 기술입니다. 각 워커는 독립적인 스레드에서 실행되므로, 멀티코어 프로세서의 성능을 최대한 활용하여 애플리케이션의 처리량을 높일 수 있습니다.
1.2 클러스터링의 이점
- 성능 향상: 여러 프로세스가 동시에 작업을 처리하여 전반적인 성능이 향상됩니다. 특히 CPU 집약적인 작업에서 큰 효과를 볼 수 있습니다.
- 신뢰성 향상: 하나의 워커 프로세스에 문제가 생겨도 다른 워커 프로세스들이 계속 작동하여 서비스 중단을 방지합니다.
- 부하 분산: 들어오는 요청이 여러 워커 프로세스에 분산되어 처리되므로 각 프로세스의 부하가 줄어들어 안정성이 향상됩니다.
1.3 클러스터링 구현: cluster
모듈 활용
Node.js는 내장 cluster
모듈을 제공하여 클러스터링을 쉽게 구현할 수 있습니다.
1.3.1 기본 예제: 요청 처리 분산
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// CPU 코어 수만큼 워커 프로세스를 생성합니다.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 워커 프로세스가 종료되면 새 워커를 생성합니다.
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
// 워커 프로세스는 HTTP 서버를 실행합니다.
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
코드 설명:
cluster.isMaster
를 확인하여 현재 프로세스가 마스터 프로세스인지 확인합니다.- 마스터 프로세스는
os.cpus().length
를 사용하여 CPU 코어 수를 가져옵니다. - CPU 코어 수만큼
cluster.fork()
를 호출하여 워커 프로세스를 생성합니다. cluster.on('exit', ...)
은 워커 프로세스가 종료되었을 때 발생하는 이벤트입니다. 이 이벤트를 사용하여 종료된 워커를 다시 시작할 수 있습니다.- 워커 프로세스는
http.createServer()
를 사용하여 HTTP 서버를 생성하고 8000 포트에서 요청을 수신합니다.
1.3.2 워커-마스터 통신 예제: 요청 횟수 추적
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// CPU 코어 수만큼 워커 프로세스를 생성
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 워커 종료 이벤트 핸들링
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
cluster.fork();
});
// 워커로부터 메시지 수신
cluster.on('message', (worker, message, handle) => {
if (message.cmd === 'notifyRequest') {
console.log(`Requests served by worker ${worker.process.pid}: ${message.count}`);
}
});
} else {
let requestCount = 0;
// 워커 프로세스에서 HTTP 서버 실행
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
// 요청 횟수 증가
requestCount++;
// 마스터 프로세스에 메시지 전송
process.send({ cmd: 'notifyRequest', count: requestCount });
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
코드 설명:
cluster.on('message', ...)
: 워커 프로세스로부터 메시지를 수신하는 이벤트 리스너입니다. 이 예제에서는 워커가 처리한 요청 횟수를 받아서 로깅합니다.process.send(...)
: 워커 프로세스에서 마스터 프로세스로 메시지를 보냅니다. 이 예제에서는 처리한 요청 횟수를 보냅니다.
1.3.3 CPU 집약적 작업 처리 예제:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// 마스터 프로세스에서 워커 생성 및 온라인 이벤트 처리
for (let i = 0; i < numCPUs; i++) {
const worker = cluster.fork();
// 워커 온라인 이벤트 핸들링
worker.on('online', () => {
console.log(`Worker ${worker.process.pid} is online`);
});
}
// 워커 종료 이벤트 핸들링
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
// 각 워커에서 서버 설정 및 요청 처리
http.createServer((req, res) => {
// CPU 집약적인 작업 시뮬레이션
let sum = 0;
for (let i = 0; i < 1e8; i++) {
sum += i;
}
res.writeHead(200);
res.end(`Hello from worker ${process.pid}, sum: ${sum}\n`);
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
코드 설명:
worker.on('online', ...)
: 각 워커가 온라인 상태가 되었을 때 발생하는 이벤트 리스너를 추가합니다. 이를 통해 워커가 준비되었음을 확인할 수 있습니다.- CPU 집약적인 작업 시뮬레이션: 이 예제에서는 큰 숫자를 더하는 연산을 통해 CPU를 많이 사용하는 작업을 시뮬레이션합니다. 이를 통해 클러스터링이 CPU 바운드 작업의 성능을 어떻게 향상시키는 지 확인할 수 있습니다.
1.4 클러스터링 적용 사례
- 웹 서버 구축: 대규모 트래픽을 처리하는 웹 애플리케이션에서 클러스터링을 사용하여 성능과 안정성을 높일 수 있습니다.
- API 서비스: RESTful API 서비스에서 클러스터링을 사용하여 다수의 동시 연결을 효과적으로 처리할 수 있습니다.
- 실시간 데이터 처리: 주식 시장 데이터나 센서 데이터와 같이 실시간으로 대량의 데이터를 처리해야 하는 애플리케이션에서 클러스터링을 활용하여 처리 속도를 높일 수 있습니다.
2. 로드 밸런싱: 트래픽 분산으로 안정성과 확장성 확보
로드 밸런싱은 여러 서버에 트래픽을 분산하여 단일 서버의 과부하를 방지하고 애플리케이션의 가용성과 응답성을 향상시키는 필수 기술입니다.
2.1 로드 밸런싱의 개념
로드 밸런서는 클라이언트의 요청을 여러 서버 인스턴스에 분산하여 각 서버의 부하를 균등하게 유지합니다. 이를 통해 특정 서버에 장애가 발생해도 다른 서버들이 계속해서 서비스를 제공할 수 있어, 고가용성을 보장합니다.
2.2 로드 밸런싱의 이점
- 성능 향상: 여러 서버가 동시에 요청을 처리하여 응답 시간이 단축됩니다.
- 신뢰성 및 가용성 향상: 단일 서버 장애가 전체 서비스에 영향을 미치지 않습니다.
- 확장성: 트래픽 증가에 따라 서버를 쉽게 추가하여 확장할 수 있습니다.
- 유지 보수 용이성: 개별 서버를 중단 없이 업데이트하거나 유지 보수할 수 있습니다.
2.3 로드 밸런서의 유형
- 소프트웨어 기반 로드 밸런서: Nginx, HAProxy와 같이 서버에 설치되는 소프트웨어입니다. 구성이 유연하고 비용 효율적입니다.
- 하드웨어 기반 로드 밸런서: F5 BIG-IP와 같이 전용 하드웨어 장비입니다. 고성능과 고급 기능을 제공하지만 비용이 높습니다.
- 클라우드 기반 로드 밸런서: AWS Elastic Load Balancing, Google Cloud Load Balancer와 같이 클라우드 제공업체에서 제공하는 서비스입니다. 자동 확장과 높은 가용성을 제공합니다.
2.4 Node.js와 로드 밸런싱: Nginx 활용
Node.js 애플리케이션은 Nginx와 같은 소프트웨어 기반 로드 밸런서나 클라우드 제공업체의 로드 밸런싱 서비스를 사용하여 쉽게 로드 밸런싱을 구현할 수 있습니다.
2.4.1 Nginx를 사용한 로드 밸런싱 예제
Nginx를 로드 밸런서로 사용하여 여러 Node.js 서버 인스턴스에 트래픽을 분산하는 예제를 살펴봅니다.
Nginx 설치:
sudo apt-get update sudo apt-get install nginx
Nginx 설정:
/etc/nginx/sites-available/default
파일을 열고 다음과 같이 수정합니다.http { upstream node_app { server 192.168.0.101:3000; # 첫 번째 Node.js 서버 server 192.168.0.102:3000; # 두 번째 Node.js 서버 server 192.168.0.103:3000; # 세 번째 Node.js 서버 (선택 사항) } server { listen 80; location / { proxy_pass http://node_app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } }
설정 설명:
upstream
: 로드 밸런싱 대상 서버 그룹을 정의합니다.server
: 각 서버의 IP 주소와 포트를 지정합니다.proxy_pass
: 요청을node_app
upstream 그룹으로 전달합니다.proxy_http_version
,proxy_set_header
,proxy_cache_bypass
: WebSocket 및 기타 고급 기능을 지원하기 위한 설정입니다.
Nginx 재시작:
sudo systemctl restart nginx
이제 Nginx는 80 포트로 들어오는 요청을 node_app
upstream 그룹에 정의된 Node.js 서버들에 라운드 로빈(Round Robin) 방식으로 분산합니다.
2.4.2 최소 연결 (Least Connections) 알고리즘
http {
upstream node_app {
least_conn; # 최소 연결 알고리즘 사용
server 192.168.0.101:3000;
server 192.168.0.102:3000;
server 192.168.0.103:3000;
}
server {
listen 80;
location / {
proxy_pass http://node_app;
# ... (다른 설정)
}
}
}
설정 설명:
least_conn;
:upstream
블록 내에least_conn;
지시문을 추가하여 최소 연결 알고리즘을 활성화합니다. 이 알고리즘은 현재 활성 연결 수가 가장 적은 서버로 요청을 전달합니다.
2.4.3 IP 해시 (IP Hash) 알고리즘
http {
upstream node_app {
ip_hash; # IP 해시 알고리즘 사용
server 192.168.0.101:3000;
server 192.168.0.102:3000;
server 192.168.0.103:3000;
}
server {
listen 80;
location / {
proxy_pass http://node_app;
# ... (다른 설정)
}
}
}
설정 설명:
ip_hash;
:upstream
블록 내에ip_hash;
지시문을 추가하여 IP 해시 알고리즘을 활성화합니다. 이 알고리즘은 클라이언트의 IP 주소를 해싱하여 동일한 클라이언트의 요청을 항상 동일한 서버로 전달합니다. 이를 통해 세션 지속성(session persistence)을 유지할 수 있습니다.
3. 캐싱: 데이터 재사용으로 성능 극대화
캐싱은 자주 사용되는 데이터나 연산 결과를 임시 저장소에 저장하여, 동일한 요청이 들어왔을 때 빠르게 응답할 수 있도록 하는 기술입니다. 이를 통해 성능 향상, 서버 부하 감소, 비용 절감 효과를 얻을 수 있습니다.
3.1 캐싱의 개념
캐싱은 데이터나 계산 결과를 임시 저장소(캐시)에 저장하여, 동일한 요청이 들어왔을 때 원본 데이터 소스(예: 데이터베이스)에 접근하지 않고 캐시에서 데이터를 빠르게 반환하는 기술입니다.
3.2 캐싱의 중요성
- 성능 향상: 데이터베이스나 외부 API에 대한 접근 횟수를 줄여 응답 시간을 대폭 단축합니다.
- 서버 부하 감소: 반복적인 데이터 요청을 캐시에서 처리하여 백엔드 서버의 부하를 줄입니다.
- 비용 절감: 데이터베이스나 클라우드 리소스 사용량을 줄여 비용을 절감할 수 있습니다.
- 확장성 향상: 캐싱을 통해 애플리케이션의 확장성을 향상시킬 수 있습니다.
3.3 Node.js에서의 캐싱 구현
3.3.1 인-메모리(In-Memory) 캐싱
가장 간단한 캐싱 방법으로, 애플리케이션의 메모리에 데이터를 저장합니다.
3.3.1.1. 직접 구현 (LRU 알고리즘 적용)
const cache = {};
const MAX_CACHE_SIZE = 100; // 최대 캐시 크기
let cacheSize = 0;
function getData(key) {
if (cache[key]) {
console.log('Cache hit');
// 캐시 항목 접근 시간 업데이트 (LRU 구현)
cache[key].accessed = Date.now();
return cache[key].data;
} else {
console.log('Cache miss');
const data = fetchFromDatabase(key);
// 캐시 크기 제한
if (cacheSize >= MAX_CACHE_SIZE) {
evictLRU(); // 가장 오랫동안 사용되지 않은 항목 제거
}
cache[key] = { data: data, accessed: Date.now() };
cacheSize++;
return data;
}
}
function fetchFromDatabase(key) {
// 데이터베이스에서 데이터를 가져오는 로직 (가정)
if (key === 'user1') {
return { name: 'John Doe', id: 1 };
}
return null;
}
function evictLRU() {
let oldestKey = null;
let oldestAccessed = Infinity;
for (const key in cache) {
if (cache[key].accessed < oldestAccessed) {
oldestAccessed = cache[key].accessed;
oldestKey = key;
}
}
if (oldestKey) {
console.log(`Evicting ${oldestKey} from cache`);
delete cache[oldestKey];
cacheSize--;
}
}
// 사용 예
const user1Data = getData('user1'); // 처음 호출: Cache miss
console.log(user1Data);
const user1DataAgain = getData('user1'); // 두 번째 호출: Cache hit
console.log(user1DataAgain);
코드 설명:
cache
객체는 키-값 쌍으로 데이터를 저장하는 간단한 인메모리 캐시입니다.getData
함수는 먼저 캐시에서 데이터를 찾습니다.- 캐시에 데이터가 있으면(Cache hit) 즉시 반환합니다. 이 때, 해당 캐시 항목의 접근 시간을
Date.now()
로 업데이트하여 LRU(Least Recently Used) 알고리즘을 구현합니다. - 캐시에 데이터가 없으면(Cache miss)
fetchFromDatabase
함수를 호출하여 데이터베이스에서 데이터를 가져옵니다. MAX_CACHE_SIZE
상수를 사용하여 캐시의 최대 크기를 제한합니다.cacheSize
변수는 현재 캐시에 저장된 항목의 수를 추적합니다.evictLRU
함수는 캐시가 가득 찼을 때 가장 오랫동안 사용되지 않은 항목을 제거합니다.
3.3.1.2. node-cache
라이브러리 활용
const NodeCache = require('node-cache');
const myCache = new NodeCache({ stdTTL: 600, checkperiod: 120 }); // TTL: 600초, checkperiod: 120초
function getDataWithNodeCache(key) {
const value = myCache.get(key);
if (value) {
console.log('Cache hit');
return value;
} else {
console.log('Cache miss');
const data = fetchFromDatabase(key);
myCache.set(key, data); // 데이터를 캐시에 저장
return data;
}
}
// ... (fetchFromDatabase 함수는 이전 예제와 동일)
// 사용 예:
(async () => {
const user1Data = getDataWithNodeCache('user1');
console.log(user1Data);
const user1DataAgain = getDataWithNodeCache('user1');
console.log(user1DataAgain);
})();
코드 설명:
node-cache
모듈을 사용하여NodeCache
객체를 생성합니다.stdTTL
옵션은 캐시 항목의 기본 만료 시간(초)을 설정합니다.checkperiod
옵션은 만료된 항목을 정리하는 주기(초)를 설정합니다.getDataWithNodeCache
함수는myCache.get(key)
을 사용하여 캐시에서 데이터를 가져옵니다.- 데이터가 있으면(Cache hit) 반환합니다.
- 데이터가 없으면(Cache miss)
fetchFromDatabase
함수를 호출하여 데이터베이스에서 데이터를 가져오고,myCache.set(key, data)
를 사용하여 데이터를 캐시에 저장합니다.
3.3.2 파일 시스템 캐싱
파일 시스템을 캐시 저장소로 사용하는 방법입니다.
3.3.2.1. 기본적인 파일 시스템 캐싱
const fs = require('fs');
const path = require('path');
function readFileWithCache(filePath) {
const cacheFilePath = path.join(__dirname, 'cache', path.basename(filePath) + '.cache');
try {
if (fs.existsSync(cacheFilePath)) {
console.log('Cache hit');
const stats = fs.statSync(cacheFilePath);
const now = Date.now();
const cacheDuration = 60 * 60 * 1000; // 캐시 지속 시간: 1시간
// 캐시 파일이 만료되었는지 확인
if (now - stats.mtimeMs > cacheDuration) {
console.log('Cache expired');
fs.unlinkSync(cacheFilePath); // 만료된 캐시 파일 삭제
throw new Error('Cache expired'); // 강제로 Cache miss 발생
}
// 캐시 파일이 존재하면 캐시에서 읽기
return fs.readFileSync(cacheFilePath, 'utf-8');
} else {
throw new Error('Cache miss'); // 강제로 Cache miss 발생
}
} catch (err) {
console.log(err.message);
// 캐시 접근에 실패하거나 캐시 파일이 없으면 원본 파일을 읽고 캐시에 저장
const content = fs.readFileSync(filePath, 'utf-8');
// 캐시 디렉토리가 없으면 생성
const cacheDir = path.dirname(cacheFilePath);
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
fs.writeFileSync(cacheFilePath, content);
return content;
}
}
// 사용 예:
const filePath = path.join(__dirname, 'data.txt'); // 캐싱할 파일 경로
const fileContent = readFileWithCache(filePath); // 처음 호출: Cache miss, 파일을 읽고 캐시에 저장
console.log(fileContent);
const fileContentAgain = readFileWithCache(filePath); // 두 번째 호출: Cache hit, 캐시에서 파일 내용 반환
console.log(fileContentAgain);
코드 설명:
readFileWithCache
함수는 먼저 캐시 파일이 존재하는지 확인합니다.- 캐시 파일이 존재하면 캐시에서 파일 내용을 읽어서 반환합니다.
- 캐시 파일이 존재하지 않으면 원본 파일을 읽고 캐시 파일에 저장한 후 파일 내용을 반환합니다.
fs.statSync
를 사용하여 캐시 파일의 수정 시간을 확인하고, 현재 시간과 비교하여 캐시가 만료되었는지 확인합니다.- 만료된 캐시 파일은
fs.unlinkSync
를 사용하여 삭제합니다. fs.mkdirSync
를 사용하여 캐시 디렉토리가 없으면 생성합니다.
3.3.2.2. 파일 내용 기반 해싱을 통한 캐싱
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
function hashContent(content) {
return crypto.createHash('sha256').update(content).digest('hex');
}
function readFileWithCacheAndHash(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const contentHash = hashContent(content);
const cacheFilePath = path.join(__dirname, 'cache', `${path.basename(filePath)}.${contentHash}.cache`);
try {
if (fs.existsSync(cacheFilePath)) {
console.log('Cache hit');
return fs.readFileSync(cacheFilePath, 'utf-8');
} else {
console.log('Cache miss');
fs.writeFileSync(cacheFilePath, content);
return content;
}
} catch (err) {
console.error('Error accessing cache:', err);
return content;
}
}
// 사용 예:
const filePath = path.join(__dirname, 'data.txt');
const fileContent = readFileWithCacheAndHash(filePath);
console.log(fileContent);
const fileContentAgain = readFileWithCacheAndHash(filePath);
console.log(fileContentAgain);
코드 설명:
hashContent
함수는 파일 내용의 SHA256 해시를 계산합니다.readFileWithCacheAndHash
함수는 먼저 파일 내용을 읽고 해시를 계산합니다.- 캐시 파일 이름은 원본 파일 이름과 해시 값을 조합하여 생성합니다.
- 해당 해시 값을 가진 캐시 파일이 존재하면 캐시에서 파일 내용을 읽어서 반환합니다.
- 캐시 파일이 존재하지 않으면 원본 파일을 읽고 캐시 파일에 저장한 후 파일 내용을 반환합니다.
3.3.3 외부 캐시 솔루션: Redis
Redis와 같은 인메모리 데이터 스토어를 사용하여 캐시를 구현할 수 있습니다. Redis는 고성능과 다양한 데이터 구조를 지원하여 복잡한 캐싱 시나리오에 적합합니다.
3.3.3.1. Redis 기본 사용 예제
const redis = require('redis');
const client = redis.createClient(); // 기본 설정으로 Redis 서버에 연결
// Redis 클라이언트 에러 핸들링
client.on('error', (err) => {
console.log('Redis Client Error', err);
});
async function getDataFromRedis(key) {
try {
await client.connect(); // Redis 서버에 연결
const result = await client.get(key); // Redis에서 데이터 가져오기
if (result) {
console.log('Cache hit:', key);
await client.disconnect(); // 연결 종료
return JSON.parse(result); // Cache hit: 저장된 데이터 반환
} else {
console.log('Cache miss:', key);
const data = fetchFromDatabase(key); // 데이터베이스에서 데이터 조회
// 트랜잭션을 사용하여 데이터 저장 및 만료 시간 설정
const multi = client.multi();
multi.set(key, JSON.stringify(data));
multi.expire(key, 3600); // 1시간 후 만료
await multi.exec();
await client.disconnect(); // 연결 종료
return data;
}
} catch (err) {
console.error('Error accessing Redis:', err);
await client.disconnect(); // 에러 발생 시 연결 종료
return fetchFromDatabase(key); // Redis 접근에 실패하면 데이터베이스에서 데이터를 가져옴
}
}
function fetchFromDatabase(key) {
// 데이터베이스에서 데이터를 가져오는 로직 (가정)
if (key === 'user1') {
return { name: 'John Doe', id: 1 };
}
return null;
}
// 사용 예:
(async () => {
const user1Data = await getDataFromRedis('user1'); // 처음 호출: Cache miss
console.log(user1Data);
const user1DataAgain = await getDataFromRedis('user1'); // 두 번째 호출: Cache hit
console.log(user1DataAgain);
})();
코드 설명:
redis
모듈을 사용하여 Redis 클라이언트를 생성합니다.client.connect()
를 사용하여 Redis 서버에 연결합니다.client.get(key)
을 사용하여 Redis에서 데이터를 가져옵니다.- 데이터가 있으면(Cache hit) JSON.parse를 사용하여 데이터를 파싱하고 반환합니다.
- 데이터가 없으면(Cache miss)
fetchFromDatabase
함수를 호출하여 데이터베이스에서 데이터를 가져옵니다. client.multi()
를 사용하여 트랜잭션을 시작합니다.multi.set(key, JSON.stringify(data))
를 사용하여 데이터를 Redis에 저장합니다.multi.expire(key, 3600)
을 사용하여 데이터의 만료 시간을 1시간(3600초)으로 설정합니다.multi.exec()
를 사용하여 트랜잭션을 실행합니다.client.disconnect()
를 사용하여 Redis 서버와의 연결을 종료합니다.- 에러 핸들링을 위해
try...catch
블록을 사용합니다.
3.3.3.2. Redis 파이프라인 활용 예제
async function getDataFromRedisWithPipeline(keys) {
try {
await client.connect();
const pipeline = client.pipeline(); // 파이프라인 생성
// 여러 키에 대한 GET 명령을 파이프라인에 추가
keys.forEach(key => {
pipeline.get(key);
});
const results = await pipeline.exec(); // 파이프라인 실행
const data = {};
results.forEach((result, index) => {
if (result) {
console.log('Cache hit:', keys[index]);
data[keys[index]] = JSON.parse(result);
} else {
console.log('Cache miss:', keys[index]);
const dbData = fetchFromDatabase(keys[index]);
client.setEx(keys[index], 3600, JSON.stringify(dbData)); // 데이터를 캐시에 저장
data[keys[index]] = dbData;
}
});
await client.disconnect();
return data;
} catch (err) {
console.error('Error accessing Redis:', err);
await client.disconnect();
// Redis 접근에 실패하면 데이터베이스에서 데이터를 가져옴
const data = {};
keys.forEach(key => {
data[key] = fetchFromDatabase(key);
});
return data;
}
}
// 사용 예:
(async () => {
const data = await getDataFromRedisWithPipeline(['user1', 'user2', 'product1']);
console.log(data);
})();
코드 설명:
getDataFromRedisWithPipeline
함수는 여러 키를 배열로 받아서 한 번에 여러 데이터를 가져옵니다.client.pipeline()
을 사용하여 파이프라인을 생성합니다.keys.forEach
루프를 사용하여 각 키에 대한get
명령을 파이프라인에 추가합니다.pipeline.exec()
를 사용하여 파이프라인에 추가된 모든 명령을 한 번에 실행합니다.results
배열에는 각get
명령의 결과가 순서대로 저장됩니다.results.forEach
루프를 사용하여 각 결과를 확인하고, 캐시 히트 여부에 따라 데이터를 처리합니다.- 캐시 미스가 발생한 경우
fetchFromDatabase
함수를 호출하여 데이터베이스에서 데이터를 가져오고,client.setEx
를 사용하여 데이터를 캐시에 저장합니다.
결론
클러스터링, 로드 밸런싱, 캐싱은 Node.js 애플리케이션의 성능을 최적화하고 확장성과 안정성을 확보하는 데 필수적인 기술입니다.
- 클러스터링은 멀티코어 CPU의 활용도를 극대화하여 CPU 집약적인 작업의 성능을 향상시킵니다.
- 로드 밸런싱은 트래픽을 여러 서버에 분산하여 단일 서버의 과부하를 방지하고 가용성을 향상시킵니다.
- 캐싱은 데이터나 연산 결과를 재사용하여 응답 시간을 단축하고 서버 부하를 감소시킵니다.
'프로그래밍 > Node.js' 카테고리의 다른 글
Node.js의 빛나는 미래: 커뮤니티, 생태계, 그리고 진화하는 기술 트렌드 (1) | 2025.02.18 |
---|---|
Node.js 보안: 안전한 웹 애플리케이션 개발을 위한 가이드 (0) | 2025.02.18 |
Node.js: 현대 웹 개발의 핵심, 실전 활용 가이드 (0) | 2025.02.18 |
Node.js: 서버 개발의 새로운 패러다임 - 심층 분석 (0) | 2025.02.18 |
Node.js 웹 개발: Express.js와 Koa.js 프레임워크 비교 (0) | 2025.02.18 |