Node.js๋กœ ๊ณ ์„ฑ๋Šฅ ์›น์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ: Cluster์™€ Worker Threads

2025. 9. 28. 19:22ยท๐ŸŒ WEB
728x90

๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ์„ ๊ฒฌ๋””๋Š” ๊ณ ์„ฑ๋Šฅ ์›น์„œ๋ฒ„ ๊ตฌํ˜„ํ•˜๊ธฐ

Node.js๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ ์ด๋ฒคํŠธ ๋ฃจํ”„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์—ฌํƒœ๊นŒ์ง€๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” HTTP ์›น ์„œ๋ฒ„๋ฅผ ๋‹ค๋ฃจ์—ˆ๋Š”๋ฐ์š”. ๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ ์š”์ฒญ์˜ ์ˆ˜๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค๋ฉด, ๋ฉ€ํ‹ฐ ์ฝ”์–ด CPU ํ™˜๊ฒฝ์—์„œ ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๋งŒ ์‚ฌ์šฉํ•ด์„œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋‹ค์†Œ ์•„์‰ฝ๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋งŒ์•ฝ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ํ™œ์šฉํ•ด์„œ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ๋Š” ์˜์—ญ์ด๋ผ๋ฉด ๊ตณ์ด ์ธ์Šคํ„ด์Šค๋ฅผ ์ˆ˜ํ‰ ํ™•์žฅํ•  ํ•„์š”๋„ ์—†๊ธฐ์— ์žˆ๋Š” ์ž์›์„ ์ตœ๋Œ€ํ•œ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 

๋”ฐ๋ผ์„œ ์›น ์„œ๋ฒ„๋ฅผ ๊ฐ๊ฐ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค/ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ด๋ณด๊ณ  ๊ฐ ๋ฐฉ์‹์„ ๋น„๊ต ๋ถ„์„ํ•˜๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

1. cluster ๋ชจ๋“ˆ์„ ํ™œ์šฉํ•ด CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑ

2. Worker Threads ๊ธฐ๋ฐ˜ ์ž‘์—… ์ฒ˜๋ฆฌ


์™œ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค/์Šค๋ ˆ๋“œ๊ฐ€ ํ•„์š”ํ• ๊นŒ?

Node.js์˜ ๋‹จ์ผ ์Šค๋ ˆ๋“œ ๋ชจ๋ธ์€ I/O ์ž‘์—…์—์„œ๋Š” ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ์„ ๋ณด์ด์ง€๋งŒ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • CPU ์ง‘์•ฝ์  ์ž‘์—…: ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ, ์•”ํ˜ธํ™”, ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋“ฑ
  • ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ™œ์šฉ: 8์ฝ”์–ด CPU์—์„œ 1์ฝ”์–ด๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋‚ญ๋น„
  • ์žฅ์•  ๊ฒฉ๋ฆฌ: ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ฃฝ์–ด๋„ ์„œ๋น„์Šค๋Š” ๊ณ„์† ์šด์˜๋˜์–ด์•ผ ํ•จ

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‘ ๊ฐ€์ง€ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. Cluster ๋ชจ๋“ˆ: ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•ด ํฌํŠธ๋ฅผ ๊ณต์œ 
  2. Worker Threads: ๊ฐ™์€ ํ”„๋กœ์„ธ์Šค ๋‚ด์—์„œ ๋ณ„๋„ ์Šค๋ ˆ๋“œ๋กœ ๋ฌด๊ฑฐ์šด ์ž‘์—… ์ฒ˜๋ฆฌ

Part 1: Cluster๋กœ ํ”„๋กœ์„ธ์Šค ๋ณ‘๋ ฌํ™”ํ•˜๊ธฐ

๊ธฐ๋ณธ ๊ฐœ๋…

Cluster ๋ชจ๋“ˆ์€ Master-Worker ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • Master: ์›Œ์ปค๋“ค์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌ
  • Workers: ์‹ค์ œ HTTP ์š”์ฒญ์„ ์ฒ˜๋ฆฌ

ํ•ต์‹ฌ์€ ๋ชจ๋“  ์›Œ์ปค๊ฐ€ ๊ฐ™์€ ํฌํŠธ๋ฅผ ๊ณต์œ ํ•˜๋ฉด์„œ๋„ OS ๋ ˆ๋ฒจ์—์„œ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ์ด ๋œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

์‹ค์ „ ๊ตฌํ˜„: net ๋ชจ๋“ˆ ๊ธฐ๋ฐ˜ Cluster ์„œ๋ฒ„

๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” webserver/server.js์— Cluster ๋ชจ๋“œ๊ฐ€ ์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

// webserver/server.js (์‹ค์ œ ๊ตฌํ˜„๋œ ์ฝ”๋“œ)
import cluster from 'cluster';
import os from 'os';
import net from 'net';

// Cluster ๋ชจ๋“œ ์„ค์ • (ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋˜๋Š” ๋ช…๋ นํ–‰ ์ธ์ˆ˜๋กœ ์ œ์–ด)
const USE_CLUSTER = process.env.USE_CLUSTER === 'true' || process.argv.includes('--cluster');
const NUM_WORKERS = process.env.NUM_WORKERS ? parseInt(process.env.NUM_WORKERS) : os.cpus().length;

console.log(`๐Ÿ”ง Cluster ๋ชจ๋“œ: ${USE_CLUSTER ? 'ํ™œ์„ฑํ™”' : '๋น„ํ™œ์„ฑํ™”'}`);

// HTTP ์„œ๋ฒ„ ์ƒ์„ฑ ํ•จ์ˆ˜
function createServer() {
  const server = net.createServer((socket) => {
    // TCP ์†Œ์ผ“์„ ์ง์ ‘ ๋‹ค๋ฃจ๋Š” ์ˆœ์ˆ˜ net ๋ชจ๋“ˆ ๊ธฐ๋ฐ˜ ๊ตฌํ˜„
    // ... HTTP ์š”์ฒญ ํŒŒ์‹ฑ ๋ฐ ์‘๋‹ต ์ฒ˜๋ฆฌ ๋กœ์ง
  });

  return server;
}

// Cluster ๋ชจ๋“œ์— ๋”ฐ๋ฅธ ์„œ๋ฒ„ ์‹œ์ž‘
if (USE_CLUSTER && cluster.isPrimary) {
  console.log(`๐Ÿš€ Cluster ๋ชจ๋“œ ์‹œ์ž‘: ${NUM_WORKERS}๊ฐœ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ`);
  console.log(`๐Ÿ“Š CPU ์ฝ”์–ด ์ˆ˜: ${os.cpus().length}`);

  // ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ
  for (let i = 0; i < NUM_WORKERS; i++) {
    const worker = cluster.fork();
    console.log(`๐Ÿ”ง ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ๋จ: PID ${worker.process.pid}`);
  }

  // ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹œ ์ž๋™ ์žฌ์‹œ์ž‘
  cluster.on('exit', (worker, code, signal) => {
    console.log(
      `๐Ÿ’€ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ: PID ${worker.process.pid} (์ฝ”๋“œ: ${code}, ์‹œ๊ทธ๋„: ${signal})`
    );

    if (!worker.exitedAfterDisconnect) {
      console.log('์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์žฌ์‹œ์ž‘ ์ค‘...');
      const newWorker = cluster.fork();
      console.log(`๐Ÿ”ง ์ƒˆ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ๋จ: PID ${newWorker.process.pid}`);
    }
  });

  // ๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค Graceful Shutdown
  process.on('SIGINT', () => {
    console.log('\n๐Ÿ›‘ ๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹ ํ˜ธ ์ˆ˜์‹ ');
    console.log('๋ชจ๋“  ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์— ์ข…๋ฃŒ ์‹ ํ˜ธ ์ „์†ก...');

    for (const id in cluster.workers) {
      cluster.workers[id].kill();
    }

    setTimeout(() => {
      console.log('๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ');
      process.exit(0);
    }, 2000);
  });
} else {
  // ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ๋ชจ๋“œ ๋˜๋Š” ์›Œ์ปค ํ”„๋กœ์„ธ์Šค
  const server = createServer();
  const PORT = 80;
  const HOST = '::'; // IPv6 + IPv4 ๋ชจ๋‘ ์ˆ˜์šฉ

  server.listen(PORT, HOST, () => {
    const mode = USE_CLUSTER ? `์›Œ์ปค ํ”„๋กœ์„ธ์Šค (PID: ${process.pid})` : '๋‹จ์ผ ํ”„๋กœ์„ธ์Šค';
    console.log(`๐Ÿš€ Codestargram Web Server running on http://${HOST}:${PORT} [${mode}]`);
  });
}

์‹คํ–‰ ๋ฐฉ๋ฒ•

# ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ๋ชจ๋“œ (๊ธฐ๋ณธ๊ฐ’)
npm start

# Cluster ๋ชจ๋“œ ํ™œ์„ฑํ™” (CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ ์›Œ์ปค ์ƒ์„ฑ)
USE_CLUSTER=true npm start
# ๋˜๋Š”
node server.js --cluster

# ์›Œ์ปค ์ˆ˜ ์ง€์ •
NUM_WORKERS=4 USE_CLUSTER=true npm start

์ฃผ์š” ํฌ์ธํŠธ

  1. ์ž๋™ ์žฌ์‹œ์ž‘: ์›Œ์ปค๊ฐ€ ์ฃฝ์œผ๋ฉด ์ฆ‰์‹œ ์ƒˆ๋กœ์šด ์›Œ์ปค๋ฅผ ์ƒ์„ฑ
  2. Zero Downtime: ํ•œ ์›Œ์ปค๊ฐ€ ์ฃฝ์–ด๋„ ๋‚˜๋จธ์ง€ ์›Œ์ปค๋“ค์ด ์š”์ฒญ ์ฒ˜๋ฆฌ
  3. ๋ชจ๋‹ˆํ„ฐ๋ง: Master๊ฐ€ ์›Œ์ปค๋“ค์˜ ์ƒํƒœ๋ฅผ ์ถ”์ 

Part 2: Worker Threads๋กœ CPU ์ž‘์—… ๋ถ„๋ฆฌํ•˜๊ธฐ

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

  • ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•/์••์ถ•
  • ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ํŒŒ์‹ฑ (CSV, JSON)
  • ์•”ํ˜ธํ™”/๋ณตํ˜ธํ™” ์—ฐ์‚ฐ
  • ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜

Worker Thread ๊ตฌํ˜„ ์˜ˆ์‹œ

// backend/src/workers/image-processor.ts
import { Worker } from 'worker_threads';
import path from 'path';

export interface ImageProcessTask {
  imagePath: string;
  targetWidth: number;
  targetHeight: number;
}

export function processImageAsync(task: ImageProcessTask): Promise<string> {
  return new Promise((resolve, reject) => {
    const worker = new Worker(path.join(__dirname, 'image-worker.js'), {
      workerData: task
    });

    worker.on('message', (result) => {
      resolve(result.outputPath);
    });

    worker.on('error', reject);

    worker.on('exit', (code) => {
      if (code !== 0) {
        reject(new Error(`Worker stopped with exit code ${code}`));
      }
    });
  });
}
// backend/src/workers/image-worker.js
import { parentPort, workerData } from 'worker_threads';
import sharp from 'sharp';
import path from 'path';

const { imagePath, targetWidth, targetHeight } = workerData;

(async () => {
  try {
    const outputPath = path.join(
      path.dirname(imagePath),
      `resized_${path.basename(imagePath)}`
    );

    await sharp(imagePath)
      .resize(targetWidth, targetHeight, {
        fit: 'cover',
        position: 'center'
      })
      .toFile(outputPath);

    parentPort?.postMessage({ outputPath });
  } catch (error) {
    throw error;
  }
})();

๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ API์— ์ ์šฉํ•˜๊ธฐ

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์˜ ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ๋กœ์ง(backend/src/routes/postRoutes.ts:224-382)์— Worker Thread๋ฅผ ํ†ตํ•ฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

// backend/src/routes/postRoutes.ts
import { processImageAsync } from '../workers/image-processor.js';

async function createPostWithMultipart(request: HttpRequest): Promise<HttpResponse> {
  try {
    const multipartData = request.multipartData;

    // ... ๊ธฐ์กด ๊ฒ€์ฆ ๋กœ์ง ...

    let savedImageUrl = null;

    if (multipartData.files.length > 0) {
      const imageFile = multipartData.files.find((file) => file.name === 'image');

      if (imageFile) {
        // ์›๋ณธ ํŒŒ์ผ ์ €์žฅ
        const saveResult = await saveUploadedFile(imageFile.data, imageFile.filename);

        // Worker Thread๋กœ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• (๋น„๋™๊ธฐ, ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ฐจ๋‹จ ์—†์Œ)
        const processedPath = await processImageAsync({
          imagePath: saveResult.path,
          targetWidth: 800,
          targetHeight: 600
        });

        savedImageUrl = `/uploads/${path.basename(processedPath)}`;
        console.log(`โœ… ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: ${processedPath}`);
      }
    }

    // ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ
    const newPost = {
      id: (postIdCounter++).toString(),
      userId: 'anonymous',
      username: 'Anonymous',
      content: multipartData.fields.content,
      imageUrl: savedImageUrl,
      likes: 0,
      comments: [],
      createdAt: new Date().toISOString(),
    };

    mockPosts.push(newPost);

    return {
      statusCode: 201,
      contentType: 'application/json',
      body: JSON.stringify({
        success: true,
        message: '๊ฒŒ์‹œ๊ธ€์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
        data: newPost,
        timestamp: new Date().toISOString(),
      }),
    };
  } catch (error) {
    console.error('Post creation error:', error);
    // ... ์—๋Ÿฌ ์ฒ˜๋ฆฌ ...
  }
}

Part 3: Worker Pool ํŒจํ„ด์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋งค๋ฒˆ Worker๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Worker Pool์„ ๊ตฌํ˜„ํ•ด ์žฌ์‚ฌ์šฉํ•˜๋ฉด ํ›จ์”ฌ ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

// backend/src/workers/worker-pool.ts
import { Worker } from 'worker_threads';
import path from 'path';

export class WorkerPool {
  private workers: Worker[] = [];
  private availableWorkers: Worker[] = [];
  private taskQueue: Array<{
    data: any;
    resolve: (value: any) => void;
    reject: (error: any) => void;
  }> = [];

  constructor(private workerScript: string, private poolSize: number) {
    this.initialize();
  }

  private initialize() {
    for (let i = 0; i < this.poolSize; i++) {
      const worker = new Worker(this.workerScript);
      this.workers.push(worker);
      this.availableWorkers.push(worker);

      worker.on('message', (result) => {
        this.availableWorkers.push(worker);
        this.processQueue();
      });

      worker.on('error', (error) => {
        console.error('Worker error:', error);
        this.availableWorkers.push(worker);
        this.processQueue();
      });
    }
  }

  public execute(data: any): Promise<any> {
    return new Promise((resolve, reject) => {
      this.taskQueue.push({ data, resolve, reject });
      this.processQueue();
    });
  }

  private processQueue() {
    if (this.taskQueue.length === 0 || this.availableWorkers.length === 0) {
      return;
    }

    const task = this.taskQueue.shift()!;
    const worker = this.availableWorkers.shift()!;

    worker.once('message', task.resolve);
    worker.once('error', task.reject);
    worker.postMessage(task.data);
  }

  public terminate() {
    this.workers.forEach(worker => worker.terminate());
  }
}

์‚ฌ์šฉ ์˜ˆ์‹œ

// backend/src/services/imageService.ts
import { WorkerPool } from '../workers/worker-pool.js';
import path from 'path';

const imagePool = new WorkerPool(
  path.join(__dirname, '../workers/image-worker.js'),
  4 // 4๊ฐœ์˜ ์›Œ์ปค๋ฅผ ํ’€์— ์œ ์ง€
);

export async function resizeImage(imagePath: string) {
  return imagePool.execute({
    imagePath,
    targetWidth: 800,
    targetHeight: 600
  });
}

๊ตฌํ˜„ ๊ฒ€์ฆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๋ณธ ํ”„๋กœ์ ํŠธ์˜ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค/์Šค๋ ˆ๋“œ ๊ตฌํ˜„์ด ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

โœ… Cluster ๋ชจ๋“œ (๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค)

1. USE_CLUSTER ํ”Œ๋ž˜๊ทธ๋กœ ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ™•์žฅ ์ œ์–ด ๊ฐ€๋Šฅํ•œ๊ฐ€?

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - webserver/server.js:30-36

const USE_CLUSTER = process.env.USE_CLUSTER === 'true' || process.argv.includes('--cluster');
const NUM_WORKERS = process.env.NUM_WORKERS ? parseInt(process.env.NUM_WORKERS) : os.cpus().length;

console.log(`๐Ÿ”ง Cluster ๋ชจ๋“œ: ${USE_CLUSTER ? 'ํ™œ์„ฑํ™”' : '๋น„ํ™œ์„ฑํ™”'}`);

๊ฒ€์ฆ ๋ฐฉ๋ฒ•:

# Cluster ๋น„ํ™œ์„ฑํ™” (๋‹จ์ผ ํ”„๋กœ์„ธ์Šค)
npm start

# Cluster ํ™œ์„ฑํ™” (๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค)
USE_CLUSTER=true npm start

2. ์›Œ์ปค ์ˆ˜๋ฅผ OS CPU ์ฝ”์–ด ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ์ฑ…์ •ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - os.cpus().length ์‚ฌ์šฉ

const NUM_WORKERS = process.env.NUM_WORKERS
  ? parseInt(process.env.NUM_WORKERS)  // ๋ช…์‹œ์  ์ง€์ •
  : os.cpus().length;                   // ์ž๋™ ๊ฐ์ง€

๊ฒ€์ฆ ๋ฐฉ๋ฒ•:

# CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ ์ž๋™ ์ƒ์„ฑ
USE_CLUSTER=true npm start

# ์›Œ์ปค ์ˆ˜ ๋ช…์‹œ์  ์ง€์ •
NUM_WORKERS=4 USE_CLUSTER=true npm start

์‹คํ–‰ ์˜ˆ์‹œ:

๐Ÿš€ Cluster ๋ชจ๋“œ ์‹œ์ž‘: 8๊ฐœ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ
๐Ÿ“Š CPU ์ฝ”์–ด ์ˆ˜: 8
๐Ÿ”ง ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ๋จ: PID 12345
๐Ÿ”ง ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ๋จ: PID 12346
...

3. ์›Œ์ปค๊ฐ€ ์ฃฝ์—ˆ์„ ๋•Œ Auto Restart ๊ฐ€๋Šฅํ•œ๊ฐ€?

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - webserver/server.js:491-501

cluster.on('exit', (worker, code, signal) => {
  console.log(
    `๐Ÿ’€ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ: PID ${worker.process.pid} (์ฝ”๋“œ: ${code}, ์‹œ๊ทธ๋„: ${signal})`
  );

  // ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์ข…๋ฃŒ์ธ ๊ฒฝ์šฐ์—๋งŒ ์žฌ์‹œ์ž‘
  if (!worker.exitedAfterDisconnect) {
    console.log('์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์žฌ์‹œ์ž‘ ์ค‘...');
    const newWorker = cluster.fork();
    console.log(`๐Ÿ”ง ์ƒˆ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ๋จ: PID ${newWorker.process.pid}`);
  }
});

๊ฒ€์ฆ ๋ฐฉ๋ฒ•:

# 1. Cluster ๋ชจ๋“œ๋กœ ์„œ๋ฒ„ ์‹คํ–‰
USE_CLUSTER=true npm start

# 2. ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ๊ฐ•์ œ ์ข…๋ฃŒ
kill -9 <์›Œ์ปค_PID>

# 3. ๋กœ๊ทธ ํ™•์ธ - ์ž๋™์œผ๋กœ ์ƒˆ ์›Œ์ปค๊ฐ€ ์ƒ์„ฑ๋˜์–ด์•ผ ํ•จ

ํ•ต์‹ฌ ํฌ์ธํŠธ:

  • worker.exitedAfterDisconnect: ์ •์ƒ ์ข…๋ฃŒ(graceful shutdown)์™€ ์˜ˆ์™ธ ์ข…๋ฃŒ ๊ตฌ๋ถ„
  • ์˜ˆ์™ธ ์ข…๋ฃŒ ์‹œ์—๋งŒ ์ž๋™ ์žฌ์‹œ์ž‘ → ๋ฌดํ•œ ์žฌ์‹œ์ž‘ ๋ฃจํ”„ ๋ฐฉ์ง€

4. Graceful Shutdown ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ์ธ๊ฐ€?

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - webserver/server.js:504-516

process.on('SIGINT', () => {
  console.log('\n๐Ÿ›‘ ๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹ ํ˜ธ ์ˆ˜์‹ ');
  console.log('๋ชจ๋“  ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์— ์ข…๋ฃŒ ์‹ ํ˜ธ ์ „์†ก...');

  for (const id in cluster.workers) {
    cluster.workers[id].kill();
  }

  setTimeout(() => {
    console.log('๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ');
    process.exit(0);
  }, 2000); // 2์ดˆ ํƒ€์ž„์•„์›ƒ
});

๊ฒ€์ฆ ๋ฐฉ๋ฒ•:

# ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘ Ctrl+C ๋˜๋Š” SIGINT ์ „์†ก
USE_CLUSTER=true npm start
# Ctrl+C ์ž…๋ ฅ

# ๋˜๋Š”
kill -SIGINT <๋งˆ์Šคํ„ฐ_PID>

 

Graceful Shutdown ํ”„๋กœ์„ธ์Šค:

  1. ๋งˆ์Šคํ„ฐ๊ฐ€ SIGINT ์‹œ๊ทธ๋„ ์ˆ˜์‹ 
  2. ๋ชจ๋“  ์›Œ์ปค์—๊ฒŒ ์ข…๋ฃŒ ์‹ ํ˜ธ ์ „์†ก
  3. ์›Œ์ปค๋“ค์ด ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ ์™„๋ฃŒ ๋Œ€๊ธฐ
  4. 2์ดˆ ํƒ€์ž„์•„์›ƒ ํ›„ ๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ

๊ฐœ์„  ์ œ์•ˆ:

// ๋” ์ •๊ตํ•œ Graceful Shutdown
process.on('SIGINT', async () => {
  console.log('\n๐Ÿ›‘ ๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹ ํ˜ธ ์ˆ˜์‹ ');

  // ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ ์ฐจ๋‹จ
  server.close(() => {
    console.log('์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ ์ฐจ๋‹จ ์™„๋ฃŒ');
  });

  // ์›Œ์ปค์—๊ฒŒ graceful shutdown ์‹œ๊ทธ๋„ ์ „์†ก
  for (const id in cluster.workers) {
    cluster.workers[id].send({ cmd: 'shutdown' });
    cluster.workers[id].disconnect(); // IPC ์ฑ„๋„ ์ข…๋ฃŒ
  }

  // ๋ชจ๋“  ์›Œ์ปค ์ข…๋ฃŒ ๋Œ€๊ธฐ (์ตœ๋Œ€ 10์ดˆ)
  const timeout = setTimeout(() => {
    console.log('โš ๏ธ ํƒ€์ž„์•„์›ƒ: ๊ฐ•์ œ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค');
    process.exit(1);
  }, 10000);

  // ๋ชจ๋“  ์›Œ์ปค ์ข…๋ฃŒ ์™„๋ฃŒ ์‹œ
  cluster.on('disconnect', () => {
    if (Object.keys(cluster.workers).length === 0) {
      clearTimeout(timeout);
      console.log('โœ… ๋ชจ๋“  ์›Œ์ปค ์ •์ƒ ์ข…๋ฃŒ ์™„๋ฃŒ');
      process.exit(0);
    }
  });
});

Worker Threads (๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ)

5. USE_WORKER_THREADS ํ”Œ๋ž˜๊ทธ๋กœ CPU ์ง‘์•ฝ์  ์ž‘์—… ๋ถ„๋ฆฌ ๊ฐ€๋Šฅํ•œ๊ฐ€?

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - webserver/server.js:22-28

// webserver/server.js
const USE_WORKER_THREADS =
  process.env.USE_WORKER_THREADS === 'true' ||
  process.argv.includes('--worker-threads');

console.log(`๐Ÿงต Worker Threads ๋ชจ๋“œ: ${USE_WORKER_THREADS ? 'ํ™œ์„ฑํ™”' : '๋น„ํ™œ์„ฑํ™”'}`);

์‹ค์ œ ์ ์šฉ ์ฝ”๋“œ - webserver/server.js:175-191:

if (USE_WORKER_THREADS) {
  // Worker Threads๋ฅผ ์‚ฌ์šฉํ•œ ํŒŒ์ผ ์ฒ˜๋ฆฌ
  const workerResult = await workerManager.processFile(
    filePath,
    requestedPath
  );
  contentType = workerResult.contentType;
  responseBody = workerResult.fileContent;
} else {
  // ๊ธฐ์กด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ํŒŒ์ผ ์ฒ˜๋ฆฌ
  contentType = getMimeType(filePath);
  if (isTextMimeType(contentType)) {
    responseBody = await fs.readFile(filePath, 'utf8');
  } else {
    responseBody = await fs.readFile(filePath);
  }
}

๊ฒ€์ฆ ๋ฐฉ๋ฒ•:

# Worker Threads ๋น„ํ™œ์„ฑํ™” (๊ธฐ๋ณธ๊ฐ’)
npm start

# Worker Threads ํ™œ์„ฑํ™”
USE_WORKER_THREADS=true npm start
# ๋˜๋Š”
node server.js --worker-threads

# Cluster + Worker Threads ๋™์‹œ ํ™œ์„ฑํ™” (์ตœ๊ณ  ์„ฑ๋Šฅ)
USE_CLUSTER=true USE_WORKER_THREADS=true npm start

์‹คํ–‰ ์˜ˆ์‹œ:

๐Ÿงต Worker Threads ๋ชจ๋“œ: ํ™œ์„ฑํ™”
๐Ÿงต WorkerManager ์ดˆ๊ธฐํ™”: ์ตœ๋Œ€ ์›Œ์ปค ์ˆ˜ 7
โœ… Worker-0 ์ƒ์„ฑ ์™„๋ฃŒ
โœ… Worker-1 ์ƒ์„ฑ ์™„๋ฃŒ
...

6. ์›Œ์ปค ํ’€์ด ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉ/๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋Š”๊ฐ€? (ํ์ž‰, ๋ฐธ๋Ÿฐ์‹ฑ, Recovery)

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - webserver/workers/workerManager.js

ํ”„๋กœ์ ํŠธ์— ์™„๋ฒฝํ•œ WorkerManager ํด๋ž˜์Šค๊ฐ€ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค!

์ฃผ์š” ๊ธฐ๋Šฅ:

1. ํ์ž‰ (Queueing) - FIFO ๋ฐฉ์‹
  • webserver/workers/workerManager.js:152-174
async executeTask(type, data) {
  return new Promise((resolve, reject) => {
    const taskId = ++this.taskIdCounter;
    const task = { taskId, type, data, resolve, reject, createdAt: Date.now() };

    // ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œ์ปค๊ฐ€ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ ์‹คํ–‰
    const availableWorker = this.getAvailableWorker();
    if (availableWorker) {
      this.assignTaskToWorker(task, availableWorker);
    } else {
      // ๋ชจ๋“  ์›Œ์ปค๊ฐ€ ๋ฐ”์˜๋ฉด ํ์— ์ถ”๊ฐ€
      this.taskQueue.push(task);
      console.log(`โณ Task-${taskId} ํ์— ๋Œ€๊ธฐ ์ค‘ (ํ ํฌ๊ธฐ: ${this.taskQueue.length})`);
    }
  });
}
2. ๋ฐธ๋Ÿฐ์‹ฑ (Balancing) - ์ตœ์†Œ ๋ถ€ํ•˜ ์›Œ์ปค ์„ ํƒ
  • webserver/workers/workerManager.js:139-147
getAvailableWorker() {
  for (const [workerId, workerInfo] of this.workers) {
    if (!workerInfo.busy) {
      return { workerId, workerInfo };
    }
  }
  return null;
}
3. Recovery - ์ž๋™ ์›Œ์ปค ์žฌ์ƒ์„ฑ
  • webserver/workers/workerManager.js:88-106
restartWorker(workerId) {
  const workerInfo = this.workers.get(workerId);
  if (workerInfo) {
    try {
      workerInfo.worker.terminate();
    } catch (error) {
      console.error(`์›Œ์ปค ์ข…๋ฃŒ ์ค‘ ์—๋Ÿฌ:`, error);
    }
    this.workers.delete(workerId);
  }

  // ์ƒˆ ์›Œ์ปค ์ƒ์„ฑ (1์ดˆ ํ›„)
  setTimeout(() => {
    if (!this.isShuttingDown) {
      this.createWorker(workerId);
    }
  }, 1000);
}

์—๋Ÿฌ ์ฒ˜๋ฆฌ:

// ์›Œ์ปค ์—๋Ÿฌ ์‹œ ์ž๋™ ์žฌ์‹œ์ž‘
worker.on('error', (error) => {
  console.error(`โŒ Worker-${workerId} ์—๋Ÿฌ:`, error);
  this.restartWorker(workerId);
});

// ๋น„์ •์ƒ ์ข…๋ฃŒ ์‹œ ์ž๋™ ์žฌ์‹œ์ž‘
worker.on('exit', (code) => {
  if (code !== 0) {
    console.error(`๐Ÿ’€ Worker-${workerId} ๋น„์ •์ƒ ์ข…๋ฃŒ (์ฝ”๋“œ: ${code})`);
    if (!this.isShuttingDown) {
      this.restartWorker(workerId);
    }
  }
});

๊ฒ€์ฆ ๋ฐฉ๋ฒ•:

# Worker Threads ํ™œ์„ฑํ™”ํ•˜๊ณ  ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ
USE_WORKER_THREADS=true npm start

# ๋‹ค๋ฅธ ํ„ฐ๋ฏธ๋„์—์„œ ๋ถ€ํ•˜ ์ƒ์„ฑ
ab -n 1000 -c 50 http://localhost:8080/

์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„ ํ™•์ธ:

# Worker Pool ์ƒํƒœ API ํ˜ธ์ถœ
curl http://localhost:8080/api/worker-stats

# ์ถœ๋ ฅ ์˜ˆ์‹œ:
{
  "success": true,
  "data": {
    "totalWorkers": 7,
    "busyWorkers": 3,
    "queueSize": 5,
    "activeTasks": 3,
    "workers": [
      { "id": 0, "busy": true, "taskCount": 42, "uptime": 60000 },
      { "id": 1, "busy": false, "taskCount": 38, "uptime": 60000 },
      ...
    ]
  }
}

7. ์›Œ์ปค์˜ ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ์ •๊ตํ•˜๊ฒŒ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋Š”๊ฐ€?

โœ… ๊ตฌํ˜„ ์™„๋ฃŒ - 5๋‹จ๊ณ„ ์ƒ๋ช…์ฃผ๊ธฐ ์™„๋ฒฝ ๊ด€๋ฆฌ

1. ์›Œ์ปค ์ƒ์„ฑ (Initialization) โœ…
  • webserver/workers/workerManager.js:35-39
  • CPU ์ฝ”์–ด ์ˆ˜์— ๋งž์ถฐ ์ž๋™ ์ดˆ๊ธฐํ™”: Math.max(2, os.cpus().length - 1)
  • ์›Œ์ปค ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์ƒ์„ฑ: new Worker(script, { workerData: { workerId } })
initializeWorkers() {
  for (let i = 0; i < this.maxWorkers; i++) {
    this.createWorker(i);
  }
}
2. ์›Œ์ปค ์‹คํ–‰ (Execution) โœ…
  • webserver/workers/workerManager.js:178-196
  • ๋ฉ”์‹œ์ง€ ๊ธฐ๋ฐ˜ ํ†ต์‹  (postMessage / on('message'))
  • ์ž‘์—… ์™„๋ฃŒ ํ›„ ์ž๋™์œผ๋กœ ํ’€๋กœ ๋ณต๊ท€
assignTaskToWorker(task, { workerId, workerInfo }) {
  workerInfo.busy = true;
  this.activeTasks.set(taskId, task);

  workerInfo.worker.postMessage({ taskId, type, data });
  console.log(`๐Ÿš€ Task-${taskId} -> Worker-${workerId} ํ• ๋‹น`);
}

handleWorkerMessage(workerId, message) {
  workerInfo.busy = false;  // ์›Œ์ปค๋ฅผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
  workerInfo.taskCount++;
  this.processNextTask();    // ๋‹ค์Œ ๋Œ€๊ธฐ ์ž‘์—… ์ฒ˜๋ฆฌ
}
3. ์›Œ์ปค ์—๋Ÿฌ ์ฒ˜๋ฆฌ (Error Handling) โœ…
  • webserver/workers/workerManager.js:55-59
  • error ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋‹ ๋ฐ ์ž๋™ ์žฌ์‹œ์ž‘
worker.on('error', (error) => {
  console.error(`โŒ Worker-${workerId} ์—๋Ÿฌ:`, error);
  this.restartWorker(workerId);
});
4. ์›Œ์ปค ์ข…๋ฃŒ (Termination) โœ…
  • webserver/workers/workerManager.js:62-69
  • ์ •์ƒ ์ข…๋ฃŒ(code=0) vs ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ตฌ๋ถ„
  • exit ์ด๋ฒคํŠธ๋กœ ๊ฐ์ง€ ๋ฐ ์ž๋™ ์žฌ์ƒ์„ฑ
worker.on('exit', (code) => {
  if (code !== 0) {
    console.error(`๐Ÿ’€ Worker-${workerId} ๋น„์ •์ƒ ์ข…๋ฃŒ (์ฝ”๋“œ: ${code})`);
    if (!this.isShuttingDown) {
      this.restartWorker(workerId);
    }
  }
});
5. ์›Œ์ปค ํ’€ ์ข…๋ฃŒ (Pool Termination) โœ…
  • webserver/workers/workerManager.js:266-306
  • Graceful Shutdown ์™„๋ฒฝ ๊ตฌํ˜„
  • ํ™œ์„ฑ ์ž‘์—… ์™„๋ฃŒ ๋Œ€๊ธฐ (์ตœ๋Œ€ 5์ดˆ)
  • ๋ชจ๋“  ์›Œ์ปค ์ •๋ฆฌ
async shutdown() {
  this.isShuttingDown = true;

  // 1. ๋Œ€๊ธฐ ์ค‘์ธ ์ž‘์—… ์ทจ์†Œ
  for (const task of this.taskQueue) {
    task.reject(new Error('Worker pool is shutting down'));
  }

  // 2. ํ™œ์„ฑ ์ž‘์—… ์™„๋ฃŒ ๋Œ€๊ธฐ (์ตœ๋Œ€ 5์ดˆ)
  while (this.activeTasks.size > 0) {
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  // 3. ๋ชจ๋“  ์›Œ์ปค ์ข…๋ฃŒ
  await Promise.allSettled(
    Array.from(this.workers.values()).map(w => w.worker.terminate())
  );

  console.log('โœ… WorkerManager ์ข…๋ฃŒ ์™„๋ฃŒ');
}

์ƒ๋ช…์ฃผ๊ธฐ ๋‹ค์ด์–ด๊ทธ๋žจ:

[์ดˆ๊ธฐํ™”] โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   ↓                          โ”‚
[๋Œ€๊ธฐ ์ค‘] ←โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”‚
   ↓               โ”‚           โ”‚
[์ž‘์—… ํ• ๋‹น]         โ”‚           โ”‚
   ↓               โ”‚           โ”‚
[์ž‘์—… ์‹คํ–‰]         โ”‚           โ”‚
   ↓               โ”‚           โ”‚
[๊ฒฐ๊ณผ ๋ฐ˜ํ™˜] โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
   ↓ (์—๋Ÿฌ ๋ฐœ์ƒ)                โ”‚
[์›Œ์ปค ์žฌ์ƒ์„ฑ] โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
   ↓ (shutdown)
[Graceful ์ข…๋ฃŒ]

์‹œ๊ทธ๋„ ํ•ธ๋“ค๋ง:

// SIGINT/SIGTERM ์‹œ๊ทธ๋„ ์ฒ˜๋ฆฌ
process.on('SIGINT', async () => {
  await workerManager.shutdown();
  process.exit(0);
});

process.on('SIGTERM', async () => {
  await workerManager.shutdown();
  process.exit(0);
});

์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ Cluster์™€ Worker Threads์˜ ์„ฑ๋Šฅ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•œ API ๊ตฌํ˜„:

// webserver/server.js - ์„œ๋ฒ„ ์ •๋ณด API
if (url === '/api/server-info') {
  return createJsonResponse(200, {
    success: true,
    data: {
      mode: USE_CLUSTER ? 'cluster' : 'single',
      pid: process.pid,
      cpuCores: os.cpus().length,
      memoryUsage: process.memoryUsage(),
      uptime: process.uptime(),
      workers: USE_CLUSTER ? Object.keys(cluster.workers || {}).length : 1,
    },
    timestamp: new Date().toISOString(),
  });
}
// backend/src/routes/workerRoutes.ts - Worker Pool ํ†ต๊ณ„ API
export function createWorkerStatsRoute(pool: WorkerPool): RouteHandler {
  return {
    async handle(request: HttpRequest): Promise<HttpResponse> {
      const stats = pool.getStats();

      return {
        statusCode: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          success: true,
          data: stats,
          timestamp: new Date().toISOString(),
        }),
      };
    },
  };
}

๋ชจ๋‹ˆํ„ฐ๋ง ์˜ˆ์‹œ:

# ์„œ๋ฒ„ ์ •๋ณด ํ™•์ธ
curl http://localhost/api/server-info

# Worker Pool ํ†ต๊ณ„ ํ™•์ธ
curl http://localhost/api/worker-stats

๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค vs ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ: ํ•ต์‹ฌ ์ฐจ์ด์ 

๊ฐœ๋… ๋น„๊ต

๊ตฌ๋ถ„ Cluster (๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค) Worker Threads (๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ)
๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์™„์ „ํžˆ ๋…๋ฆฝ๋œ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„ ๊ฐ™์€ ํ”„๋กœ์„ธ์Šค ๋‚ด ๋ฉ”๋ชจ๋ฆฌ ๊ณต์œ 
ํ†ต์‹  ๋ฐฉ์‹ IPC (Inter-Process Communication) ๋ฉ”์‹œ์ง€ ํŒจ์‹ฑ (postMessage)
์ƒ์„ฑ ๋น„์šฉ ๋†’์Œ (ํ”„๋กœ์„ธ์Šค ์ „์ฒด ๋ณต์ œ) ๋‚ฎ์Œ (์Šค๋ ˆ๋“œ๋งŒ ์ƒ์„ฑ)
๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ๊ฐ ํ”„๋กœ์„ธ์Šค๋งˆ๋‹ค ๋…๋ฆฝ์  ๋ฉ”๋ชจ๋ฆฌ ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ์˜์—ญ ํ™œ์šฉ
์žฅ์•  ๊ฒฉ๋ฆฌ ํ•œ ํ”„๋กœ์„ธ์Šค ์ฃฝ์–ด๋„ ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค ์˜ํ–ฅ ์—†์Œ ํ•œ ์Šค๋ ˆ๋“œ ์—๋Ÿฌ๊ฐ€ ํ”„๋กœ์„ธ์Šค ์ „์ฒด์— ์˜ํ–ฅ ๊ฐ€๋Šฅ
์ ํ•ฉํ•œ ์ž‘์—… I/O ๋ฐ”์šด๋“œ, HTTP ์š”์ฒญ ์ฒ˜๋ฆฌ CPU ๋ฐ”์šด๋“œ, ๊ณ„์‚ฐ ์ง‘์•ฝ์  ์ž‘์—…

์‹ค์ œ ํ”„๋กœ์ ํŠธ ์ ์šฉ ์˜ˆ์‹œ

// webserver/server.js

// 1๏ธโƒฃ Cluster: ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋กœ HTTP ์š”์ฒญ ๋ถ„์‚ฐ
if (USE_CLUSTER && cluster.isPrimary) {
  // 8์ฝ”์–ด CPU๋ผ๋ฉด 8๊ฐœ์˜ ๋…๋ฆฝ๋œ ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ
  for (let i = 0; i < os.cpus().length; i++) {
    cluster.fork(); // ๊ฐ ํ”„๋กœ์„ธ์Šค๋Š” ๋…๋ฆฝ๋œ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„ ๋ณด์œ 
  }
}

// 2๏ธโƒฃ Worker Threads: ํŒŒ์ผ ์ฒ˜๋ฆฌ๋ฅผ ๋ณ„๋„ ์Šค๋ ˆ๋“œ๋กœ ๋ถ„๋ฆฌ
if (USE_WORKER_THREADS) {
  const workerResult = await workerManager.processFile(filePath);
  // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ์ฐจ๋‹จ๋˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์š”์ฒญ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
}

๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ๋„์ž…ํ•˜๋ฉด ํ•ญ์ƒ ์„ฑ๋Šฅ์ด ๋” ์ข‹์•„์ง€๋Š”๊ฐ€?

โŒ ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค!

๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋Š” ํŠน์ • ์ƒํ™ฉ์—์„œ๋งŒ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋˜๋Š” ๊ฒฝ์šฐ โœ…

1. CPU ์ง‘์•ฝ์  ์ž‘์—…

// โœ… ์ข‹์€ ์˜ˆ: ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• (CPU ์ž‘์—…)
await workerManager.processImage({
  imagePath: '/uploads/large-image.jpg',
  resize: { width: 1920, height: 1080 },
  compress: true
});
// ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ์ฐจ๋‹จ๋˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ HTTP ์š”์ฒญ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

์„ฑ๋Šฅ ๋น„๊ต:

๋‹จ์ผ ์Šค๋ ˆ๋“œ: ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ 500ms → ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ฐจ๋‹จ → ๋‹ค๋ฅธ ์š”์ฒญ ๋Œ€๊ธฐ
๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ: ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ 500ms → ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๊ณ„์† ๋™์ž‘ → ๋‹ค๋ฅธ ์š”์ฒญ ์ฆ‰์‹œ ์ฒ˜๋ฆฌ

2. ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜

// โœ… ์ข‹์€ ์˜ˆ: CSV ํŒŒ์ผ ํŒŒ์‹ฑ (CPU ์ž‘์—…)
await workerManager.processData({
  inputData: largeCSVFile,
  operation: 'parse-and-transform'
});

3. ์•”ํ˜ธํ™”/ํ•ด์‹ฑ ์ž‘์—…

// โœ… ์ข‹์€ ์˜ˆ: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ (CPU ์ž‘์—…)
await workerManager.executeTask('hash', {
  password: userPassword,
  algorithm: 'bcrypt',
  rounds: 10
});

์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜๋Š” ๊ฒฝ์šฐ โŒ

1. I/O ๋ฐ”์šด๋“œ ์ž‘์—…

// โŒ ๋‚˜์œ ์˜ˆ: ํŒŒ์ผ ์ฝ๊ธฐ (I/O ์ž‘์—…)
// Node.js๋Š” ์ด๋ฏธ ๋น„๋™๊ธฐ I/O๋กœ ์ตœ์ ํ™”๋˜์–ด ์žˆ์Œ
await workerManager.processFile('/path/to/file.txt');

// โœ… ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•: ๊ทธ๋ƒฅ fs/promises ์‚ฌ์šฉ
await fs.readFile('/path/to/file.txt', 'utf8');

์ด์œ : Node.js์˜ ๋น„๋™๊ธฐ I/O๋Š” libuv์˜ ์Šค๋ ˆ๋“œ ํ’€์„ ์ด๋ฏธ ํ™œ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Worker Threads๋ฅผ ์ถ”๊ฐ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๋งŒ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž‘์€ ์ž‘์—…๋“ค

// โŒ ๋‚˜์œ ์˜ˆ: ๊ฐ„๋‹จํ•œ JSON ํŒŒ์‹ฑ
await workerManager.executeTask('parse', {
  data: smallJsonString
});

// โœ… ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•: ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌ
const parsed = JSON.parse(smallJsonString);

์„ฑ๋Šฅ ๋น„๊ต:

์ž‘์—… ์‹œ๊ฐ„: 1ms
์›Œ์ปค ์ƒ์„ฑ/ํ†ต์‹  ์˜ค๋ฒ„ํ—ค๋“œ: 5-10ms
→ ์ด ์‹œ๊ฐ„: 6-11ms (์˜คํžˆ๋ ค 10๋ฐฐ ๋А๋ฆผ!)

3. ๋นˆ๋ฒˆํ•œ ์ž‘์€ ๋ฉ”์‹œ์ง€ ์ „์†ก

// โŒ ๋‚˜์œ ์˜ˆ: ๋ฃจํ”„์—์„œ ๋งค๋ฒˆ ์›Œ์ปค ํ˜ธ์ถœ
for (let i = 0; i < 10000; i++) {
  await workerManager.executeTask('increment', { value: i });
}
// ํ†ต์‹  ์˜ค๋ฒ„ํ—ค๋“œ 10000๋ฐฐ ๋ฐœ์ƒ!

// โœ… ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•: ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
await workerManager.executeTask('batch-increment', {
  values: Array.from({ length: 10000 }, (_, i) => i)
});

์–ธ์ œ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

๊ฒฐ์ • ๊ธฐ์ค€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… CPU ์‚ฌ์šฉ๋ฅ ์ด 100%์— ๊ฐ€๊นŒ์šด๊ฐ€?
โœ… ์ž‘์—… ์‹œ๊ฐ„์ด 10ms ์ด์ƒ์ธ๊ฐ€?
โœ… ์ž‘์—…์ด ๋ฉ”์ธ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ฐจ๋‹จํ•˜๋Š”๊ฐ€?
โœ… ๋™์‹œ ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์ค‘์š”ํ•œ๊ฐ€?

์œ„ ์งˆ๋ฌธ์— ๋ชจ๋‘ YES๋ผ๋ฉด → Worker Threads ๊ณ ๋ ค
ํ•˜๋‚˜๋ผ๋„ NO๋ผ๋ฉด → ๊ธฐ์กด ๋น„๋™๊ธฐ I/O ์‚ฌ์šฉ

์‹ค์ „ ํŒ๋‹จ ์˜ˆ์‹œ

์ž‘์—… CPU ์ง‘์•ฝ? ์‹œ๊ฐ„ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ํ•„์š”?
์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• โœ… YES 200-500ms โœ… ํ•„์š”ํ•จ
๋น„๋””์˜ค ์ธ์ฝ”๋”ฉ โœ… YES 5-30์ดˆ โœ… ํ•„์š”ํ•จ
ํŒŒ์ผ ์ฝ๊ธฐ โŒ NO (I/O) 5-50ms โŒ ๋ถˆํ•„์š”
JSON ํŒŒ์‹ฑ (์ž‘์Œ) โš ๏ธ ์•ฝ๊ฐ„ 1ms โŒ ๋ถˆํ•„์š”
JSON ํŒŒ์‹ฑ (๋Œ€์šฉ๋Ÿ‰) โœ… YES 100ms+ โœ… ํ•„์š”ํ•จ
DB ์ฟผ๋ฆฌ โŒ NO (I/O) 10-100ms โŒ ๋ถˆํ•„์š”
๋ณต์žกํ•œ ์ •๊ทœ์‹ โœ… YES 50ms+ โœ… ๊ณ ๋ ค

์„ฑ๋Šฅ ์ธก์ • ์‹ค์Šต

1. ๋ฒค์น˜๋งˆํฌ ์ฝ”๋“œ

// benchmark.js
import { performance } from 'perf_hooks';

// ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
async function benchmarkMainThread() {
  const start = performance.now();

  // CPU ์ง‘์•ฝ์  ์ž‘์—… ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  let result = 0;
  for (let i = 0; i < 10000000; i++) {
    result += Math.sqrt(i);
  }

  const end = performance.now();
  console.log(`๋ฉ”์ธ ์Šค๋ ˆ๋“œ: ${(end - start).toFixed(2)}ms`);
  return result;
}

// Worker Thread์—์„œ ์‹คํ–‰
async function benchmarkWorkerThread() {
  const start = performance.now();

  const result = await workerManager.executeTask('heavy-compute', {
    iterations: 10000000
  });

  const end = performance.now();
  console.log(`์›Œ์ปค ์Šค๋ ˆ๋“œ: ${(end - start).toFixed(2)}ms`);
  return result;
}

// ๋™์‹œ ์š”์ฒญ ์‹œ๋‚˜๋ฆฌ์˜ค
async function benchmarkConcurrent() {
  const start = performance.now();

  // 10๊ฐœ์˜ ๋™์‹œ ์š”์ฒญ
  await Promise.all([
    benchmarkMainThread(),
    benchmarkMainThread(),
    // ... ๋‹ค๋ฅธ ์š”์ฒญ๋“ค
  ]);

  const end = performance.now();
  console.log(`๋™์‹œ ์ฒ˜๋ฆฌ (๋ฉ”์ธ): ${(end - start).toFixed(2)}ms`);
}

2. ์˜ˆ์ƒ ๊ฒฐ๊ณผ

# ๋‹จ์ผ ์ž‘์—… ๋น„๊ต
๋ฉ”์ธ ์Šค๋ ˆ๋“œ: 245ms
์›Œ์ปค ์Šค๋ ˆ๋“œ: 250ms (ํ†ต์‹  ์˜ค๋ฒ„ํ—ค๋“œ +5ms)
→ ๊ฑฐ์˜ ๋™์ผ

# ๋™์‹œ ์ž‘์—… ๋น„๊ต (10๊ฐœ)
๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ˆœ์ฐจ: 2450ms (10 × 245ms)
์›Œ์ปค ์Šค๋ ˆ๋“œ ๋ณ‘๋ ฌ: 300ms (์›Œ์ปค 8๊ฐœ ์‚ฌ์šฉ)
→ 8๋ฐฐ ๋น ๋ฆ„! ๐Ÿš€

์˜ค๋ฒ„ํ—ค๋“œ ์ดํ•ดํ•˜๊ธฐ

Worker Thread ์ƒ์„ฑ ๋น„์šฉ

const { Worker } = require('worker_threads');

console.time('์›Œ์ปค ์ƒ์„ฑ');
const worker = new Worker('./worker.js');
console.timeEnd('์›Œ์ปค ์ƒ์„ฑ');
// ์ถœ๋ ฅ: ์›Œ์ปค ์ƒ์„ฑ: 5.2ms

console.time('์›Œ์ปค ์‹คํ–‰');
worker.postMessage({ task: 'simple' });
worker.on('message', () => {
  console.timeEnd('์›Œ์ปค ์‹คํ–‰');
  // ์ถœ๋ ฅ: ์›Œ์ปค ์‹คํ–‰: 2.1ms
});

์˜ค๋ฒ„ํ—ค๋“œ ์š”์•ฝ:

  • ์›Œ์ปค ์ƒ์„ฑ: ~5-10ms
  • ๋ฉ”์‹œ์ง€ ์ „์†ก: ~1-2ms
  • ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”: ํฌ๊ธฐ์— ๋น„๋ก€

๊ฒฐ๋ก : ์ž‘์—… ์‹œ๊ฐ„์ด 10ms ์ดํ•˜๋ผ๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์ด๋“๋ณด๋‹ค ํด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


์ตœ์ข… ๊ถŒ์žฅ์‚ฌํ•ญ

ํ”„๋กœ์ ํŠธ๋ณ„ ๊ฐ€์ด๋“œ

1. ์†Œ๊ทœ๋ชจ API ์„œ๋ฒ„ (์›” 1๋งŒ ์š”์ฒญ ๋ฏธ๋งŒ)

๊ถŒ์žฅ: Cluster๋งŒ ์‚ฌ์šฉ (Worker Threads ๋ถˆํ•„์š”)
์ด์œ : ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์ด I/O ๋ฐ”์šด๋“œ

2. ์ค‘๊ทœ๋ชจ ์›น ์„œ๋น„์Šค (์›” 100๋งŒ ์š”์ฒญ)

๊ถŒ์žฅ: Cluster + ์„ ํƒ์  Worker Threads
์ด์œ : ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋“ฑ ํŠน์ • CPU ์ž‘์—…๋งŒ ๋ถ„๋ฆฌ

3. ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ (์‹ค์‹œ๊ฐ„ ๋ถ„์„)

๊ถŒ์žฅ: Cluster + Worker Pool
์ด์œ : CPU ์ง‘์•ฝ์  ์ž‘์—…์ด ์ง€์†์ ์œผ๋กœ ๋ฐœ์ƒ

์›น ์„ฑ๋Šฅ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ : Apache Bench

์ด ๋•Œ, ์„ฑ๋Šฅ ๊ฐœ์„  ํ›„์™€ ์ „์˜ ์„ฑ๋Šฅ ์ฐจ์ด๋ฅผ ํ™•์ธํ•ด๋ณด๊ธฐ ์œ„ํ•ด์„œ ์›น ์„ฑ๋Šฅ ๋ถ€ํ•˜๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด Apache Bench ๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

Apache Bench (ab)

  • ์•„ํŒŒ์น˜ HTTP ์„œ๋ฒ„์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜๋Š” ๊ฐ„๋‹จํ•œ ๋ฒค์น˜๋งˆํฌ ๋„๊ตฌ
  • ๊ธฐ๋ณธ ๊ธฐ๋Šฅ: ๋™์‹œ ์ ‘์† ์ˆ˜, ์š”์ฒญ ์ˆ˜๋ฅผ ์ง€์ •ํ•ด์„œ ์„œ๋ฒ„์— ๋ถ€ํ•˜ ๊ฑธ๊ธฐ
  • ์˜ˆ:
    • -n 1000 → ์ด 1000๋ฒˆ ์š”์ฒญ
    • -c 100 → ๋™์‹œ์— 100๊ฐœ ์š”์ฒญ
  • ab -n 1000 -c 100 http://localhost/

Part 4: ์„ฑ๋Šฅ ๋น„๊ต ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ

# Apache Bench๋กœ ์„ฑ๋Šฅ ์ธก์ •
# ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค
ab -n 10000 -c 100 http://localhost:3000/api/posts

# Cluster ๋ชจ๋“œ (4 workers)
ab -n 10000 -c 100 http://localhost:3000/api/posts

์‹ค์ œ ์„ฑ๋Šฅ ์ฐจ์ด (์˜ˆ์‹œ)

๊ตฌ์„ฑ Requests/sec ํ‰๊ท  ์‘๋‹ต์‹œ๊ฐ„
๋‹จ์ผ ํ”„๋กœ์„ธ์Šค 1,234 req/s 81ms
Cluster (4 workers) 4,567 req/s 22ms
+ Worker Pool 5,892 req/s 17ms

Best Practices

1. Cluster ์„ค์ •

// ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์›Œ์ปค ์ˆ˜ ์กฐ์ ˆ
const NUM_WORKERS = process.env.NODE_ENV === 'production'
  ? os.cpus().length
  : 2; // ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” 2๊ฐœ๋กœ ์ œํ•œ

2. Graceful Shutdown

// Master ํ”„๋กœ์„ธ์Šค
process.on('SIGTERM', () => {
  logger.info('SIGTERM received, shutting down gracefully...');

  Object.values(cluster.workers || {}).forEach((worker) => {
    worker?.kill('SIGTERM');
  });

  setTimeout(() => {
    process.exit(0);
  }, 10000); // 10์ดˆ ํƒ€์ž„์•„์›ƒ
});

3. Worker Thread ์—๋Ÿฌ ํ•ธ๋“ค๋ง

worker.on('error', (error) => {
  logger.error('Worker thread error:', error);
  // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์žฌ์‹œ๋„ ๋กœ์ง
  retryTask(task);
});

ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ

(base) kimseoyeon@gimseoyeons-MacBook-Air-2 webserver % npm run test:performance

> codestargram-webserver@1.0.0 test:performance
> node test/performanceTest.js

๐Ÿš€ ์›น์„œ๋ฒ„ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์‹œ์ž‘
๐Ÿ“ก ๋Œ€์ƒ ์„œ๋ฒ„: http://127.0.0.1:80
============================================================

๐Ÿ“Š ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ๋กœ๋“œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 1000 -c 10 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: HTML ํŽ˜์ด์ง€ ๋กœ๋“œ (index.html)

๐Ÿ“Š JavaScript ํŒŒ์ผ ๋กœ๋“œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 500 -c 10 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/bundle.js
๐Ÿ“ ํ…Œ์ŠคํŠธ: JavaScript ํŒŒ์ผ ๋กœ๋“œ (bundle.js)

๐Ÿ“Š ํŒŒ๋น„์ฝ˜ ๋กœ๋“œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 500 -c 10 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/favicon.ico
๐Ÿ“ ํ…Œ์ŠคํŠธ: ํŒŒ๋น„์ฝ˜ ๋กœ๋“œ (favicon.ico)

๐Ÿ“Š ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ

๐Ÿ”ฅ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜: 1
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 200 -c 1 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ๋™์‹œ ์—ฐ๊ฒฐ 1๊ฐœ

๐Ÿ”ฅ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜: 5
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 200 -c 5 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ๋™์‹œ ์—ฐ๊ฒฐ 5๊ฐœ

๐Ÿ”ฅ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜: 10
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 200 -c 10 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ๋™์‹œ ์—ฐ๊ฒฐ 10๊ฐœ

๐Ÿ”ฅ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜: 20
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 200 -c 20 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ๋™์‹œ ์—ฐ๊ฒฐ 20๊ฐœ

๐Ÿ”ฅ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜: 50
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 200 -c 50 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ๋™์‹œ ์—ฐ๊ฒฐ 50๊ฐœ

๐Ÿ“Š Keep-Alive ์„ฑ๋Šฅ ๋น„๊ต ํ…Œ์ŠคํŠธ
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 500 -c 10 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: Keep-Alive ๋น„ํ™œ์„ฑํ™”
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 500 -c 10 -s 60 -g gnuplot.dat -e results.csv -k http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: Keep-Alive ํ™œ์„ฑํ™”

๐Ÿ“Š ์ง€์† ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ (20์ดˆ)
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -t 20 -c 10 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: 20์ดˆ๊ฐ„ ์ง€์† ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ

============================================================
๐Ÿ“Š ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์š”์•ฝ
============================================================

๐ŸŽฏ HTML ํŽ˜์ด์ง€ ๋กœ๋“œ (index.html)
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 1000
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 7008.74 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 1.43 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 3114.24 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.143 ์ดˆ

๐ŸŽฏ JavaScript ํŒŒ์ผ ๋กœ๋“œ (bundle.js)
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 500
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 4468.08 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 2.24 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 163857.07 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.112 ์ดˆ

๐ŸŽฏ ํŒŒ๋น„์ฝ˜ ๋กœ๋“œ (favicon.ico)
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 500
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 5574.26 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 1.79 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 947.19 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.09 ์ดˆ

๐ŸŽฏ ๋™์‹œ ์—ฐ๊ฒฐ 1๊ฐœ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 200
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 1048.29 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 0.95 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 465.79 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.191 ์ดˆ

๐ŸŽฏ ๋™์‹œ ์—ฐ๊ฒฐ 5๊ฐœ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 200
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 6928.81 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 0.72 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 3078.72 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.029 ์ดˆ

๐ŸŽฏ ๋™์‹œ ์—ฐ๊ฒฐ 10๊ฐœ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 200
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 8215.24 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 1.22 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 3650.33 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.024 ์ดˆ

๐ŸŽฏ ๋™์‹œ ์—ฐ๊ฒฐ 20๊ฐœ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 200
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 9794.80 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 2.04 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 4352.18 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.02 ์ดˆ

๐ŸŽฏ ๋™์‹œ ์—ฐ๊ฒฐ 50๊ฐœ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 200
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 8990.38 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 5.56 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 3994.75 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.022 ์ดˆ

๐ŸŽฏ Keep-Alive ๋น„ํ™œ์„ฑํ™”
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 500
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 8943.58 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 1.12 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 3973.96 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.056 ์ดˆ

๐ŸŽฏ Keep-Alive ํ™œ์„ฑํ™”
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 500
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 7413.89 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 1.35 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 3294.26 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 0.067 ์ดˆ

๐ŸŽฏ 20์ดˆ๊ฐ„ ์ง€์† ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 12342
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 559.31 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 17.88 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 248.52 KB/sec
   โฐ ์ด ์†Œ์š” ์‹œ๊ฐ„: 22.067 ์ดˆ

๐ŸŽ‰ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!

Cluster ๋ชจ๋“ˆ ๊ธฐ๋ฐ˜ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ์„œ๋ฒ„ ๊ตฌํ˜„

(base) kimseoyeon@gimseoyeons-MacBook-Air-2 webserver % node test/clusterTest.js
๐Ÿš€ ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ์„ฑ๋Šฅ ๋น„๊ต ํ…Œ์ŠคํŠธ ์‹œ์ž‘
๐Ÿ“ก ๋Œ€์ƒ ์„œ๋ฒ„: ClusterPerformanceTester
============================================================

๐Ÿ“Š ๋‹จ์ผ vs ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค ์„ฑ๋Šฅ ๋น„๊ต ํ…Œ์ŠคํŠธ

๐Ÿ”น ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ๋ชจ๋“œ ํ…Œ์ŠคํŠธ
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 2000 -c 50 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ๋ชจ๋“œ

โณ ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ์ „ํ™˜์„ ์œ„ํ•ด ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ํ•„์š”
๐Ÿ’ก ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•˜์„ธ์š”:
   USE_CLUSTER=true node webserver/server.js
   ๋˜๋Š”
   node webserver/server.js --cluster

โธ๏ธ  ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ์„œ๋ฒ„ ์‹œ์ž‘ ํ›„ ์•„๋ฌด ํ‚ค๋‚˜ ๋ˆ„๋ฅด์„ธ์š”...

๐Ÿ”น ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ํ…Œ์ŠคํŠธ
๐Ÿš€ ์‹คํ–‰ ์ค‘: ab -n 2000 -c 50 -s 60 -g gnuplot.dat -e results.csv http://127.0.0.1:80/
๐Ÿ“ ํ…Œ์ŠคํŠธ: ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ

============================================================
๐Ÿ“ˆ ํด๋Ÿฌ์Šคํ„ฐ ์„ฑ๋Šฅ ๋น„๊ต ๋ถ„์„
============================================================

๐Ÿ”น ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค:
   โšก RPS: 6191.91 req/sec
   โฑ๏ธ  ์‘๋‹ต์‹œ๊ฐ„: 8.07 ms
   ๐Ÿ“ˆ ์ „์†ก์†๋„: 2751.29 KB/sec

๐Ÿ”น ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ:
   โšก RPS: 5421.27 req/sec
   โฑ๏ธ  ์‘๋‹ต์‹œ๊ฐ„: 9.22 ms
   ๐Ÿ“ˆ ์ „์†ก์†๋„: 2408.87 KB/sec

๐Ÿ“Š ์„ฑ๋Šฅ ๊ฐœ์„ ๋„:
   โšก RPS ํ–ฅ์ƒ: -12.45%
   โฑ๏ธ  ์‘๋‹ต์‹œ๊ฐ„ ๊ฐœ์„ : -14.22%
   ๐Ÿ“ˆ ์ „์†ก์†๋„ ํ–ฅ์ƒ: -12.45%

โš ๏ธ  ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ์—์„œ ์˜ˆ์ƒ๋ณด๋‹ค ์„ฑ๋Šฅ ํ–ฅ์ƒ์ด ์ ์Šต๋‹ˆ๋‹ค.
    ๋†’์€ ๋™์‹œ์„ฑ ๋ถ€ํ•˜์—์„œ ๋” ํฐ ์ฐจ์ด๋ฅผ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

============================================================
๐Ÿ“Š ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์š”์•ฝ
============================================================

๐ŸŽฏ ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ๋ชจ๋“œ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 2000
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 6191.91 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 8.07 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 2751.29 KB/sec

๐ŸŽฏ ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ
   โœ… ์™„๋ฃŒ๋œ ์š”์ฒญ: 2000
   โŒ ์‹คํŒจํ•œ ์š”์ฒญ: 0
   โšก ์ดˆ๋‹น ์š”์ฒญ ์ˆ˜: 5421.27 req/sec
   โฑ๏ธ  ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: 9.22 ms
   ๐Ÿ“ˆ ์ „์†ก ์†๋„: 2408.87 KB/sec

๐ŸŽ‰ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!

๐Ÿ’พ ํด๋Ÿฌ์Šคํ„ฐ ๋น„๊ต ๊ฒฐ๊ณผ ์ €์žฅ๋จ: cluster-performance-test-2025-09-17T13-26-31-597Z.json
(base) kimseoyeon@gimseoyeons-MacBook-Air-2 webserver %

๋งˆ๋ฌด๋ฆฌ

Node.js์˜ Cluster์™€ Worker Threads๋Š” ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • Cluster: ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋กœ ์š”์ฒญ์„ ๋ถ„์‚ฐ (์ˆ˜ํ‰ ํ™•์žฅ)
  • Worker Threads: ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ๋กœ ๋ถ„๋ฆฌ (๋ธ”๋กœํ‚น ๋ฐฉ์ง€)

์ด ๋‘˜์„ ์กฐํ•ฉํ•˜๋ฉด ๋ฉ€ํ‹ฐ์ฝ”์–ด๋ฅผ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๋ฉด์„œ๋„ ๋ฉ”์ธ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๋ง‰์ง€ ์•Š๋Š” ์ง„์ •ํ•œ ๊ณ ์„ฑ๋Šฅ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” PM2๋‚˜ Kubernetes ๊ฐ™์€ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋„๊ตฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ๊ฐ•๋ ฅํ•œ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋” ์ฝ์–ด๋ณด๊ธฐ

  • Node.js Cluster ๊ณต์‹ ๋ฌธ์„œ
  • Worker Threads ๊ณต์‹ ๋ฌธ์„œ
  • PM2 ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ ๊ฐ€์ด๋“œ

'๐ŸŒ WEB' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

์›น ์‚ฌ์ดํŠธ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•  (0) 2026.01.19
HTTP Multipart/form-data ์ง์ ‘ ํŒŒ์„œ ๋งŒ๋“ค๋ฉฐ ์›๋ฆฌ ์ดํ•ดํ•˜๊ธฐ  (0) 2025.10.01
HTTP ํŒจํ‚ท ๋ถ„์„ํ•˜๊ธฐ  (0) 2025.09.18
HTTP ํŒจํ‚ท ๊ตฌ์กฐ, ์š”์ฒญ ํ—ค๋”/๋ฐ”๋””  (0) 2025.09.17
Web Server ์™€ WAS(Web Application Server)๋ž€ ?  (0) 2025.09.15
'๐ŸŒ WEB' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์›น ์‚ฌ์ดํŠธ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•
  • HTTP Multipart/form-data ์ง์ ‘ ํŒŒ์„œ ๋งŒ๋“ค๋ฉฐ ์›๋ฆฌ ์ดํ•ดํ•˜๊ธฐ
  • HTTP ํŒจํ‚ท ๋ถ„์„ํ•˜๊ธฐ
  • HTTP ํŒจํ‚ท ๊ตฌ์กฐ, ์š”์ฒญ ํ—ค๋”/๋ฐ”๋””
์—ฐ์žŽ(lotus leaf)
์—ฐ์žŽ(lotus leaf)
  • ์—ฐ์žŽ(lotus leaf)
    lotus' s develog ๐Ÿƒ
    ์—ฐ์žŽ(lotus leaf)
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (79)
      • โœ๏ธ ๊ฐœ๋ฐœํšŒ๊ณ ๋ก (5)
      • ๐Ÿงฎ ์•Œ๊ณ ๋ฆฌ์ฆ˜ (3)
      • ๐Ÿ’™ ํ”„๋ก ํŠธ์—”๋“œ(FE) (19)
        • HTML (0)
        • CSS (0)
        • Javascript (0)
        • React (0)
        • Next.js (0)
        • webpack & babel (0)
      • ๐Ÿ’ป ๋ฐฑ์—”๋“œ(BE) (2)
        • Nest.js (0)
        • Express.js (0)
        • MySQL (1)
      • โš™๏ธ ์ธํ”„๋ผ(Devops) (2)
      • ๐Ÿค– AI (1)
      • ๐ŸŒ WEB (8)
      • ๐Ÿ’ป CS (16)
        • ์ž๋ฃŒ๊ตฌ์กฐ (0)
        • ์ปดํ“จํ„ฐ ๋„คํŠธ์›Œํฌ (1)
        • ์šด์˜์ฒด์ œ (0)
        • ์ธ๊ณต์ง€๋Šฅ (8)
        • ์›น ๋ณด์•ˆ (1)
        • ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ… (6)
      • ๐Ÿ–‹๏ธ DevLog (1)
      • ๐Ÿฆพ ๋กœ๋ณดํ‹ฑ์Šค (4)
      • ๐Ÿ“— ๋„ค์ด๋ฒ„๋ถ€์ŠคํŠธ์บ ํ”„ ์›น ๋ชจ๋ฐ”์ผ (0)
      • ๐ŸŽฎ Unity(C#) (10)
      • ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด (4)
        • C (4)
        • C++ (0)
        • Java (0)
        • Python (0)
      • MSA (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    ๋ฆฌ์•กํŠธ
    hard margin svm
    isaac automator
    gaussian rbf svm
    ์ŠคํŒธ๋ถ„๋ฅ˜๊ธฐ
    ์ฝ”๋”ํŒจ๋“œ
    C
    soft margin svm
    AWS
    Devops #๋Œ€๊ทœ๋ชจํŠธ๋ž˜ํ”ฝ์ฒ˜๋ฆฌ
    nav2
    c์–ธ์–ด
    ์กฐํ•ฉ
    next.js12
    c++
    ์ˆœ์—ด
    ros bridge
    advaned detail
    ํŒŒ์ผํŠธ๋ฆฌ
    C#
    ๊ธฐ์ดˆ์•Œ๊ณ ๋ฆฌ์ฆ˜
    turtlebot
    auto-scaling
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    ์ŠคํŒธ๋ฉ”์ผ๋ถ„๋ฅ˜๊ธฐ
    ๋ฐฑ์ค€
    ros workspace
    ์ดํ™”์—ฌ์ž๋Œ€ํ•™๊ต #๋„์ „ํ•™๊ธฐ์ œ
    deploy-aws
    client-streaming
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.6
์—ฐ์žŽ(lotus leaf)
Node.js๋กœ ๊ณ ์„ฑ๋Šฅ ์›น์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ: Cluster์™€ Worker Threads
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”