From 925f5026a4a6e92ce9d6a65c357ef6f62b2114ea Mon Sep 17 00:00:00 2001 From: kilhyeonjun Date: Wed, 10 Dec 2025 14:52:38 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20Chapter=2011=20=EA=B3=A0=EA=B8=89=20?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=ED=94=BC=20=ED=95=99=EC=8A=B5=20=EC=9E=90?= =?UTF-8?q?=EB=A3=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🀖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- chapter11/kilhyeonjun/README.md | 432 ++++++++++++++++++ .../kilhyeonjun/code/01-db-without-queue.js | 56 +++ .../kilhyeonjun/code/02-db-local-init.js | 63 +++ .../kilhyeonjun/code/03-db-delayed-startup.js | 68 +++ .../code/04-db-preinitialization-queue.js | 73 +++ .../kilhyeonjun/code/05-db-state-pattern.js | 108 +++++ chapter11/kilhyeonjun/code/06-batch-api.js | 85 ++++ chapter11/kilhyeonjun/code/07-cache-api.js | 146 ++++++ chapter11/kilhyeonjun/code/08-cancel-basic.js | 116 +++++ .../kilhyeonjun/code/09-cancel-wrapper.js | 145 ++++++ .../kilhyeonjun/code/10-cancel-generator.js | 178 ++++++++ .../code/11-subset-sum-blocking.js | 109 +++++ .../code/12-subset-sum-interleaving.js | 161 +++++++ chapter11/kilhyeonjun/code/13-process-pool.js | 178 ++++++++ .../kilhyeonjun/code/13-process-worker.js | 58 +++ chapter11/kilhyeonjun/code/14-thread-pool.js | 235 ++++++++++ chapter11/kilhyeonjun/code/README.md | 113 +++++ .../code/exercises/11.1-proxy-queue.js | 178 ++++++++ .../exercises/11.2-callback-batch-cache.js | 215 +++++++++ .../code/exercises/11.3-deep-cancelable.js | 283 ++++++++++++ .../code/exercises/11.4-computing-farm.js | 408 +++++++++++++++++ 21 files changed, 3408 insertions(+) create mode 100644 chapter11/kilhyeonjun/README.md create mode 100644 chapter11/kilhyeonjun/code/01-db-without-queue.js create mode 100644 chapter11/kilhyeonjun/code/02-db-local-init.js create mode 100644 chapter11/kilhyeonjun/code/03-db-delayed-startup.js create mode 100644 chapter11/kilhyeonjun/code/04-db-preinitialization-queue.js create mode 100644 chapter11/kilhyeonjun/code/05-db-state-pattern.js create mode 100644 chapter11/kilhyeonjun/code/06-batch-api.js create mode 100644 chapter11/kilhyeonjun/code/07-cache-api.js create mode 100644 chapter11/kilhyeonjun/code/08-cancel-basic.js create mode 100644 chapter11/kilhyeonjun/code/09-cancel-wrapper.js create mode 100644 chapter11/kilhyeonjun/code/10-cancel-generator.js create mode 100644 chapter11/kilhyeonjun/code/11-subset-sum-blocking.js create mode 100644 chapter11/kilhyeonjun/code/12-subset-sum-interleaving.js create mode 100644 chapter11/kilhyeonjun/code/13-process-pool.js create mode 100644 chapter11/kilhyeonjun/code/13-process-worker.js create mode 100644 chapter11/kilhyeonjun/code/14-thread-pool.js create mode 100644 chapter11/kilhyeonjun/code/README.md create mode 100644 chapter11/kilhyeonjun/code/exercises/11.1-proxy-queue.js create mode 100644 chapter11/kilhyeonjun/code/exercises/11.2-callback-batch-cache.js create mode 100644 chapter11/kilhyeonjun/code/exercises/11.3-deep-cancelable.js create mode 100644 chapter11/kilhyeonjun/code/exercises/11.4-computing-farm.js diff --git a/chapter11/kilhyeonjun/README.md b/chapter11/kilhyeonjun/README.md new file mode 100644 index 0000000..2f32b7f --- /dev/null +++ b/chapter11/kilhyeonjun/README.md @@ -0,0 +1,432 @@ +# Chapter 11: 고꞉ 레시플 + +> **발표자**: Ꞟ현쀀 +> **발표음**: 2025-12-08 +> **죌제**: 비동Ʞ 쎈Ʞ화, 요청 배치/캐싱, 작업 췚소, CPU 바욎드 싀행 + +--- + +## 📌 목찚 + +1. [개요](#개요) +2. [비동Ʞ적윌로 쎈Ʞ화되는 컎포넌튞 닀룚Ʞ](#1-비동Ʞ적윌로-쎈Ʞ화되는-컎포넌튞-닀룚Ʞ) +3. [비동Ʞ식 요청 음ꎄ 처늬 및 캐싱](#2-비동Ʞ식-요청-음ꎄ-처늬-및-캐싱) +4. [비동Ʞ 작업 췚소](#3-비동Ʞ-작업-췚소) +5. [CPU 바욎드 작업 싀행](#4-cpu-바욎드-작업-싀행) +6. [요앜](#요앜) +7. [연습묞제](#연습묞제) + +--- + +## 개요 + +### 왜 쀑요한가? + +Node.js 애플늬쌀읎션을 개발하닀 볎멎 닚순한 비동Ʞ 처늬륌 넘얎서는 고꞉ Ʞ법읎 필요한 상황을 자죌 마죌합니닀: + +- **비동Ʞ 쎈Ʞ화**: DB 연결, 섀정 로드 등 비동Ʞ로 쎈Ʞ화되는 컎포넌튞륌 얎떻게 안전하게 사용할까? +- **쀑복 요청 최적화**: 동음한 API륌 동시에 여러 번 혞출할 때 얎떻게 횚윚적윌로 처늬할까? +- **작업 췚소**: 사용자가 페읎지륌 떠났는데 진행 쀑읞 작업을 얎떻게 쀑닚할까? +- **CPU 집앜적 작업**: 묎거욎 계산읎 서버 응답을 막는 것을 얎떻게 방지할까? + +### 핵심 킀워드 + +| 개념 | 섀명 | +|------|------| +| **사전 쎈Ʞ화 큐** | 쎈Ʞ화 완료 전 요청을 큐에 저장하여 나쀑에 싀행 | +| **요청 배치** | 동음한 요청 쀑복 방지 (플Ʞ백) | +| **요청 캐싱** | 결곌륌 저장하여 재사용 | +| **췚소 토큰** | 비동Ʞ 작업을 안전하게 쀑닚 | +| **작업자 풀** | CPU 작업을 별도 프로섞슀/슀레드에서 싀행 | + +--- + +## 1. 비동Ʞ적윌로 쎈Ʞ화되는 컎포넌튞 닀룚Ʞ + +### 묞제 상황 + +```javascript +// ❌ 묞제: 연결 전에 쿌늬하멎 에러 발생 +const db = new DB() +db.connect() // 비동Ʞ 쎈Ʞ화 시작 +db.query('SELECT * FROM users') // Error: Not connected yet +``` + +### 핎결책 비교 + +#### 1) 로컬 쎈Ʞ화 확읞 + +맀번 연결 상태륌 확읞하고 대Ʞ합니닀. + +```javascript +async function updateLastAccess() { + // 맀번 연결 상태 확읞 + if (!db.connected) { + await once(db, 'connected') + } + await db.query('INSERT INTO ...') +} +``` + +**닚점**: 윔드 쀑복, 몚든 핚수에서 확읞 필요 + +#### 2) 지연 시작 + +몚든 쎈Ʞ화가 완료된 후 애플늬쌀읎션 로직을 싀행합니닀. + +```javascript +async function main() { + // 뚌저 몚든 쎈Ʞ화 완료 대Ʞ + await initialize() + + // 읎후 비슈니슀 로직 싀행 + startServer() +} +``` + +**닚점**: 시작 시간 지연, 재쎈Ʞ화 믞고렀 + +#### 3) 사전 쎈Ʞ화 큐 (권장) + +쎈Ʞ화 전 요청을 큐에 저장하고, 완료 후 음ꎄ 싀행합니닀. + +```javascript +async query(queryString) { + if (!this.connected) { + // 명령을 큐에 저장하고 Promise 반환 + return new Promise((resolve, reject) => { + this.commandsQueue.push(() => { + this.query(queryString).then(resolve, reject) + }) + }) + } + // 싀제 쿌늬 싀행 + return this._executeQuery(queryString) +} +``` + +### 상태 팚턎윌로 개선 + +``` +┌──────────────────────────────────────────────────────────┐ +│ Client │ +└──────────────────────────────────────────────────────────┘ + │ + â–Œ + ┌─────────────────────────┐ + │ DB │ + │ (Context) │ + │ │ + │ state.query(...) │ + └─────────────────────────┘ + │ + ┌────────────────┌────────────────┐ + │ │ + â–Œ â–Œ +┌─────────────────────┐ ┌─────────────────────┐ +│ QueuingState │ │ InitializedState │ +│ │ │ │ +│ - 요청을 큐에 저장 │ ──────> │ - 싀제 쿌늬 싀행 │ +│ - Promise 반환 │ connect │ - 비슈니슀 로직 │ +└─────────────────────┘ └─────────────────────┘ +``` + +**싀전 예시**: Mongoose, pg 띌읎람러늬가 읎 방식 사용 + +--- + +## 2. 비동Ʞ식 요청 음ꎄ 처늬 및 캐싱 + +### 요청 배치 (Batching) + +동음한 API 요청읎 진행 쀑음 때, 새 요청을 시작하지 않고 Ʞ졎 요청에 **플Ʞ백(piggyback)** 합니닀. + +``` +┌─────────────────────────────────────────────────────────┐ +│ Without Batching │ +├────────────────────────────────────────────────────────── +│ Client A ──→ [API Call] ──→ Result A │ +│ Client B ──→ [API Call] ──→ Result B │ +│ Client C ──→ [API Call] ──→ Result C │ +│ │ +│ → 3번의 API 혞출, 3배의 시간 │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ With Batching │ +├────────────────────────────────────────────────────────── +│ Client A ─┐ │ +│ ├──→ [API Call] ──→ Result (공유) │ +│ Client B ── ↓ │ +│ │ A, B, C 몚두 동음 결곌 │ +│ Client C ─┘ │ +│ │ +│ → 1번의 API 혞출, 1/3 시간 │ +└─────────────────────────────────────────────────────────┘ +``` + +```javascript +function createBatchedApi(originalApi) { + const runningRequests = new Map() + + return async function(key) { + // 동음한 요청읎 진행 쀑읎멎 핎당 Promise 반환 + if (runningRequests.has(key)) { + return runningRequests.get(key) + } + + // 새 요청 시작 + const resultPromise = originalApi(key) + runningRequests.set(key, resultPromise) + + resultPromise.finally(() => { + runningRequests.delete(key) + }) + + return resultPromise + } +} +``` + +### 요청 캐싱 (Caching) + +배치 처늬에 **TTL(Time To Live)** êž°ë°˜ 캐시륌 추가합니닀. + +```javascript +function createCachedApi(originalApi, ttlMs = 5000) { + const cache = new Map() + const runningRequests = new Map() + + return async function(key) { + // 1. 캐시 확읞 + const cached = cache.get(key) + if (cached && (Date.now() - cached.timestamp) < ttlMs) { + return cached.value // Cache HIT + } + + // 2. 배치 확읞 + if (runningRequests.has(key)) { + return runningRequests.get(key) + } + + // 3. 새 요청 + const resultPromise = originalApi(key) + runningRequests.set(key, resultPromise) + + const result = await resultPromise + cache.set(key, { value: result, timestamp: Date.now() }) + runningRequests.delete(key) + + return result + } +} +``` + +### 캐싱 고렀사항 + +| 고렀사항 | 섀명 | +|----------|------| +| **TTL 섀정** | 데읎터 특성에 맞는 적절한 만료 시간 | +| **메몚늬 ꎀ늬** | LRU 알고늬슘윌로 였래된 항목 제거 | +| **묎횚화 전략** | 데읎터 변겜 시 캐시 동Ʞ화 | +| **분산 캐시** | 여러 서버 간 캐시 공유 (Redis 등) | + +--- + +## 3. 비동Ʞ 작업 췚소 + +### Ʞ볞 팹턮: cancelRequested 플래귞 + +```javascript +async function cancellableOperation(cancelObj) { + const res1 = await asyncStep('Step 1') + if (cancelObj.cancelRequested) { + throw new CancelError() + } + + const res2 = await asyncStep('Step 2') + if (cancelObj.cancelRequested) { + throw new CancelError() + } + + return [res1, res2] +} + +// 사용 +const cancelObj = { cancelRequested: false } +const promise = cancellableOperation(cancelObj) + +setTimeout(() => { + cancelObj.cancelRequested = true // 췚소 요청 +}, 500) +``` + +### 래퍌 팹턮 + +```javascript +function createCancelWrapper() { + let cancelRequested = false + + return { + cancel: () => { cancelRequested = true }, + cancelWrapper: (asyncFn, ...args) => { + if (cancelRequested) { + return Promise.reject(new CancelError()) + } + return asyncFn(...args) + } + } +} +``` + +### 제너레읎터 팹턮 (권장) + +```javascript +const cancellableOperation = createAsyncCancelable(function* () { + // 각 yield가 자동윌로 췚소 포읞튞 + const resA = yield asyncStep('Step A') + const resB = yield asyncStep('Step B') + const resC = yield asyncStep('Step C') + return [resA, resB, resC] +}) + +// 사용 +const { promise, cancel } = cancellableOperation() +setTimeout(cancel, 500) // 500ms 후 췚소 +await promise +``` + +**ì°žê³  띌읎람러늬**: [caf](https://github.com/getify/CAF) + +--- + +## 4. CPU 바욎드 작업 싀행 + +### 묞제: 읎벀튞 룚프 찚닚 + +```javascript +// ❌ 묎거욎 동Ʞ 계산 - 서버가 응답 불가 +function heavyComputation(data) { + // O(2^n) 시간 복잡도... + for (let i = 0; i < 1e10; i++) { /* ... */ } +} +``` + +### 핎결책 비교 + +``` +┌────────────────────────────────────────────────────────────────┐ +│ Main Thread │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ Request │ │ Request │ │ Request │ │ Request │ │ +│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ │ │ │ │ +│ â–Œ â–Œ â–Œ â–Œ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Event Loop (Non-Blocking) │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────┌────────────────────┐ │ +│ â–Œ â–Œ â–Œ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │Worker 1 │ │Worker 2 │ │Worker 3 │ │ +│ │(Thread) │ │(Thread) │ │(Process)│ │ +│ └─────────┘ └─────────┘ └─────────┘ │ +└────────────────────────────────────────────────────────────────┘ +``` + +#### 1) setImmediate 읞터늬빙 + +```javascript +// 계산 닚계 사읎에 읎벀튞 룚프에 양볎 +function* compute() { + for (let i = 0; i < items.length; i++) { + yield processItem(items[i]) // 각 닚계 후 양볎 + } +} +``` + +**장점**: 추가 늬소슀 불필요 +**닚점**: 성능 였버헀드, 닚음 윔얎만 사용 + +#### 2) 왞부 프로섞슀 (child_process.fork) + +```javascript +const pool = new ProcessPool('./worker.js', 4) +const result = await pool.run({ set, sum }) +``` + +**장점**: 멀티윔얎 활용, 크래시 격늬 +**닚점**: 메몚늬 사용량, 시작 비용 + +#### 3) 작업자 슀레드 (worker_threads) + +```javascript +const pool = new ThreadPool(4) +const result = await pool.run({ set, sum }) +``` + +**장점**: 프로섞슀볎닀 가벌움, SharedArrayBuffer 지원 +**닚점**: Node 10.5+ 필요 + +### 비교표 + +| 방법 | 읎벀튞 룚프 | 멀티윔얎 | 였버헀드 | 적합한 겜우 | +|------|-------------|----------|----------|-------------| +| setImmediate | 공유 | ❌ | 낮음 | 짧은 닚계로 분할 가능한 작업 | +| child_process | 분늬 | ✅ | 높음 | 장시간 싀행, 격늬 필요 | +| worker_threads | 분늬 | ✅ | 쀑간 | 빈번한 CPU 작업, 메몚늬 공유 | + +**권장 띌읎람러늬**: +- [workerpool](https://github.com/josdejong/workerpool) +- [piscina](https://github.com/piscinajs/piscina) + +--- + +## 요앜 + +| 팹턮 | 묞제 상황 | 핎결 방법 | 싀전 예시 | +|------|----------|----------|----------| +| **사전 쎈Ʞ화 큐** | 비동Ʞ 쎈Ʞ화 전 API 혞출 | 요청을 큐에 저장 후 나쀑에 싀행 | Mongoose, pg | +| **요청 배치** | 동음 API 쀑복 혞출 | runningRequests Map윌로 플Ʞ백 | API Gateway | +| **요청 캐싱** | 반복적읞 동음 요청 | TTL êž°ë°˜ 캐시 + 배치 | Redis 캐시 | +| **작업 췚소** | 불필요핎진 진행 쀑 작업 | cancelWrapper, 제너레읎터 | caf | +| **CPU 바욎드** | 읎벀튞 룚프 찚닚 | 프로섞슀/슀레드 풀 | piscina | + +--- + +## 연습묞제 + +### 11.1 프록시륌 사용한 대Ʞ엎 구현 + +Proxy륌 사용하여 비동Ʞ로 쎈Ʞ화되는 몚든 컎포넌튞에 대Ʞ엎을 투명하게 적용하는 음반 래퍌 만듀Ʞ. + +### 11.2 윜백 êž°ë°˜ 배치 및 캐싱 + +Promise 없읎 윜백 방식윌로 요청 배치 및 캐싱 구현하Ʞ. + +### 11.3 Deep 췚소 가능한 비동Ʞ 핚수 + +쀑첩된 췚소 가능 핚수에서 룚튞 핚수 췚소 시 몚든 쀑첩 핚수까지 췚소되는 Ʞ능 구현하Ʞ. + +### 11.4 컎퓚팅 팜 + +HTTP로 작업을 분산하고 eval/vm윌로 동적 윔드륌 싀행하는 분산 컎퓚팅 시슀템 구현하Ʞ. + +--- + +## ì°žê³  자료 + +### 띌읎람러늬 + +- [Mongoose](https://mongoosejs.com/) - 사전 쎈Ʞ화 큐 사용 +- [pg](https://node-postgres.com/) - PostgreSQL 큎띌읎얞튞 +- [caf](https://github.com/getify/CAF) - 췚소 가능한 비동Ʞ 핚수 +- [workerpool](https://github.com/josdejong/workerpool) - 프로섞슀/슀레드 풀 +- [piscina](https://github.com/piscinajs/piscina) - 작업자 슀레드 풀 + +### 공식 묞서 + +- [child_process](https://nodejs.org/api/child_process.html) +- [worker_threads](https://nodejs.org/api/worker_threads.html) +- [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) diff --git a/chapter11/kilhyeonjun/code/01-db-without-queue.js b/chapter11/kilhyeonjun/code/01-db-without-queue.js new file mode 100644 index 0000000..0d93c03 --- /dev/null +++ b/chapter11/kilhyeonjun/code/01-db-without-queue.js @@ -0,0 +1,56 @@ +/** + * 01-db-without-queue.js + * 비동Ʞ 쎈Ʞ화 컎포넌튞의 묞제점 + * + * 연결읎 완료되Ʞ 전에 쿌늬륌 싀행하멎 에러 발생 + */ + +import { EventEmitter } from 'events' + +// 비동Ʞ 쎈Ʞ화가 필요한 DB 몚듈 +class DB extends EventEmitter { + connected = false + + connect() { + console.log('Connecting to database...') + // 연결 지연 시뮬레읎션 (500ms) + setTimeout(() => { + this.connected = true + console.log('Database connected!') + this.emit('connected') + }, 500) + } + + async query(queryString) { + if (!this.connected) { + throw new Error('Not connected yet') + } + console.log(`Query executed: ${queryString}`) + return { rows: [] } + } +} + +export const db = new DB() + +// 묞제 시연 +async function main() { + db.connect() + + // 연결 완료 전에 쿌늬 싀행 시도 - 에러 발생! + try { + await db.query('SELECT * FROM users') + } catch (err) { + console.error('Error:', err.message) + } + + // 600ms 후 (연결 완료 후) 쿌늬 싀행 - 성공 + setTimeout(async () => { + try { + await db.query('SELECT * FROM users') + } catch (err) { + console.error('Error:', err.message) + } + }, 600) +} + +main() diff --git a/chapter11/kilhyeonjun/code/02-db-local-init.js b/chapter11/kilhyeonjun/code/02-db-local-init.js new file mode 100644 index 0000000..5a57258 --- /dev/null +++ b/chapter11/kilhyeonjun/code/02-db-local-init.js @@ -0,0 +1,63 @@ +/** + * 02-db-local-init.js + * 로컬 쎈Ʞ화 확읞 방식 + * + * API 혞출 시마닀 쎈Ʞ화 여부륌 확읞하고 대Ʞ + */ + +import { EventEmitter, once } from 'events' + +class DB extends EventEmitter { + connected = false + + connect() { + console.log('Connecting to database...') + setTimeout(() => { + this.connected = true + console.log('Database connected!') + this.emit('connected') + }, 500) + } + + async query(queryString) { + if (!this.connected) { + throw new Error('Not connected yet') + } + console.log(`Query executed: ${queryString}`) + return { rows: [] } + } +} + +export const db = new DB() + +// 로컬 쎈Ʞ화 확읞 방식 +async function updateLastAccess() { + // 맀번 연결 상태 확읞 + if (!db.connected) { + console.log('Waiting for connection...') + await once(db, 'connected') + } + + await db.query(`INSERT (${Date.now()}) INTO "LastAccesses"`) +} + +async function main() { + db.connect() + + // 연결 전 혞출 - 대Ʞ 후 싀행 + updateLastAccess() + + // 600ms 후 혞출 - 바로 싀행 + setTimeout(() => { + updateLastAccess() + }, 600) +} + +main() + +/** + * 닚점: + * - 맀번 쎈Ʞ화 상태륌 확읞핎알 핹 + * - 윔드 쀑복 발생 + * - 여러 비동Ʞ 컎포넌튞가 있윌멎 복잡핎짐 + */ diff --git a/chapter11/kilhyeonjun/code/03-db-delayed-startup.js b/chapter11/kilhyeonjun/code/03-db-delayed-startup.js new file mode 100644 index 0000000..6de02c1 --- /dev/null +++ b/chapter11/kilhyeonjun/code/03-db-delayed-startup.js @@ -0,0 +1,68 @@ +/** + * 03-db-delayed-startup.js + * 지연 시작 방식 + * + * 몚든 쎈Ʞ화가 완료된 후 애플늬쌀읎션 로직 싀행 + */ + +import { EventEmitter, once } from 'events' + +class DB extends EventEmitter { + connected = false + + connect() { + console.log('Connecting to database...') + setTimeout(() => { + this.connected = true + console.log('Database connected!') + this.emit('connected') + }, 500) + } + + async query(queryString) { + if (!this.connected) { + throw new Error('Not connected yet') + } + console.log(`Query executed: ${queryString}`) + return { rows: [] } + } +} + +export const db = new DB() + +// 쎈Ʞ화 핚수 +async function initialize() { + db.connect() + await once(db, 'connected') + console.log('All services initialized!') +} + +// 비슈니슀 로직 (쎈Ʞ화 상태 확읞 불필요) +async function updateLastAccess() { + await db.query(`INSERT (${Date.now()}) INTO "LastAccesses"`) +} + +async function main() { + // 뚌저 몚든 쎈Ʞ화 완료 대Ʞ + await initialize() + + // 읎후 비슈니슀 로직 싀행 + updateLastAccess() + + setTimeout(() => { + updateLastAccess() + }, 100) +} + +main() + +/** + * 장점: + * - 비슈니슀 로직에서 쎈Ʞ화 상태 확읞 불필요 + * - 간닚하고 명확핚 + * + * 닚점: + * - 애플늬쌀읎션 시작 시간 지연 + * - 비동Ʞ 컎포넌튞의 재쎈Ʞ화는 고렀하지 않음 + * - ì–Žë–€ 컎포넌튞가 쎈Ʞ화가 필요한지 믞늬 알아알 핹 + */ diff --git a/chapter11/kilhyeonjun/code/04-db-preinitialization-queue.js b/chapter11/kilhyeonjun/code/04-db-preinitialization-queue.js new file mode 100644 index 0000000..c817811 --- /dev/null +++ b/chapter11/kilhyeonjun/code/04-db-preinitialization-queue.js @@ -0,0 +1,73 @@ +/** + * 04-db-preinitialization-queue.js + * 사전 쎈Ʞ화 큐 방식 + * + * 쎈Ʞ화 전 요청을 큐에 저장하고, 쎈Ʞ화 완료 후 음ꎄ 싀행 + */ + +import { EventEmitter } from 'events' + +class DB extends EventEmitter { + connected = false + commandsQueue = [] + + connect() { + console.log('Connecting to database...') + setTimeout(() => { + this.connected = true + console.log('Database connected!') + this.emit('connected') + + // 큐에 쌓읞 명령듀 싀행 + this.commandsQueue.forEach(command => command()) + this.commandsQueue = [] + }, 500) + } + + async query(queryString) { + if (!this.connected) { + console.log(`Request queued: ${queryString}`) + + // 명령을 큐에 저장하고 Promise 반환 + return new Promise((resolve, reject) => { + const command = () => { + this.query(queryString) + .then(resolve, reject) + } + this.commandsQueue.push(command) + }) + } + + console.log(`Query executed: ${queryString}`) + return { rows: [] } + } +} + +export const db = new DB() + +async function main() { + db.connect() + + // 연결 전 혞출 - 큐에 저장됚 + const promise1 = db.query('SELECT * FROM users') + const promise2 = db.query('SELECT * FROM orders') + + // 연결 완료 후 자동 싀행 + const results = await Promise.all([promise1, promise2]) + console.log('All queries completed!') + + // 읎후 혞출 - 바로 싀행 + setTimeout(async () => { + await db.query('SELECT * FROM products') + }, 600) +} + +main() + +/** + * 장점: + * - 사용자 윔드에서 쎈Ʞ화 상태 확읞 불필요 + * - 투명한 사용 가능 (쎈Ʞ화 상태륌 몰띌도 됚) + * + * 읎 방식읎 Mongoose, pg 등에서 사용됚 + */ diff --git a/chapter11/kilhyeonjun/code/05-db-state-pattern.js b/chapter11/kilhyeonjun/code/05-db-state-pattern.js new file mode 100644 index 0000000..213f2b7 --- /dev/null +++ b/chapter11/kilhyeonjun/code/05-db-state-pattern.js @@ -0,0 +1,108 @@ +/** + * 05-db-state-pattern.js + * 상태 팚턎을 활용한 사전 쎈Ʞ화 큐 + * + * QueuingState와 InitializedState로 분늬하여 더 깔끔하게 구현 + */ + +import { EventEmitter } from 'events' + +// 쎈Ʞ화가 필요한 핚수 목록 +const METHODS_REQUIRING_CONNECTION = ['query'] + +// 상태 비활성화륌 위한 Symbol (읎늄 충돌 방지) +const deactivate = Symbol('deactivate') + +// 대Ʞ 상태: 쎈Ʞ화 전 +class QueuingState { + constructor(db) { + this.db = db + this.commandsQueue = [] + + // 연결읎 필요한 핚수듀을 동적윌로 생성 + METHODS_REQUIRING_CONNECTION.forEach(methodName => { + this[methodName] = function (...args) { + console.log('Command queued:', methodName, args[0]) + return new Promise((resolve, reject) => { + const command = () => { + db[methodName](...args) + .then(resolve, reject) + } + this.commandsQueue.push(command) + }) + } + }) + } + + // 상태 비활성화 시 큐 싀행 + [deactivate]() { + console.log('Flushing queued commands...') + this.commandsQueue.forEach(command => command()) + this.commandsQueue = [] + } +} + +// 쎈Ʞ화 완료 상태: 비슈니슀 로직만 구현 +class InitializedState { + async query(queryString) { + console.log(`Query executed: ${queryString}`) + return { rows: [] } + } +} + +// DB 컚텍슀튞 큎래슀 +class DB extends EventEmitter { + constructor() { + super() + this.state = new QueuingState(this) + } + + // 현재 상태로 위임 + async query(queryString) { + return this.state.query(queryString) + } + + connect() { + console.log('Connecting to database...') + setTimeout(() => { + console.log('Database connected!') + this.emit('connected') + + // 상태 전환 + const oldState = this.state + this.state = new InitializedState() + + // 읎전 상태 비활성화 (큐 싀행) + if (oldState[deactivate]) { + oldState[deactivate]() + } + }, 500) + } +} + +export const db = new DB() + +async function main() { + db.connect() + + // 연결 전 혞출 - QueuingState가 처늬 + const promise1 = db.query('SELECT * FROM users') + const promise2 = db.query('SELECT * FROM orders') + + const results = await Promise.all([promise1, promise2]) + console.log('All queries completed!') + + // 연결 후 혞출 - InitializedState가 처늬 + setTimeout(async () => { + await db.query('SELECT * FROM products') + }, 600) +} + +main() + +/** + * 장점: + * - 상태별 로직 분늬로 윔드 가독성 향상 + * - InitializedState는 순수 비슈니슀 로직만 포핚 + * - 확장읎 용읎 (새로욎 상태 추가 가능) + */ diff --git a/chapter11/kilhyeonjun/code/06-batch-api.js b/chapter11/kilhyeonjun/code/06-batch-api.js new file mode 100644 index 0000000..3d60913 --- /dev/null +++ b/chapter11/kilhyeonjun/code/06-batch-api.js @@ -0,0 +1,85 @@ +/** + * 06-batch-api.js + * 비동Ʞ 요청 음ꎄ 처늬 (Batching) + * + * 동음한 요청읎 진행 쀑음 때 새 요청을 시작하지 않고 + * Ʞ졎 요청에 펞승(piggyback) + */ + +// 느며 API 시뮬레읎션 +async function slowApiCall(key) { + console.log(`[API] Starting request for: ${key}`) + await new Promise(resolve => setTimeout(resolve, 1000)) + console.log(`[API] Completed request for: ${key}`) + return { key, data: `Result for ${key}`, timestamp: Date.now() } +} + +// 배치 처늬 래퍌 +function createBatchedApi(originalApi) { + const runningRequests = new Map() + + return async function batchedApi(key) { + // 동음한 요청읎 진행 쀑읎멎 핎당 Promise 반환 + if (runningRequests.has(key)) { + console.log(`[Batch] Piggybacking on existing request: ${key}`) + return runningRequests.get(key) + } + + // 새 요청 시작 + console.log(`[Batch] Starting new request: ${key}`) + const resultPromise = originalApi(key) + runningRequests.set(key, resultPromise) + + // 요청 완료 시 Map에서 제거 + resultPromise.finally(() => { + runningRequests.delete(key) + console.log(`[Batch] Request completed and cleared: ${key}`) + }) + + return resultPromise + } +} + +// 배치 처늬가 적용된 API +const batchedApiCall = createBatchedApi(slowApiCall) + +async function main() { + console.log('=== Without Batching ===') + // 배치 없읎 동음 요청 3번 + const start1 = Date.now() + await Promise.all([ + slowApiCall('user:1'), + slowApiCall('user:1'), + slowApiCall('user:1') + ]) + console.log(`Time without batching: ${Date.now() - start1}ms\n`) + + console.log('=== With Batching ===') + // 배치 적용하여 동음 요청 3번 + const start2 = Date.now() + await Promise.all([ + batchedApiCall('user:1'), + batchedApiCall('user:1'), + batchedApiCall('user:1') + ]) + console.log(`Time with batching: ${Date.now() - start2}ms\n`) + + console.log('=== Different Keys ===') + // 닀륞 킀는 별도 요청 + const start3 = Date.now() + await Promise.all([ + batchedApiCall('user:1'), + batchedApiCall('user:2'), + batchedApiCall('user:1') // user:1곌 배치됚 + ]) + console.log(`Time with different keys: ${Date.now() - start3}ms`) +} + +main() + +/** + * 싀행 결곌: + * - Without Batching: ~3000ms (3번 순찚 싀행) + * - With Batching: ~1000ms (1번만 싀행, 3명읎 공유) + * - Different Keys: ~1000ms (2개 병렬 싀행) + */ diff --git a/chapter11/kilhyeonjun/code/07-cache-api.js b/chapter11/kilhyeonjun/code/07-cache-api.js new file mode 100644 index 0000000..e53aff7 --- /dev/null +++ b/chapter11/kilhyeonjun/code/07-cache-api.js @@ -0,0 +1,146 @@ +/** + * 07-cache-api.js + * 비동Ʞ 요청 캐싱 (Caching) + * + * 배치 처늬에 TTL 캐시륌 추가하여 결곌 재사용 + */ + +// 느며 API 시뮬레읎션 +async function slowApiCall(key) { + console.log(`[API] Starting request for: ${key}`) + await new Promise(resolve => setTimeout(resolve, 1000)) + console.log(`[API] Completed request for: ${key}`) + return { key, data: `Result for ${key}`, timestamp: Date.now() } +} + +// 캐시 + 배치 처늬 래퍌 +function createCachedApi(originalApi, ttlMs = 5000) { + const runningRequests = new Map() // 진행 쀑읞 요청 + const cache = new Map() // 완료된 결곌 캐시 + + return async function cachedApi(key) { + // 1. 캐시 확읞 + const cached = cache.get(key) + if (cached) { + const age = Date.now() - cached.timestamp + if (age < ttlMs) { + console.log(`[Cache] HIT for ${key} (age: ${age}ms)`) + return cached.value + } + // TTL 만료 + console.log(`[Cache] EXPIRED for ${key}`) + cache.delete(key) + } + + // 2. 진행 쀑읞 요청 확읞 (배치) + if (runningRequests.has(key)) { + console.log(`[Batch] Piggybacking on existing request: ${key}`) + return runningRequests.get(key) + } + + // 3. 새 요청 시작 + console.log(`[Cache] MISS for ${key}, starting new request`) + const resultPromise = originalApi(key) + runningRequests.set(key, resultPromise) + + try { + const result = await resultPromise + // 결곌 캐시 + cache.set(key, { + value: result, + timestamp: Date.now() + }) + return result + } finally { + runningRequests.delete(key) + } + } +} + +// 캐시 묎횚화 Ʞ능 추가 버전 +function createCachedApiWithInvalidation(originalApi, ttlMs = 5000) { + const runningRequests = new Map() + const cache = new Map() + + const cachedApi = async function(key) { + const cached = cache.get(key) + if (cached && (Date.now() - cached.timestamp) < ttlMs) { + console.log(`[Cache] HIT for ${key}`) + return cached.value + } + + if (runningRequests.has(key)) { + return runningRequests.get(key) + } + + const resultPromise = originalApi(key) + runningRequests.set(key, resultPromise) + + try { + const result = await resultPromise + cache.set(key, { value: result, timestamp: Date.now() }) + return result + } finally { + runningRequests.delete(key) + } + } + + // 캐시 묎횚화 메서드 + cachedApi.invalidate = (key) => { + console.log(`[Cache] Invalidating: ${key}`) + cache.delete(key) + } + + cachedApi.invalidateAll = () => { + console.log(`[Cache] Invalidating all entries`) + cache.clear() + } + + cachedApi.getStats = () => ({ + cacheSize: cache.size, + runningRequests: runningRequests.size + }) + + return cachedApi +} + +// 캐시가 적용된 API +const cachedApiCall = createCachedApi(slowApiCall, 3000) + +async function main() { + console.log('=== First Call (Cache MISS) ===') + const start1 = Date.now() + const result1 = await cachedApiCall('user:1') + console.log(`Time: ${Date.now() - start1}ms\n`) + + console.log('=== Second Call (Cache HIT) ===') + const start2 = Date.now() + const result2 = await cachedApiCall('user:1') + console.log(`Time: ${Date.now() - start2}ms\n`) + + console.log('=== Concurrent Calls (Batch + Cache) ===') + const start3 = Date.now() + const results = await Promise.all([ + cachedApiCall('user:2'), + cachedApiCall('user:2'), + cachedApiCall('user:2') + ]) + console.log(`Time: ${Date.now() - start3}ms\n`) + + console.log('=== After TTL Expiry ===') + console.log('Waiting 4 seconds...') + await new Promise(resolve => setTimeout(resolve, 4000)) + const start4 = Date.now() + await cachedApiCall('user:1') + console.log(`Time: ${Date.now() - start4}ms`) +} + +main() + +/** + * 캐싱 고렀사항: + * - TTL 섀정: 데읎터 특성에 맞는 적절한 만료 시간 + * - 메몚늬 ꎀ늬: LRU 알고늬슘윌로 였래된 항목 제거 + * - 묎횚화 전략: 데읎터 변겜 시 캐시 동Ʞ화 + * - 분산 캐시: 여러 서버 간 캐시 공유 (Redis 등) + */ diff --git a/chapter11/kilhyeonjun/code/08-cancel-basic.js b/chapter11/kilhyeonjun/code/08-cancel-basic.js new file mode 100644 index 0000000..10e4015 --- /dev/null +++ b/chapter11/kilhyeonjun/code/08-cancel-basic.js @@ -0,0 +1,116 @@ +/** + * 08-cancel-basic.js + * 비동Ʞ 작업 췚소 - Ʞ볞 팹턮 + * + * cancelRequested 플래귞륌 사용한 췚소 구현 + */ + +// 컀슀텀 췚소 에러 +class CancelError extends Error { + constructor(message = 'Operation cancelled') { + super(message) + this.name = 'CancelError' + } +} + +// 비동Ʞ 작업 시뮬레읎션 +async function asyncStep(name, durationMs) { + console.log(`[Step] Starting: ${name}`) + await new Promise(resolve => setTimeout(resolve, durationMs)) + console.log(`[Step] Completed: ${name}`) + return `Result of ${name}` +} + +// 췚소 가능한 비동Ʞ 핚수 (Ʞ볞 팹턮) +async function cancellableOperation(cancelObj) { + const results = [] + + // Step 1 + const res1 = await asyncStep('Step 1', 500) + if (cancelObj.cancelRequested) { + throw new CancelError('Cancelled after Step 1') + } + results.push(res1) + + // Step 2 + const res2 = await asyncStep('Step 2', 500) + if (cancelObj.cancelRequested) { + throw new CancelError('Cancelled after Step 2') + } + results.push(res2) + + // Step 3 + const res3 = await asyncStep('Step 3', 500) + if (cancelObj.cancelRequested) { + throw new CancelError('Cancelled after Step 3') + } + results.push(res3) + + return results +} + +// 싀행 예제 +async function main() { + console.log('=== Example 1: Complete without cancellation ===') + const cancelObj1 = { cancelRequested: false } + + try { + const results = await cancellableOperation(cancelObj1) + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Operation was cancelled:', err.message) + } else { + throw err + } + } + + console.log('\n=== Example 2: Cancel after 700ms ===') + const cancelObj2 = { cancelRequested: false } + + // 700ms 후 췚소 요청 + setTimeout(() => { + console.log('[Main] Requesting cancellation...') + cancelObj2.cancelRequested = true + }, 700) + + try { + const results = await cancellableOperation(cancelObj2) + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Operation was cancelled:', err.message) + } else { + throw err + } + } + + console.log('\n=== Example 3: Cancel before start ===') + const cancelObj3 = { cancelRequested: true } + + try { + const results = await cancellableOperation(cancelObj3) + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Operation was cancelled:', err.message) + } else { + throw err + } + } +} + +main() + +/** + * Ʞ볞 팚턎의 장닚점: + * + * 장점: + * - 간닚하고 읎핎하Ʞ 쉬움 + * - 왞부 띌읎람러늬 불필요 + * + * 닚점: + * - ë§€ 닚계마닀 수동윌로 첎크 필요 + * - 윔드 쀑복 발생 + * - 비동Ʞ 혞출 진행 쀑에는 췚소 불가 + */ diff --git a/chapter11/kilhyeonjun/code/09-cancel-wrapper.js b/chapter11/kilhyeonjun/code/09-cancel-wrapper.js new file mode 100644 index 0000000..d64c507 --- /dev/null +++ b/chapter11/kilhyeonjun/code/09-cancel-wrapper.js @@ -0,0 +1,145 @@ +/** + * 09-cancel-wrapper.js + * 비동Ʞ 작업 췚소 - 래퍌 팹턮 + * + * 비동Ʞ 혞출을 래핑하여 췚소 로직 재사용 + */ + +// 컀슀텀 췚소 에러 +class CancelError extends Error { + constructor(message = 'Operation cancelled') { + super(message) + this.name = 'CancelError' + } +} + +// 췚소 가능한 래퍌 생성 팩토늬 +function createCancelWrapper() { + let cancelRequested = false + + function cancel() { + cancelRequested = true + } + + // 비동Ʞ 핚수륌 래핑하는 핚수 + function cancelWrapper(asyncFn, ...args) { + if (cancelRequested) { + return Promise.reject(new CancelError()) + } + return asyncFn(...args) + } + + return { cancel, cancelWrapper } +} + +// 비동Ʞ 작업 시뮬레읎션 +async function asyncStep(name, durationMs) { + console.log(`[Step] Starting: ${name}`) + await new Promise(resolve => setTimeout(resolve, durationMs)) + console.log(`[Step] Completed: ${name}`) + return `Result of ${name}` +} + +// 췚소 래퍌륌 사용한 핚수 +async function operationWithWrapper(cancelWrapper) { + const results = [] + + // 각 비동Ʞ 혞출을 래퍌로 감싞Ʞ + results.push(await cancelWrapper(asyncStep, 'Step 1', 500)) + results.push(await cancelWrapper(asyncStep, 'Step 2', 500)) + results.push(await cancelWrapper(asyncStep, 'Step 3', 500)) + + return results +} + +// HTTP 요청 시뮬레읎션 +async function fetchData(url) { + console.log(`[Fetch] Requesting: ${url}`) + await new Promise(resolve => setTimeout(resolve, 300)) + console.log(`[Fetch] Received: ${url}`) + return { url, data: 'response data' } +} + +// 복잡한 작업 예제 +async function complexOperation(cancelWrapper) { + // 순찚 요청 + const user = await cancelWrapper(fetchData, '/api/user') + const orders = await cancelWrapper(fetchData, `/api/orders?user=${user.url}`) + + // 병렬 요청 + const [products, reviews] = await Promise.all([ + cancelWrapper(fetchData, '/api/products'), + cancelWrapper(fetchData, '/api/reviews') + ]) + + return { user, orders, products, reviews } +} + +async function main() { + console.log('=== Example 1: Complete without cancellation ===') + const { cancel: cancel1, cancelWrapper: wrapper1 } = createCancelWrapper() + + try { + const results = await operationWithWrapper(wrapper1) + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Cancelled:', err.message) + } else { + throw err + } + } + + console.log('\n=== Example 2: Cancel during operation ===') + const { cancel: cancel2, cancelWrapper: wrapper2 } = createCancelWrapper() + + setTimeout(() => { + console.log('[Main] Cancelling...') + cancel2() + }, 700) + + try { + const results = await operationWithWrapper(wrapper2) + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Cancelled:', err.message) + } else { + throw err + } + } + + console.log('\n=== Example 3: Complex operation with cancellation ===') + const { cancel: cancel3, cancelWrapper: wrapper3 } = createCancelWrapper() + + setTimeout(() => { + console.log('[Main] Cancelling complex operation...') + cancel3() + }, 500) + + try { + const results = await complexOperation(wrapper3) + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Cancelled:', err.message) + } else { + throw err + } + } +} + +main() + +/** + * 래퍌 팚턎의 장닚점: + * + * 장점: + * - 췚소 로직 재사용 + * - 윔드가 더 깔끔핚 + * - Ʞ졎 핚수 수정 없읎 적용 가능 + * + * 닚점: + * - 몚든 비동Ʞ 혞출을 래핑핎알 핹 + * - 래핑을 잊윌멎 췚소가 안됚 + */ diff --git a/chapter11/kilhyeonjun/code/10-cancel-generator.js b/chapter11/kilhyeonjun/code/10-cancel-generator.js new file mode 100644 index 0000000..f39aaca --- /dev/null +++ b/chapter11/kilhyeonjun/code/10-cancel-generator.js @@ -0,0 +1,178 @@ +/** + * 10-cancel-generator.js + * 비동Ʞ 작업 췚소 - 제너레읎터 팹턮 + * + * 제너레읎터륌 사용하여 자동윌로 췚소 포읞튞 생성 + */ + +// 컀슀텀 췚소 에러 +class CancelError extends Error { + constructor(message = 'Operation cancelled') { + super(message) + this.name = 'CancelError' + } +} + +// 제너레읎터 êž°ë°˜ 췚소 가능한 비동Ʞ 핚수 생성 +function createAsyncCancelable(generatorFn) { + return function(...args) { + const generator = generatorFn(...args) + let cancelRequested = false + + function cancel() { + cancelRequested = true + } + + const promise = new Promise((resolve, reject) => { + async function step(nextValue) { + // 췚소 요청 확읞 + if (cancelRequested) { + return reject(new CancelError()) + } + + let result + try { + result = generator.next(nextValue) + } catch (err) { + return reject(err) + } + + if (result.done) { + return resolve(result.value) + } + + try { + // Promise 대Ʞ 후 닀음 닚계 + const value = await result.value + step(value) + } catch (err) { + try { + // 에러륌 제너레읎터에 전달 + result = generator.throw(err) + if (result.done) { + return resolve(result.value) + } + step(result.value) + } catch (thrownErr) { + reject(thrownErr) + } + } + } + + step() + }) + + return { promise, cancel } + } +} + +// 비동Ʞ 작업 시뮬레읎션 +async function asyncStep(name, durationMs) { + console.log(`[Step] Starting: ${name}`) + await new Promise(resolve => setTimeout(resolve, durationMs)) + console.log(`[Step] Completed: ${name}`) + return `Result of ${name}` +} + +// 제너레읎터 핚수로 비동Ʞ 로직 정의 +const cancellableOperation = createAsyncCancelable(function* () { + const results = [] + + // yield로 비동Ʞ 작업 싀행 - 각 yield가 췚소 포읞튞 + results.push(yield asyncStep('Step 1', 500)) + results.push(yield asyncStep('Step 2', 500)) + results.push(yield asyncStep('Step 3', 500)) + + return results +}) + +// 더 복잡한 예제 +const complexOperation = createAsyncCancelable(function* () { + console.log('[Complex] Starting complex operation') + + const user = yield asyncStep('Fetch User', 300) + console.log('[Complex] Got user:', user) + + const orders = yield asyncStep('Fetch Orders', 300) + console.log('[Complex] Got orders:', orders) + + // 조걎부 로직도 가능 + if (orders) { + const details = yield asyncStep('Fetch Details', 300) + console.log('[Complex] Got details:', details) + } + + return { user, orders } +}) + +async function main() { + console.log('=== Example 1: Complete without cancellation ===') + const { promise: promise1, cancel: cancel1 } = cancellableOperation() + + try { + const results = await promise1 + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Cancelled:', err.message) + } else { + throw err + } + } + + console.log('\n=== Example 2: Cancel during operation ===') + const { promise: promise2, cancel: cancel2 } = cancellableOperation() + + setTimeout(() => { + console.log('[Main] Cancelling...') + cancel2() + }, 700) + + try { + const results = await promise2 + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Cancelled:', err.message) + } else { + throw err + } + } + + console.log('\n=== Example 3: Complex operation ===') + const { promise: promise3, cancel: cancel3 } = complexOperation() + + setTimeout(() => { + console.log('[Main] Cancelling complex operation...') + cancel3() + }, 500) + + try { + const results = await promise3 + console.log('Results:', results) + } catch (err) { + if (err instanceof CancelError) { + console.log('Cancelled:', err.message) + } else { + throw err + } + } +} + +main() + +/** + * 제너레읎터 팚턎의 장닚점: + * + * 장점: + * - 몚든 yield가 자동윌로 췚소 포읞튞 + * - 비슈니슀 로직에 췚소 윔드 없음 + * - 가독성읎 좋음 + * + * 닚점: + * - 제너레읎터 묞법 읎핎 필요 + * - 앜간의 성능 였버헀드 + * + * ì°žê³ : caf 띌읎람러늬가 읎 팚턎을 구현 + * https://github.com/getify/CAF + */ diff --git a/chapter11/kilhyeonjun/code/11-subset-sum-blocking.js b/chapter11/kilhyeonjun/code/11-subset-sum-blocking.js new file mode 100644 index 0000000..87c0eb9 --- /dev/null +++ b/chapter11/kilhyeonjun/code/11-subset-sum-blocking.js @@ -0,0 +1,109 @@ +/** + * 11-subset-sum-blocking.js + * CPU 바욎드 작업 - 읎벀튞 룚프 찚닚 묞제 + * + * 부분집합 합계 묞제 (Subset Sum Problem) + * 죌얎진 집합에서 합읎 특정 값읎 되는 부분집합 ì°Ÿêž° + * 시간 복잡도: O(2^n) - NP-완전 묞제 + */ + +// 부분집합 합계 묞제 - 동Ʞ 버전 (읎벀튞 룚프 찚닚) +class SubsetSumSync { + constructor(set, sum) { + this.set = set + this.sum = sum + this.results = [] + } + + // 재귀적윌로 몚든 조합 탐색 + _combine(set, subset) { + for (let i = 0; i < set.length; i++) { + const newSubset = subset.concat(set[i]) + const currentSum = newSubset.reduce((a, b) => a + b, 0) + + if (currentSum === this.sum) { + this.results.push(newSubset) + } + + // 낚은 원소듀로 계속 탐색 + this._combine(set.slice(i + 1), newSubset) + } + } + + run() { + this._combine(this.set, []) + return this.results + } +} + +// 읎벀튞 룚프 찚닚 시연 +async function demonstrateBlocking() { + console.log('=== Event Loop Blocking Demo ===\n') + + // 작은 집합 테슀튞 + console.log('Small set (10 elements):') + const smallSet = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + const smallSum = 15 + + const start1 = Date.now() + const solver1 = new SubsetSumSync(smallSet, smallSum) + const results1 = solver1.run() + console.log(`Found ${results1.length} solutions in ${Date.now() - start1}ms`) + console.log('Sample solutions:', results1.slice(0, 3)) + + // 쀑간 크Ʞ 집합 테슀튞 + console.log('\nMedium set (20 elements):') + const mediumSet = Array.from({ length: 20 }, (_, i) => i + 1) + const mediumSum = 50 + + const start2 = Date.now() + const solver2 = new SubsetSumSync(mediumSet, mediumSum) + const results2 = solver2.run() + console.log(`Found ${results2.length} solutions in ${Date.now() - start2}ms`) + + // 읎벀튞 룚프 찚닚 시연 + console.log('\n=== Demonstrating Event Loop Blocking ===') + console.log('Starting computation...') + + // setInterval로 읎벀튞 룚프 몚니터링 + let ticks = 0 + const tickInterval = setInterval(() => { + ticks++ + console.log(`[Tick ${ticks}] Event loop is running`) + }, 100) + + // 큰 집합 계산 (읎벀튞 룚프 찚닚) + setTimeout(() => { + console.log('\n[Compute] Starting heavy computation...') + const heavySet = Array.from({ length: 22 }, (_, i) => i + 1) + const heavySum = 100 + + const start = Date.now() + const solver = new SubsetSumSync(heavySet, heavySum) + const results = solver.run() + console.log(`[Compute] Done! Found ${results.length} solutions in ${Date.now() - start}ms`) + console.log('[Compute] Notice how tick messages stopped during computation!') + + clearInterval(tickInterval) + }, 500) + + // 2쎈 후 정늬 + setTimeout(() => { + console.log('\n[End] Demo complete') + }, 10000) +} + +demonstrateBlocking() + +/** + * 묞제점: + * - 동Ʞ 계산 쀑 읎벀튞 룚프가 완전히 찚닚됚 + * - 타읎뚞, I/O 윜백읎 싀행되지 않음 + * - HTTP 요청 처늬 불가 + * - 서버가 응답하지 않는 것처럌 볎임 + * + * 핎결책: + * - setImmediate 읞터늬빙 (12-subset-sum-interleaving.js) + * - 왞부 프로섞슀 (13-process-pool.js) + * - 작업자 슀레드 (14-thread-pool.js) + */ diff --git a/chapter11/kilhyeonjun/code/12-subset-sum-interleaving.js b/chapter11/kilhyeonjun/code/12-subset-sum-interleaving.js new file mode 100644 index 0000000..32bfffa --- /dev/null +++ b/chapter11/kilhyeonjun/code/12-subset-sum-interleaving.js @@ -0,0 +1,161 @@ +/** + * 12-subset-sum-interleaving.js + * CPU 바욎드 작업 - setImmediate 읞터늬빙 + * + * setImmediate()륌 사용하여 계산 닚계 사읎에 + * 닀륞 I/O 작업읎 싀행될 수 있게 핹 + */ + +import { EventEmitter } from 'events' + +// 부분집합 합계 묞제 - 읞터늬빙 버전 +class SubsetSumInterleaved extends EventEmitter { + constructor(set, sum) { + super() + this.set = set + this.sum = sum + this.results = [] + this.iterations = 0 + } + + _combineInterleaved(set, subset, callback) { + this.iterations++ + + // 죌Ʞ적윌로 진행 상황 읎벀튞 발생 + if (this.iterations % 10000 === 0) { + this.emit('progress', { + iterations: this.iterations, + results: this.results.length + }) + } + + // setImmediate륌 사용하여 닀륞 작업에 양볎 + setImmediate(() => { + if (set.length === 0) { + return callback() + } + + // 현재 원소륌 포핚하는 겜우 처늬 + const currentElement = set[0] + const remaining = set.slice(1) + const newSubset = subset.concat(currentElement) + const currentSum = newSubset.reduce((a, b) => a + b, 0) + + if (currentSum === this.sum) { + this.results.push(newSubset) + this.emit('match', newSubset) + } + + // 현재 원소륌 포핚하는 ë¶„êž° + this._combineInterleaved(remaining, newSubset, () => { + // 현재 원소륌 제왞하는 ë¶„êž° + this._combineInterleaved(remaining, subset, callback) + }) + }) + } + + run(callback) { + const startTime = Date.now() + this._combineInterleaved(this.set, [], () => { + const duration = Date.now() - startTime + callback(null, { + results: this.results, + iterations: this.iterations, + duration + }) + }) + } + + // Promise 버전 + runAsync() { + return new Promise((resolve, reject) => { + this.run((err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } +} + +// 읎벀튞 룚프가 찚닚되지 않음을 시연 +async function demonstrateInterleaving() { + console.log('=== setImmediate Interleaving Demo ===\n') + + const set = Array.from({ length: 18 }, (_, i) => i + 1) + const sum = 50 + + console.log(`Finding subsets of [1..${set.length}] that sum to ${sum}`) + console.log('Notice how tick messages continue during computation!\n') + + // 읎벀튞 룚프 몚니터링 + let ticks = 0 + const tickInterval = setInterval(() => { + ticks++ + console.log(`[Tick ${ticks}] Event loop is responsive`) + }, 200) + + // 읞터늬빙 계산 싀행 + const solver = new SubsetSumInterleaved(set, sum) + + // 진행 상황 읎벀튞 늬슀너 + solver.on('progress', ({ iterations, results }) => { + console.log(`[Progress] Iterations: ${iterations}, Found: ${results} solutions`) + }) + + // 결곌 찟을 때마닀 읎벀튞 + let matchCount = 0 + solver.on('match', (subset) => { + matchCount++ + if (matchCount <= 3) { + console.log(`[Match #${matchCount}] ${JSON.stringify(subset)}`) + } + }) + + try { + const result = await solver.runAsync() + console.log(`\n=== Computation Complete ===`) + console.log(`Total solutions: ${result.results.length}`) + console.log(`Total iterations: ${result.iterations}`) + console.log(`Duration: ${result.duration}ms`) + console.log(`Ticks during computation: ${ticks}`) + } finally { + clearInterval(tickInterval) + } +} + +// 비교: 동Ʞ vs 읞터늬빙 +async function comparePerformance() { + console.log('\n=== Performance Comparison ===\n') + + const set = Array.from({ length: 15 }, (_, i) => i + 1) + const sum = 30 + + // 읞터늬빙 버전 + console.log('Interleaved version:') + const solver = new SubsetSumInterleaved(set, sum) + const result = await solver.runAsync() + console.log(`Found ${result.results.length} solutions in ${result.duration}ms`) + + console.log('\nNote: Interleaving has overhead but keeps event loop responsive') +} + +demonstrateInterleaving().then(comparePerformance) + +/** + * setImmediate 읞터늬빙의 장닚점: + * + * 장점: + * - 읎벀튞 룚프가 찚닚되지 않음 + * - 추가 프로섞슀/슀레드 불필요 + * - 메몚늬 공유 가능 + * + * 닚점: + * - 성능 였버헀드 (컚텍슀튞 슀위칭) + * - 닚계가 Ꞟ멎 여전히 지연 발생 + * - 닚음 윔얎만 사용 + * + * 적합한 겜우: + * - 빠륞 닚계로 분할 가능한 작업 + * - 읎벀튞 룚프 응답성읎 쀑요한 겜우 + * - 늬소슀가 제한된 환겜 + */ diff --git a/chapter11/kilhyeonjun/code/13-process-pool.js b/chapter11/kilhyeonjun/code/13-process-pool.js new file mode 100644 index 0000000..41da3fb --- /dev/null +++ b/chapter11/kilhyeonjun/code/13-process-pool.js @@ -0,0 +1,178 @@ +/** + * 13-process-pool.js + * CPU 바욎드 작업 - 왞부 프로섞슀 풀 + * + * child_process.fork()륌 사용하여 작업을 + * 별도의 프로섞슀에서 싀행 + */ + +import { fork } from 'child_process' +import { EventEmitter } from 'events' +import { dirname, join } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +// 프로섞슀 풀 큎래슀 +class ProcessPool extends EventEmitter { + constructor(workerScript, poolSize) { + super() + this.workerScript = workerScript + this.poolSize = poolSize + this.workers = [] + this.freeWorkers = [] + this.queue = [] + + // 워컀 프로섞슀 생성 + for (let i = 0; i < poolSize; i++) { + this._createWorker() + } + } + + _createWorker() { + const worker = fork(this.workerScript) + worker.id = this.workers.length + + worker.on('message', (msg) => { + if (msg.type === 'result') { + const { resolve, reject } = worker.currentTask + worker.currentTask = null + + if (msg.error) { + reject(new Error(msg.error)) + } else { + resolve(msg.data) + } + + // 워컀륌 닀시 사용 가능 상태로 + this.freeWorkers.push(worker) + this._processQueue() + } + }) + + worker.on('error', (err) => { + console.error(`[Worker ${worker.id}] Error:`, err) + if (worker.currentTask) { + worker.currentTask.reject(err) + } + }) + + worker.on('exit', (code) => { + console.log(`[Worker ${worker.id}] Exited with code ${code}`) + // 필요시 워컀 재생성 로직 추가 + }) + + this.workers.push(worker) + this.freeWorkers.push(worker) + console.log(`[Pool] Created worker ${worker.id}`) + } + + _processQueue() { + if (this.queue.length === 0 || this.freeWorkers.length === 0) { + return + } + + const task = this.queue.shift() + const worker = this.freeWorkers.shift() + + worker.currentTask = task + worker.send({ type: 'task', data: task.data }) + } + + // 작업 싀행 + run(taskData) { + return new Promise((resolve, reject) => { + const task = { data: taskData, resolve, reject } + this.queue.push(task) + this._processQueue() + }) + } + + // 풀 종료 + async shutdown() { + console.log('[Pool] Shutting down...') + for (const worker of this.workers) { + worker.send({ type: 'shutdown' }) + } + // 몚든 워컀가 종료될 때까지 대Ʞ + await new Promise(resolve => setTimeout(resolve, 100)) + console.log('[Pool] Shutdown complete') + } + + // 상태 정볎 + getStats() { + return { + totalWorkers: this.workers.length, + freeWorkers: this.freeWorkers.length, + queuedTasks: this.queue.length + } + } +} + +// 메읞 싀행 +async function main() { + console.log('=== Process Pool Demo ===\n') + + // 워컀 슀크늜튞 겜로 + const workerScript = join(__dirname, '13-process-worker.js') + + // 풀 생성 (CPU 윔얎 수에 맞춰 조정) + const pool = new ProcessPool(workerScript, 4) + console.log('') + + // 읎벀튞 룚프 몚니터링 + let ticks = 0 + const tickInterval = setInterval(() => { + ticks++ + const stats = pool.getStats() + console.log(`[Tick ${ticks}] Event loop responsive | Workers: ${stats.freeWorkers}/${stats.totalWorkers} free`) + }, 200) + + try { + // 여러 작업 병렬 싀행 + console.log('\n=== Running Multiple Tasks ===') + const tasks = [ + { set: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], sum: 30 }, + { set: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20], sum: 40 }, + { set: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], sum: 35 }, + { set: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50], sum: 100 } + ] + + const startTime = Date.now() + const results = await Promise.all( + tasks.map(task => pool.run(task)) + ) + + console.log(`\n=== Results ===`) + results.forEach((result, i) => { + console.log(`Task ${i + 1}: Found ${result.count} solutions in ${result.duration}ms`) + }) + console.log(`Total time: ${Date.now() - startTime}ms (parallel execution)`) + console.log(`Event loop ticks during execution: ${ticks}`) + + } finally { + clearInterval(tickInterval) + await pool.shutdown() + } +} + +main().catch(console.error) + +/** + * 왞부 프로섞슀의 장닚점: + * + * 장점: + * - 읎벀튞 룚프 완전 분늬 + * - 멀티윔얎 활용 + * - 크래시 격늬 (워컀 크래시가 메읞 프로섞슀에 영향 없음) + * + * 닚점: + * - 프로섞슀 생성 비용 + * - IPC 였버헀드 + * - 메몚늬 사용량 슝가 + * + * 적합한 겜우: + * - 장시간 싀행되는 CPU 집앜적 작업 + * - 멀티윔얎 활용읎 필요한 겜우 + * - 격늬가 필요한 신뢰할 수 없는 윔드 싀행 + */ diff --git a/chapter11/kilhyeonjun/code/13-process-worker.js b/chapter11/kilhyeonjun/code/13-process-worker.js new file mode 100644 index 0000000..425708c --- /dev/null +++ b/chapter11/kilhyeonjun/code/13-process-worker.js @@ -0,0 +1,58 @@ +/** + * 13-process-worker.js + * 프로섞슀 풀의 워컀 슀크늜튞 + * + * 부분집합 합계 묞제륌 계산하는 워컀 + */ + +// 부분집합 합계 계산 +function subsetSum(set, sum) { + const results = [] + + function combine(remaining, subset) { + for (let i = 0; i < remaining.length; i++) { + const newSubset = subset.concat(remaining[i]) + const currentSum = newSubset.reduce((a, b) => a + b, 0) + + if (currentSum === sum) { + results.push(newSubset) + } + + combine(remaining.slice(i + 1), newSubset) + } + } + + combine(set, []) + return results +} + +// 메시지 처늬 +process.on('message', (msg) => { + if (msg.type === 'task') { + const { set, sum } = msg.data + const startTime = Date.now() + + try { + const results = subsetSum(set, sum) + const duration = Date.now() - startTime + + process.send({ + type: 'result', + data: { + count: results.length, + duration, + sample: results.slice(0, 3) + } + }) + } catch (error) { + process.send({ + type: 'result', + error: error.message + }) + } + } else if (msg.type === 'shutdown') { + process.exit(0) + } +}) + +console.log(`[Worker ${process.pid}] Ready`) diff --git a/chapter11/kilhyeonjun/code/14-thread-pool.js b/chapter11/kilhyeonjun/code/14-thread-pool.js new file mode 100644 index 0000000..41e7204 --- /dev/null +++ b/chapter11/kilhyeonjun/code/14-thread-pool.js @@ -0,0 +1,235 @@ +/** + * 14-thread-pool.js + * CPU 바욎드 작업 - 작업자 슀레드 풀 + * + * worker_threads륌 사용하여 작업을 + * 별도의 슀레드에서 싀행 + */ + +import { Worker, isMainThread, parentPort, workerData } from 'worker_threads' +import { EventEmitter } from 'events' +import { fileURLToPath } from 'url' + +// 워컀 슀레드 윔드 (같은 파음에서 싀행) +if (!isMainThread) { + // 부분집합 합계 계산 + function subsetSum(set, sum) { + const results = [] + + function combine(remaining, subset) { + for (let i = 0; i < remaining.length; i++) { + const newSubset = subset.concat(remaining[i]) + const currentSum = newSubset.reduce((a, b) => a + b, 0) + + if (currentSum === sum) { + results.push(newSubset) + } + + combine(remaining.slice(i + 1), newSubset) + } + } + + combine(set, []) + return results + } + + // 메시지 처늬 + parentPort.on('message', (msg) => { + if (msg.type === 'task') { + const { set, sum } = msg.data + const startTime = Date.now() + + try { + const results = subsetSum(set, sum) + const duration = Date.now() - startTime + + parentPort.postMessage({ + type: 'result', + data: { + count: results.length, + duration, + sample: results.slice(0, 3) + } + }) + } catch (error) { + parentPort.postMessage({ + type: 'result', + error: error.message + }) + } + } + }) +} else { + // 메읞 슀레드 윔드 + + // 슀레드 풀 큎래슀 + class ThreadPool extends EventEmitter { + constructor(poolSize) { + super() + this.poolSize = poolSize + this.workers = [] + this.freeWorkers = [] + this.queue = [] + + // 워컀 슀레드 생성 + for (let i = 0; i < poolSize; i++) { + this._createWorker() + } + } + + _createWorker() { + const worker = new Worker(fileURLToPath(import.meta.url)) + worker.id = this.workers.length + + worker.on('message', (msg) => { + if (msg.type === 'result') { + const { resolve, reject } = worker.currentTask + worker.currentTask = null + + if (msg.error) { + reject(new Error(msg.error)) + } else { + resolve(msg.data) + } + + // 워컀륌 닀시 사용 가능 상태로 + this.freeWorkers.push(worker) + this._processQueue() + } + }) + + worker.on('error', (err) => { + console.error(`[Thread ${worker.id}] Error:`, err) + if (worker.currentTask) { + worker.currentTask.reject(err) + } + }) + + worker.on('exit', (code) => { + if (code !== 0) { + console.error(`[Thread ${worker.id}] Exited with code ${code}`) + } + }) + + this.workers.push(worker) + this.freeWorkers.push(worker) + console.log(`[Pool] Created thread ${worker.id}`) + } + + _processQueue() { + if (this.queue.length === 0 || this.freeWorkers.length === 0) { + return + } + + const task = this.queue.shift() + const worker = this.freeWorkers.shift() + + worker.currentTask = task + worker.postMessage({ type: 'task', data: task.data }) + } + + // 작업 싀행 + run(taskData) { + return new Promise((resolve, reject) => { + const task = { data: taskData, resolve, reject } + this.queue.push(task) + this._processQueue() + }) + } + + // 풀 종료 + async shutdown() { + console.log('[Pool] Shutting down threads...') + await Promise.all( + this.workers.map(worker => worker.terminate()) + ) + console.log('[Pool] Shutdown complete') + } + + // 상태 정볎 + getStats() { + return { + totalWorkers: this.workers.length, + freeWorkers: this.freeWorkers.length, + queuedTasks: this.queue.length + } + } + } + + // 메읞 싀행 + async function main() { + console.log('=== Thread Pool Demo ===\n') + + // 풀 생성 + const pool = new ThreadPool(4) + console.log('') + + // 읎벀튞 룚프 몚니터링 + let ticks = 0 + const tickInterval = setInterval(() => { + ticks++ + const stats = pool.getStats() + console.log(`[Tick ${ticks}] Event loop responsive | Threads: ${stats.freeWorkers}/${stats.totalWorkers} free`) + }, 200) + + try { + // 여러 작업 병렬 싀행 + console.log('\n=== Running Multiple Tasks ===') + const tasks = [ + { set: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], sum: 30 }, + { set: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20], sum: 40 }, + { set: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], sum: 35 }, + { set: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50], sum: 100 } + ] + + const startTime = Date.now() + const results = await Promise.all( + tasks.map(task => pool.run(task)) + ) + + console.log(`\n=== Results ===`) + results.forEach((result, i) => { + console.log(`Task ${i + 1}: Found ${result.count} solutions in ${result.duration}ms`) + }) + console.log(`Total time: ${Date.now() - startTime}ms (parallel execution)`) + console.log(`Event loop ticks during execution: ${ticks}`) + + // 프로섞슀 풀곌 비교 + console.log('\n=== Thread vs Process Comparison ===') + console.log('Threads are lighter weight than processes:') + console.log('- Faster startup time') + console.log('- Lower memory overhead') + console.log('- Can share memory via SharedArrayBuffer') + + } finally { + clearInterval(tickInterval) + await pool.shutdown() + } + } + + main().catch(console.error) +} + +/** + * 작업자 슀레드의 장닚점: + * + * 장점: + * - 프로섞슀볎닀 가벌움 + * - 빠륞 시작 시간 + * - SharedArrayBuffer로 메몚늬 공유 가능 + * - 읎벀튞 룚프 완전 분늬 + * + * 닚점: + * - Node.js 10.5+ 필요 + * - 복잡한 에러 처늬 + * - 공유 메몚늬 사용 시 동Ʞ화 필요 + * + * 권장 띌읎람러늬: + * - workerpool: https://github.com/josdejong/workerpool + * - piscina: https://github.com/piscinajs/piscina + * + * 적합한 겜우: + * - 빈번한 CPU 집앜적 작업 + * - 메몚늬 공유가 필요한 겜우 + * - 낮은 레읎턎시가 쀑요한 겜우 + */ diff --git a/chapter11/kilhyeonjun/code/README.md b/chapter11/kilhyeonjun/code/README.md new file mode 100644 index 0000000..ed6a60c --- /dev/null +++ b/chapter11/kilhyeonjun/code/README.md @@ -0,0 +1,113 @@ +# Chapter 11 윔드 예제 + +Chapter 11에서 닀룚는 고꞉ 레시플의 윔드 예제입니닀. + +## 싀행 방법 + +```bash +# 개별 파음 싀행 +node --experimental-vm-modules 01-db-without-queue.js + +# ES 몚듈 사용 파음은 package.json에 "type": "module" 필요 +``` + +## 파음 목록 + +### 1. 비동Ʞ 쎈Ʞ화 (01-05) + +| 파음 | 섀명 | +|------|------| +| `01-db-without-queue.js` | 묞제 상황: 쎈Ʞ화 전 쿌늬 시 에러 발생 | +| `02-db-local-init.js` | 핎결책 1: 맀번 연결 상태 확읞 | +| `03-db-delayed-startup.js` | 핎결책 2: 몚든 쎈Ʞ화 완료 후 싀행 | +| `04-db-preinitialization-queue.js` | 핎결책 3: 사전 쎈Ʞ화 큐 (권장) | +| `05-db-state-pattern.js` | 상태 팚턎윌로 구조화 | + +### 2. 요청 배치 및 캐싱 (06-07) + +| 파음 | 섀명 | +|------|------| +| `06-batch-api.js` | 동음 요청 배치 처늬 (플Ʞ백) | +| `07-cache-api.js` | TTL êž°ë°˜ 캐싱 + 배치 | + +### 3. 비동Ʞ 작업 췚소 (08-10) + +| 파음 | 섀명 | +|------|------| +| `08-cancel-basic.js` | Ʞ볞 팹턮: cancelRequested 플래귞 | +| `09-cancel-wrapper.js` | 래퍌 팹턮: createCancelWrapper | +| `10-cancel-generator.js` | 제너레읎터 팹턮: createAsyncCancelable | + +### 4. CPU 바욎드 작업 (11-14) + +| 파음 | 섀명 | +|------|------| +| `11-subset-sum-blocking.js` | 묞제 상황: 읎벀튞 룚프 찚닚 | +| `12-subset-sum-interleaving.js` | 핎결책 1: setImmediate 읞터늬빙 | +| `13-process-pool.js` | 핎결책 2: 왞부 프로섞슀 풀 | +| `13-process-worker.js` | 프로섞슀 풀 워컀 슀크늜튞 | +| `14-thread-pool.js` | 핎결책 3: 작업자 슀레드 풀 | + +## 핵심 윔드 슀니펫 + +### 사전 쎈Ʞ화 큐 + +```javascript +// 04-db-preinitialization-queue.js +async query(queryString) { + if (!this.connected) { + return new Promise((resolve, reject) => { + this.commandsQueue.push(() => { + this.query(queryString).then(resolve, reject) + }) + }) + } + // 싀제 쿌늬 싀행 +} +``` + +### 요청 배치 + +```javascript +// 06-batch-api.js +if (runningRequests.has(key)) { + return runningRequests.get(key) // 플Ʞ백 +} +const promise = originalApi(key) +runningRequests.set(key, promise) +promise.finally(() => runningRequests.delete(key)) +``` + +### 제너레읎터 êž°ë°˜ 췚소 + +```javascript +// 10-cancel-generator.js +const cancellable = createAsyncCancelable(function* () { + const a = yield asyncStep('A') // 췚소 포읞튞 + const b = yield asyncStep('B') // 췚소 포읞튞 + return [a, b] +}) +const { promise, cancel } = cancellable() +``` + +### 슀레드 풀 + +```javascript +// 14-thread-pool.js +const pool = new ThreadPool(4) +const results = await Promise.all([ + pool.run({ set: [1,2,3], sum: 5 }), + pool.run({ set: [4,5,6], sum: 10 }) +]) +``` + +## 연습묞제 + +`exercises/` 디렉토늬에서 연습묞제 풀읎륌 확읞할 수 있습니닀. + +| 파음 | 연습묞제 | +|------|----------| +| `11.1-proxy-queue.js` | Proxy륌 사용한 대Ʞ엎 구현 | +| `11.2-callback-batch-cache.js` | 윜백 êž°ë°˜ 배치 및 캐싱 | +| `11.3-deep-cancelable.js` | Deep 췚소 가능한 비동Ʞ 핚수 | +| `11.4-computing-farm.js` | 컎퓚팅 팜 (HTTP + eval/vm) | diff --git a/chapter11/kilhyeonjun/code/exercises/11.1-proxy-queue.js b/chapter11/kilhyeonjun/code/exercises/11.1-proxy-queue.js new file mode 100644 index 0000000..3cf38f8 --- /dev/null +++ b/chapter11/kilhyeonjun/code/exercises/11.1-proxy-queue.js @@ -0,0 +1,178 @@ +/** + * 11.1-proxy-queue.js + * 연습묞제 11.1: Proxy륌 사용한 대Ʞ엎 구현 + * + * Proxy륌 사용하여 비동Ʞ로 쎈Ʞ화되는 몚든 컎포넌튞에 + * 대Ʞ엎을 투명하게 적용하는 음반 래퍌 + */ + +import { EventEmitter, once } from 'events' + +/** + * 프록시 êž°ë°˜ 쎈Ʞ화 대Ʞ엎 래퍌 + * + * @param {Object} target - 래핑할 객첎 + * @param {Function} initFn - 쎈Ʞ화 핚수 (Promise 반환) + * @returns {Proxy} 대Ʞ엎읎 적용된 프록시 객첎 + */ +function createQueuedProxy(target, initFn) { + let initialized = false + let initPromise = null + const queue = [] + + // 쎈Ʞ화 시작 + initPromise = initFn().then(() => { + initialized = true + // 대Ʞ 쀑읞 혞출 싀행 + queue.forEach(({ method, args, resolve, reject }) => { + Promise.resolve(target[method](...args)) + .then(resolve) + .catch(reject) + }) + queue.length = 0 + }) + + return new Proxy(target, { + get(obj, prop) { + const value = obj[prop] + + // 핚수가 아니멎 귞대로 반환 + if (typeof value !== 'function') { + return value + } + + // 쎈Ʞ화 완료 후에는 원볞 핚수 반환 + if (initialized) { + return value.bind(obj) + } + + // 쎈Ʞ화 전에는 큐에 저장하는 래퍌 반환 + return function(...args) { + return new Promise((resolve, reject) => { + queue.push({ method: prop, args, resolve, reject }) + }) + } + } + }) +} + +// 예제: 비동Ʞ 쎈Ʞ화가 필요한 DB 큎래슀 +class Database extends EventEmitter { + constructor() { + super() + this.connected = false + this.data = new Map() + } + + async connect() { + console.log('[DB] Connecting...') + await new Promise(resolve => setTimeout(resolve, 500)) + this.connected = true + console.log('[DB] Connected!') + this.emit('connected') + } + + async query(sql) { + if (!this.connected) { + throw new Error('Not connected') + } + console.log(`[DB] Executing: ${sql}`) + await new Promise(resolve => setTimeout(resolve, 100)) + return { rows: [], sql } + } + + async insert(table, data) { + if (!this.connected) { + throw new Error('Not connected') + } + console.log(`[DB] Inserting into ${table}:`, data) + const id = Date.now() + this.data.set(id, { table, ...data }) + return { id } + } +} + +// 예제: 비동Ʞ 쎈Ʞ화가 필요한 캐시 큎래슀 +class Cache extends EventEmitter { + constructor() { + super() + this.ready = false + this.store = new Map() + } + + async initialize() { + console.log('[Cache] Initializing...') + await new Promise(resolve => setTimeout(resolve, 300)) + this.ready = true + console.log('[Cache] Ready!') + this.emit('ready') + } + + async get(key) { + if (!this.ready) throw new Error('Not ready') + console.log(`[Cache] Getting: ${key}`) + return this.store.get(key) + } + + async set(key, value, ttl = 60000) { + if (!this.ready) throw new Error('Not ready') + console.log(`[Cache] Setting: ${key} = ${value}`) + this.store.set(key, value) + setTimeout(() => this.store.delete(key), ttl) + return true + } +} + +// 사용 예제 +async function main() { + console.log('=== Proxy Queue Demo ===\n') + + // 1. Database with proxy queue + console.log('--- Database Example ---') + const rawDb = new Database() + const db = createQueuedProxy(rawDb, () => rawDb.connect()) + + // 쎈Ʞ화 전에 혞출 - 자동윌로 큐에 저장됚 + const promise1 = db.query('SELECT * FROM users') + const promise2 = db.insert('users', { name: 'Alice' }) + const promise3 = db.query('SELECT * FROM orders') + + console.log('[Main] All calls queued, waiting for results...') + const results = await Promise.all([promise1, promise2, promise3]) + console.log('[Main] Results:', results) + + // 쎈Ʞ화 후 혞출 - 바로 싀행 + console.log('\n[Main] Now calling after initialization:') + const result = await db.query('SELECT * FROM products') + console.log('[Main] Immediate result:', result) + + // 2. Cache with proxy queue + console.log('\n--- Cache Example ---') + const rawCache = new Cache() + const cache = createQueuedProxy(rawCache, () => rawCache.initialize()) + + // 쎈Ʞ화 전에 혞출 + const setPromise = cache.set('user:1', { name: 'Bob' }) + const getPromise = cache.get('user:1') + + console.log('[Main] Cache calls queued...') + await setPromise + const cachedValue = await getPromise + console.log('[Main] Cached value:', cachedValue) +} + +main().catch(console.error) + +/** + * Proxy êž°ë°˜ 대Ʞ엎의 장점: + * + * 1. 투명성: 사용자 윔드에서 쎈Ʞ화 상태륌 전혀 신겜 쓰지 않아도 됚 + * 2. 음반성: ì–Žë–€ 객첎에도 적용 가능 + * 3. 자동화: 몚든 메서드 혞출을 자동윌로 처늬 + * + * 확장 가능한 Ʞ능: + * - 특정 메서드만 대Ʞ엎 적용 + * - 재쎈Ʞ화 지원 + * - 타임아웃 처늬 + * - 에러 핞듀링 개선 + */ diff --git a/chapter11/kilhyeonjun/code/exercises/11.2-callback-batch-cache.js b/chapter11/kilhyeonjun/code/exercises/11.2-callback-batch-cache.js new file mode 100644 index 0000000..33e809e --- /dev/null +++ b/chapter11/kilhyeonjun/code/exercises/11.2-callback-batch-cache.js @@ -0,0 +1,215 @@ +/** + * 11.2-callback-batch-cache.js + * 연습묞제 11.2: 윜백 êž°ë°˜ 배치 및 캐싱 + * + * Promise 없읎 순수 윜백 방식윌로 + * 요청 배치 및 캐싱 구현 + */ + +/** + * 윜백 êž°ë°˜ 배치 래퍌 + * + * @param {Function} originalFn - 원볞 비동Ʞ 핚수 (key, callback) + * @returns {Function} 배치가 적용된 핚수 + */ +function createBatchedCallback(originalFn) { + // 진행 쀑읞 요청 저장: key -> [callback1, callback2, ...] + const pendingRequests = new Map() + + return function batchedFn(key, callback) { + // 읎믞 진행 쀑읞 요청읎 있윌멎 윜백만 추가 + if (pendingRequests.has(key)) { + console.log(`[Batch] Piggybacking on: ${key}`) + pendingRequests.get(key).push(callback) + return + } + + // 새 요청 시작 + console.log(`[Batch] Starting new request: ${key}`) + pendingRequests.set(key, [callback]) + + originalFn(key, (err, result) => { + // 대Ʞ 쀑읞 몚든 윜백 혞출 + const callbacks = pendingRequests.get(key) + pendingRequests.delete(key) + + console.log(`[Batch] Notifying ${callbacks.length} callbacks for: ${key}`) + callbacks.forEach(cb => cb(err, result)) + }) + } +} + +/** + * 윜백 êž°ë°˜ 캐시 + 배치 래퍌 + * + * @param {Function} originalFn - 원볞 비동Ʞ 핚수 (key, callback) + * @param {number} ttlMs - 캐시 TTL (밀늬쎈) + * @returns {Object} { cachedFn, invalidate, invalidateAll } + */ +function createCachedCallback(originalFn, ttlMs = 5000) { + const pendingRequests = new Map() + const cache = new Map() + + function cachedFn(key, callback) { + // 1. 캐시 확읞 + const cached = cache.get(key) + if (cached) { + const age = Date.now() - cached.timestamp + if (age < ttlMs) { + console.log(`[Cache] HIT for ${key} (age: ${age}ms)`) + // 비동Ʞ로 윜백 혞출 (Zalgo 방지) + setImmediate(() => callback(null, cached.value)) + return + } + console.log(`[Cache] EXPIRED for ${key}`) + cache.delete(key) + } + + // 2. 배치 확읞 + if (pendingRequests.has(key)) { + console.log(`[Batch] Piggybacking on: ${key}`) + pendingRequests.get(key).push(callback) + return + } + + // 3. 새 요청 + console.log(`[Cache] MISS for ${key}`) + pendingRequests.set(key, [callback]) + + originalFn(key, (err, result) => { + const callbacks = pendingRequests.get(key) + pendingRequests.delete(key) + + // 성공 시 캐시에 저장 + if (!err) { + cache.set(key, { + value: result, + timestamp: Date.now() + }) + } + + callbacks.forEach(cb => cb(err, result)) + }) + } + + // 캐시 묎횚화 + function invalidate(key) { + console.log(`[Cache] Invalidating: ${key}`) + cache.delete(key) + } + + function invalidateAll() { + console.log(`[Cache] Invalidating all`) + cache.clear() + } + + return { cachedFn, invalidate, invalidateAll } +} + +// 테슀튞용 느며 API (윜백 êž°ë°˜) +function slowApiCallback(key, callback) { + console.log(`[API] Request started for: ${key}`) + setTimeout(() => { + console.log(`[API] Request completed for: ${key}`) + callback(null, { + key, + data: `Result for ${key}`, + timestamp: Date.now() + }) + }, 500) +} + +// 테슀튞 +function runTests() { + console.log('=== Callback-based Batch & Cache Demo ===\n') + + // 1. 배치만 적용 + console.log('--- Test 1: Batching Only ---') + const batchedApi = createBatchedCallback(slowApiCallback) + + let completed = 0 + const checkComplete = (expected, next) => { + return (err, result) => { + completed++ + console.log(`[Result] Received:`, result?.key) + if (completed === expected && next) { + setTimeout(next, 100) + } + } + } + + // 동시에 3개의 동음한 요청 + batchedApi('user:1', checkComplete(3)) + batchedApi('user:1', checkComplete(3)) + batchedApi('user:1', checkComplete(3, testCaching)) + + // 2. 캐시 + 배치 적용 + function testCaching() { + console.log('\n--- Test 2: Caching + Batching ---') + completed = 0 + + const { cachedFn, invalidate } = createCachedCallback(slowApiCallback, 2000) + + // 첫 번짞 혞출 (캐시 믞슀) + cachedFn('user:2', (err, result) => { + console.log('[Result 1] First call:', result?.key) + + // 두 번짞 혞출 (캐시 히튾) + cachedFn('user:2', (err, result) => { + console.log('[Result 2] Second call (should be cached):', result?.key) + + // 캐시 묎횚화 후 섞 번짞 혞출 + invalidate('user:2') + cachedFn('user:2', (err, result) => { + console.log('[Result 3] Third call (after invalidation):', result?.key) + + testConcurrent() + }) + }) + }) + } + + // 3. 동시 요청 + 캐시 + function testConcurrent() { + console.log('\n--- Test 3: Concurrent Requests with Cache ---') + completed = 0 + + const { cachedFn } = createCachedCallback(slowApiCallback, 5000) + + const done = (label) => (err, result) => { + completed++ + console.log(`[${label}] Got:`, result?.key) + if (completed === 4) { + console.log('\n=== All Tests Complete ===') + } + } + + // 동시 요청 - 첫 번짞는 API 혞출, 나뚞지는 배치 + cachedFn('user:3', done('Request A')) + cachedFn('user:3', done('Request B')) + cachedFn('user:3', done('Request C')) + + // 앜간의 딜레읎 후 요청 - 캐시 히튾 + setTimeout(() => { + cachedFn('user:3', done('Request D (delayed)')) + }, 600) + } +} + +runTests() + +/** + * 윜백 êž°ë°˜ 구현의 특징: + * + * 1. Promise 없읎 동작 + * - 레거시 윔드와 혾환 + * - 낮은 였버헀드 + * + * 2. Zalgo 방지 + * - 캐시 히튾 시에도 setImmediate로 비동Ʞ 혞출 + * - 음ꎀ된 비동Ʞ 동작 볎장 + * + * 3. 윜백 ë°°ì—Ž ꎀ늬 + * - Map에 윜백 ë°°ì—Ž 저장 + * - 완료 시 몚든 윜백에 결곌 전달 + */ diff --git a/chapter11/kilhyeonjun/code/exercises/11.3-deep-cancelable.js b/chapter11/kilhyeonjun/code/exercises/11.3-deep-cancelable.js new file mode 100644 index 0000000..7f32bd3 --- /dev/null +++ b/chapter11/kilhyeonjun/code/exercises/11.3-deep-cancelable.js @@ -0,0 +1,283 @@ +/** + * 11.3-deep-cancelable.js + * 연습묞제 11.3: Deep 췚소 가능한 비동Ʞ 핚수 + * + * 쀑첩된 췚소 가능 핚수에서 룚튞 핚수 췚소 시 + * 몚든 쀑첩 핚수까지 췚소되는 Ʞ능 구현 + */ + +// 췚소 에러 +class CancelError extends Error { + constructor(message = 'Operation cancelled') { + super(message) + this.name = 'CancelError' + } +} + +/** + * Deep 췚소 가능한 비동Ʞ 핚수 생성Ʞ + * + * 췚소 컚텍슀튞륌 자식 핚수에 전파하여 + * 쀑첩된 몚든 핚수가 췚소될 수 있도록 핹 + */ +class CancelContext { + constructor(parent = null) { + this.parent = parent + this.children = new Set() + this.cancelRequested = false + this.cancelCallbacks = new Set() + + // 부몚에 등록 + if (parent) { + parent.children.add(this) + // 부몚가 읎믞 췚소됐윌멎 자식도 췚소 + if (parent.cancelRequested) { + this.cancelRequested = true + } + } + } + + // 췚소 요청 + cancel() { + if (this.cancelRequested) return + + this.cancelRequested = true + console.log(`[Cancel] Context cancelled, propagating to ${this.children.size} children`) + + // 윜백 싀행 + this.cancelCallbacks.forEach(cb => cb()) + + // 자식듀도 췚소 + this.children.forEach(child => child.cancel()) + } + + // 췚소 윜백 등록 + onCancel(callback) { + if (this.cancelRequested) { + callback() + return + } + this.cancelCallbacks.add(callback) + return () => this.cancelCallbacks.delete(callback) + } + + // 췚소 확읞 + throwIfCancelled() { + if (this.cancelRequested) { + throw new CancelError() + } + } + + // 자식 컚텍슀튞 생성 + createChild() { + return new CancelContext(this) + } + + // 정늬 + cleanup() { + if (this.parent) { + this.parent.children.delete(this) + } + } +} + +/** + * Deep 췚소 가능한 비동Ʞ 핚수 래퍌 + */ +function createDeepCancelable(asyncFn) { + return function(ctx = new CancelContext()) { + const promise = (async () => { + try { + return await asyncFn(ctx) + } finally { + ctx.cleanup() + } + })() + + return { + promise, + cancel: () => ctx.cancel(), + context: ctx + } + } +} + +// 비동Ʞ 작업 시뮬레읎션 +async function delay(ms, ctx) { + return new Promise((resolve, reject) => { + const timer = setTimeout(resolve, ms) + + // 췚소 시 타읎뚞 정늬 + ctx.onCancel(() => { + clearTimeout(timer) + reject(new CancelError()) + }) + }) +} + +// 예제: 쀑첩된 췚소 가능 핚수듀 +const innerOperation = createDeepCancelable(async (ctx) => { + console.log('[Inner] Starting...') + ctx.throwIfCancelled() + + await delay(300, ctx) + console.log('[Inner] Step 1 done') + + ctx.throwIfCancelled() + await delay(300, ctx) + console.log('[Inner] Step 2 done') + + return 'Inner result' +}) + +const middleOperation = createDeepCancelable(async (ctx) => { + console.log('[Middle] Starting...') + ctx.throwIfCancelled() + + await delay(200, ctx) + console.log('[Middle] Step 1 done') + + // 자식 컚텍슀튞로 낎부 작업 혞출 + const childCtx = ctx.createChild() + const { promise: innerPromise } = innerOperation(childCtx) + const innerResult = await innerPromise + console.log('[Middle] Inner result:', innerResult) + + ctx.throwIfCancelled() + await delay(200, ctx) + console.log('[Middle] Step 2 done') + + return 'Middle result' +}) + +const outerOperation = createDeepCancelable(async (ctx) => { + console.log('[Outer] Starting...') + ctx.throwIfCancelled() + + await delay(100, ctx) + console.log('[Outer] Step 1 done') + + // 자식 컚텍슀튞로 쀑간 작업 혞출 + const childCtx = ctx.createChild() + const { promise: middlePromise } = middleOperation(childCtx) + const middleResult = await middlePromise + console.log('[Outer] Middle result:', middleResult) + + ctx.throwIfCancelled() + await delay(100, ctx) + console.log('[Outer] Step 2 done') + + return 'Outer result' +}) + +// 테슀튞 +async function main() { + console.log('=== Deep Cancelable Demo ===\n') + + // Test 1: 정상 완료 + console.log('--- Test 1: Normal completion ---') + try { + const { promise, cancel, context } = outerOperation() + const result = await promise + console.log('Final result:', result) + } catch (err) { + console.log('Error:', err.message) + } + + console.log('\n--- Test 2: Cancel from root (after 500ms) ---') + try { + const { promise, cancel, context } = outerOperation() + + // 500ms 후 췚소 (Inner 싀행 쀑에 췚소됚) + setTimeout(() => { + console.log('\n[Main] Requesting cancellation...') + cancel() + }, 500) + + const result = await promise + console.log('Final result:', result) + } catch (err) { + if (err instanceof CancelError) { + console.log('[Main] Operation was cancelled successfully') + } else { + throw err + } + } + + console.log('\n--- Test 3: Cancel immediately ---') + try { + const { promise, cancel } = outerOperation() + + // 슉시 췚소 + cancel() + + const result = await promise + console.log('Final result:', result) + } catch (err) { + if (err instanceof CancelError) { + console.log('[Main] Immediate cancellation worked') + } else { + throw err + } + } + + // Test 4: 병렬 쀑첩 작업 + console.log('\n--- Test 4: Parallel nested operations ---') + + const parallelOperation = createDeepCancelable(async (ctx) => { + console.log('[Parallel] Starting 3 concurrent tasks...') + + const child1 = ctx.createChild() + const child2 = ctx.createChild() + const child3 = ctx.createChild() + + const results = await Promise.all([ + innerOperation(child1).promise.catch(e => `Task 1: ${e.message}`), + innerOperation(child2).promise.catch(e => `Task 2: ${e.message}`), + innerOperation(child3).promise.catch(e => `Task 3: ${e.message}`) + ]) + + return results + }) + + try { + const { promise, cancel } = parallelOperation() + + setTimeout(() => { + console.log('\n[Main] Cancelling parallel operations...') + cancel() + }, 400) + + const result = await promise + console.log('Parallel results:', result) + } catch (err) { + if (err instanceof CancelError) { + console.log('[Main] Parallel operation cancelled') + } else { + throw err + } + } +} + +main().catch(console.error) + +/** + * Deep 췚소의 핵심: + * + * 1. CancelContext 계잵 구조 + * - 부몚-자식 ꎀ계로 컚텍슀튞 연결 + * - 부몚 췚소 시 몚든 자식에게 전파 + * + * 2. 췚소 윜백 + * - 진행 쀑읞 비동Ʞ 작업(타읎뚞, fetch 등) 정늬 + * - 늬소슀 누수 방지 + * + * 3. 명시적 췚소 확읞 + * - throwIfCancelled()로 췚소 포읞튞 생성 + * - 각 닚계에서 췚소 확읞 + * + * 확장 가능: + * - AbortController와 통합 + * - 췚소 읎유 전달 + * - 부분 췚소 (특정 자식만) + */ diff --git a/chapter11/kilhyeonjun/code/exercises/11.4-computing-farm.js b/chapter11/kilhyeonjun/code/exercises/11.4-computing-farm.js new file mode 100644 index 0000000..01bf076 --- /dev/null +++ b/chapter11/kilhyeonjun/code/exercises/11.4-computing-farm.js @@ -0,0 +1,408 @@ +/** + * 11.4-computing-farm.js + * 연습묞제 11.4: 컎퓚팅 팜 + * + * HTTP로 작업을 분산하고 vm 몚듈로 동적 윔드륌 싀행하는 + * 분산 컎퓚팅 시슀템 구현 + */ + +import { createServer } from 'http' +import { request as httpRequest } from 'http' +import vm from 'vm' + +// ============================================ +// Worker Node (작업자 녾드) +// ============================================ + +class WorkerNode { + constructor(port) { + this.port = port + this.taskCount = 0 + this.server = null + } + + start() { + this.server = createServer((req, res) => { + if (req.method === 'POST' && req.url === '/execute') { + this._handleExecute(req, res) + } else if (req.method === 'GET' && req.url === '/status') { + this._handleStatus(req, res) + } else { + res.writeHead(404) + res.end('Not Found') + } + }) + + this.server.listen(this.port, () => { + console.log(`[Worker] Listening on port ${this.port}`) + }) + } + + async _handleExecute(req, res) { + let body = '' + for await (const chunk of req) { + body += chunk + } + + try { + const { code, context = {} } = JSON.parse(body) + this.taskCount++ + + console.log(`[Worker:${this.port}] Executing task #${this.taskCount}`) + const startTime = Date.now() + + // vm윌로 안전하게 윔드 싀행 + const result = await this._executeCode(code, context) + const duration = Date.now() - startTime + + console.log(`[Worker:${this.port}] Task completed in ${duration}ms`) + + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ + success: true, + result, + duration, + worker: this.port + })) + } catch (error) { + console.error(`[Worker:${this.port}] Error:`, error.message) + res.writeHead(500, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ + success: false, + error: error.message, + worker: this.port + })) + } + } + + _handleStatus(req, res) { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ + port: this.port, + taskCount: this.taskCount, + uptime: process.uptime() + })) + } + + async _executeCode(code, contextData) { + // 샌드박슀 컚텍슀튞 생성 + const sandbox = { + console: { + log: (...args) => console.log(`[Sandbox]`, ...args) + }, + setTimeout, + Promise, + Array, + Object, + Math, + JSON, + ...contextData, + result: undefined + } + + // 컚텍슀튞 생성 + const context = vm.createContext(sandbox) + + // 타임아웃곌 핚께 윔드 싀행 + const script = new vm.Script(` + (async () => { + ${code} + })() + `) + + const result = await script.runInContext(context, { + timeout: 5000 // 5쎈 타임아웃 + }) + + return result + } + + stop() { + if (this.server) { + this.server.close() + console.log(`[Worker:${this.port}] Stopped`) + } + } +} + +// ============================================ +// Farm Coordinator (팜 윔디넀읎터) +// ============================================ + +class FarmCoordinator { + constructor(workerPorts) { + this.workers = workerPorts.map(port => ({ + port, + host: 'localhost', + busy: false, + taskCount: 0 + })) + this.taskQueue = [] + this.roundRobinIndex = 0 + } + + // 띌욎드 로빈 방식윌로 워컀 선택 + _selectWorker() { + const availableWorkers = this.workers.filter(w => !w.busy) + if (availableWorkers.length === 0) { + return null + } + + this.roundRobinIndex = (this.roundRobinIndex + 1) % availableWorkers.length + return availableWorkers[this.roundRobinIndex] + } + + // 가장 적게 사용된 워컀 선택 + _selectLeastBusyWorker() { + const availableWorkers = this.workers.filter(w => !w.busy) + if (availableWorkers.length === 0) { + return null + } + + return availableWorkers.reduce((min, w) => + w.taskCount < min.taskCount ? w : min + ) + } + + // 작업 싀행 + async execute(code, context = {}) { + const worker = this._selectLeastBusyWorker() + + if (!worker) { + // 몚든 워컀가 바쁘멎 큐에 저장 + return new Promise((resolve, reject) => { + this.taskQueue.push({ code, context, resolve, reject }) + }) + } + + worker.busy = true + worker.taskCount++ + + try { + const result = await this._sendToWorker(worker, code, context) + return result + } finally { + worker.busy = false + this._processQueue() + } + } + + // 큐 처늬 + _processQueue() { + if (this.taskQueue.length === 0) return + + const worker = this._selectLeastBusyWorker() + if (!worker) return + + const task = this.taskQueue.shift() + this.execute(task.code, task.context) + .then(task.resolve) + .catch(task.reject) + } + + // HTTP로 워컀에 작업 전송 + _sendToWorker(worker, code, context) { + return new Promise((resolve, reject) => { + const data = JSON.stringify({ code, context }) + + const req = httpRequest({ + hostname: worker.host, + port: worker.port, + path: '/execute', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(data) + } + }, (res) => { + let body = '' + res.on('data', chunk => body += chunk) + res.on('end', () => { + try { + const result = JSON.parse(body) + if (result.success) { + resolve(result) + } else { + reject(new Error(result.error)) + } + } catch (e) { + reject(e) + } + }) + }) + + req.on('error', reject) + req.write(data) + req.end() + }) + } + + // 병렬 싀행 + async executeParallel(tasks) { + return Promise.all( + tasks.map(task => this.execute(task.code, task.context)) + ) + } + + // ë§µ-늬듀슀 싀행 + async mapReduce(data, mapCode, reduceCode) { + // Map 닚계: 각 데읎터 청크륌 병렬 처늬 + const chunks = this._splitArray(data, this.workers.length) + const mapTasks = chunks.map(chunk => ({ + code: mapCode, + context: { data: chunk } + })) + + console.log(`[Farm] Map phase: ${mapTasks.length} tasks`) + const mapResults = await this.executeParallel(mapTasks) + + // Reduce 닚계: 결곌 합치Ʞ + console.log(`[Farm] Reduce phase`) + const reduceResult = await this.execute(reduceCode, { + results: mapResults.map(r => r.result) + }) + + return reduceResult + } + + _splitArray(arr, n) { + const chunks = [] + const chunkSize = Math.ceil(arr.length / n) + for (let i = 0; i < arr.length; i += chunkSize) { + chunks.push(arr.slice(i, i + chunkSize)) + } + return chunks + } + + // 상태 조회 + async getStatus() { + const statuses = await Promise.all( + this.workers.map(w => + new Promise((resolve) => { + const req = httpRequest({ + hostname: w.host, + port: w.port, + path: '/status', + method: 'GET' + }, (res) => { + let body = '' + res.on('data', chunk => body += chunk) + res.on('end', () => { + try { + resolve(JSON.parse(body)) + } catch { + resolve({ port: w.port, error: 'Parse error' }) + } + }) + }) + req.on('error', () => resolve({ port: w.port, error: 'Unreachable' })) + req.end() + }) + ) + ) + return statuses + } +} + +// ============================================ +// 데몚 싀행 +// ============================================ + +async function main() { + console.log('=== Computing Farm Demo ===\n') + + // 워컀 녾드 시작 + const workerPorts = [3001, 3002, 3003] + const workers = workerPorts.map(port => new WorkerNode(port)) + workers.forEach(w => w.start()) + + // 워컀가 시작될 때까지 대Ʞ + await new Promise(resolve => setTimeout(resolve, 500)) + + // 팜 윔디넀읎터 생성 + const farm = new FarmCoordinator(workerPorts) + + try { + // 1. 닚순 작업 싀행 + console.log('\n--- Test 1: Simple execution ---') + const result1 = await farm.execute(` + const sum = Array.from({ length: 100 }, (_, i) => i + 1) + .reduce((a, b) => a + b, 0); + return sum; + `) + console.log('Result:', result1) + + // 2. 컚텍슀튞와 핚께 싀행 + console.log('\n--- Test 2: With context ---') + const result2 = await farm.execute(` + const doubled = numbers.map(n => n * multiplier); + return doubled; + `, { + numbers: [1, 2, 3, 4, 5], + multiplier: 10 + }) + console.log('Result:', result2) + + // 3. 병렬 싀행 + console.log('\n--- Test 3: Parallel execution ---') + const tasks = [ + { code: 'return "Task A: " + Math.random()' }, + { code: 'return "Task B: " + Math.random()' }, + { code: 'return "Task C: " + Math.random()' } + ] + const results = await farm.executeParallel(tasks) + console.log('Results:', results.map(r => r.result)) + + // 4. Map-Reduce + console.log('\n--- Test 4: Map-Reduce ---') + const data = Array.from({ length: 100 }, (_, i) => i + 1) + + const mapResult = await farm.mapReduce( + data, + // Map: 각 청크의 합계 + `return data.reduce((a, b) => a + b, 0)`, + // Reduce: 몚든 합계의 합 + `return results.reduce((a, b) => a + b, 0)` + ) + console.log('Map-Reduce result:', mapResult.result) + console.log('Expected:', data.reduce((a, b) => a + b, 0)) + + // 5. 상태 확읞 + console.log('\n--- Worker Status ---') + const status = await farm.getStatus() + console.log(status) + + } finally { + // 정늬 + workers.forEach(w => w.stop()) + } +} + +main().catch(console.error) + +/** + * 컎퓚팅 팜의 특징: + * + * 1. HTTP êž°ë°˜ 분산 + * - 워컀 녞드가 HTTP 서버로 동작 + * - 넀튞워크륌 통한 작업 분배 + * - ì–žì–Ž/플랫폌 독늜적 확장 가능 + * + * 2. vm êž°ë°˜ 안전한 싀행 + * - 샌드박슀 환겜에서 윔드 싀행 + * - 타임아웃 섀정윌로 묎한 룚프 방지 + * - 제한된 API만 ë…žì¶œ + * + * 3. 로드 밞런싱 + * - 띌욎드 로빈 / 최소 사용 선택 + * - 작업 큐로 곌부하 방지 + * + * 4. Map-Reduce 팹턮 + * - 대규몚 데읎터 병렬 처늬 + * - 자동 청크 분할 + * + * 볎안 죌의사항: + * - 프로덕션에서는 더 엄격한 샌드박싱 필요 + * - 읞슝/읞가 추가 필요 + * - 늬소슀 제한 (메몚늬, CPU) 필요 + */