Event Loop íºì볎Ʞ
ð¥ ë€ìŽê°ë©°
ìµê·Œ NestJS, FastAPI, WebFluxì êŽì¬ìŽ ìꞰ멎ì ìì°ì€ëœê² Event Loopì ëíŽ êŽì¬ìŽ ìêž°ê² ëìë€.
ìž íë ììí¬ë ìžìŽë, ìíê³ë ë€ë¥Žì§ë§ ê³µíµì ìŒë¡ ë¹ëêž° ë Œëžë¡í¹(Async Non-blocking) ìŽëŒë í€ìë륌 ê°ì¡°íë€. ê·ž ì€ì¬ìë íì Event Loopê° ìë€. ìŽ êžììë Event Loopê° ë¬Žììžì§, ê° íë ììí¬ìì ìŽë»ê² 구íëëì§ë¥Œ íšê» ìŽíŽë³Žë € íë€.
1. ì Event Loopìžê°? â ì€ë ë êž°ë° ìë²ì íê³
ì íµì ìž ì¹ ìë²ë ìì² íëì ì€ë ë íë륌 í ë¹íë ë°©ììŒë¡ ëìíë€. Spring MVCë Django(Ʞ볞 ì€ì )ê° ëíì ìŽë€.
1
2
3
4
ìì² 1 âââ Thread 1 (DB 쿌늬 ëêž° ì€... ð¥±)
ìì² 2 âââ Thread 2 (íìŒ ìœêž° ëêž° ì€... ð¥±)
ìì² 3 âââ Thread 3 (ìžë¶ API ëêž° ì€... ð¥±)
ìì² 4 âââ ëêž°ìŽ... (ì€ë ë í ê³ ê°)
ìŽ ë°©ìì 묞ì ë ëª ííë€.
- ì€ë ëë ë¹ìžë€. ì€ë ë íëë Ʞ볞ì ìŒë¡ ì MBì ì€í ë©ëªšëŠ¬ë¥Œ ì°šì§íë€.
- I/O ìì ëì ì€ë ëê° ëë¹ëë€. DB ìëµì êž°ë€ëЬë ëì ì€ë ëë ì묎 ìŒë íì§ ìê³ ëžë¡í¹ëë€.
- ëì ì ììê° ëìŽë ìë¡ ì€ë ë íìŽ ê³ ê°ëê³ , ê²°êµ ìì²ìŽ íì ììŽê±°ë ê±°ì ëë€.
ìŽ ë¬žì 륌 íŽê²°íêž° ìíŽ ë±ì¥í ê²ìŽ ë Œëžë¡í¹ I/O + Event Loop 몚ëžìŽë€.
íµì¬ ììŽëìŽ: âI/Oê° ëë ëê¹ì§ êž°ë€ëŠ¬ì§ ë§ê³ , ëë멎 ëìê² ìë €ì€.â
2. Event Loopì ì늬
ìë¹ ë¹ì ë¡ ìŽíŽíêž°
ëžë¡í¹ ë°©ìì í ì§ììŽ ìë í ëª ì ìììŽ ëì¬ ëê¹ì§ 죌방 ììì êž°ë€ëЬë ìë¹ê³Œ ê°ë€. ìëìŽ 10ëª ìŽë©Ž ì§ìë 10ëª ìŽ íìíë€.
Event Loop ë°©ìì í ëª ì ì§ììŽ ì£Œë¬žì ë°ê³ , 죌방ì ì ë¬í ë€, ë€ë¥ž ìëì ìëíë€ê°, ìììŽ ëì€ë©Ž ê°ì žë€ì£Œë ë°©ììŽë€. ì§ì í ëª ìŽ íšì¬ ë§ì ìëì ì²ëЬí ì ìë€.
ëžëŒì°ì /ë°íìì êµ¬ì± ìì
Event Loopë íŒì ëìíì§ ìëë€. ëžëŒì°ì (ëë Node.js) ììì ì¬ë¬ êµ¬ì± ììê° íë ¥íë€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â ëžëŒì°ì / Node.js â
â â
â ââââââââââââââââ ââââââââââââââââââââââââââââ â
â â Call Stack â â Web APIs / Node APIs â â
â â (JS ìì§) â â (Timer, fetch, I/O...) â â
â ââââââââ¬ââââââââ ââââââââââââââ¬ââââââââââââââ â
â â â ìë£ ì ìœë°± ì ë¬ â
â ââââââââŒââââââââââââââââââââââââŒââââââââââââââ â
â â Callback Queue â â
â â âââââââââââââââââââ âââââââââââââââââââ â â
â â â Microtask Queue â â Task Queue â â â
â â â (Promise, await)â â(setTimeout, I/O)â â â
â â âââââââââââââââââââ âââââââââââââââââââ â â
â âââââââââââââââââââââââââââââââââââââââââââââââ â
â â â
â ââââââââââŽâââââââââ â
â â Event Loop â â ê°ì & ìŽë â
â âââââââââââââââââââ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ê° êµ¬ì± ììê° íë ìŒì ìŽë ë€.
- Call Stack: JS ìì§ìŽ ìœë륌 ì€ííë ê³µê°. íšì ížì¶ìŽ ììŽê³ ë°íëë©° ë¹ìì§ë€.
- Heap: ëì ìŒë¡ ìì±ë ê°ì²Žê° ì ì¥ëë ë©ëªšëЬ ê³µê°.
- Web APIs / Node APIs: ëžëŒì°ì (ëë Node.js)ê° ì ê³µíë ë¹ëêž° ìì ì²ëЬ ê³µê°. Timer, fetch, íìŒ I/O ë±ìŽ ì¬êž°ì ë©í° ì€ë ëë¡ ì²ëЬëë€.
- Event Table: í¹ì ìŽë²€íž(click, timeout ë±)ê° ë°ìíì ë ìŽë€ ìœë°±ì ížì¶í ì§ êž°ë¡íë ìë£êµ¬ì¡°.
- Task Queue (Macrotask Queue):
setTimeout,setInterval, I/O ìœë°±ìŽ ëêž°íë ê³³. - Microtask Queue:
Promise.then,async/awaitìœë°±ìŽ ëêž°íë ê³³. Task Queueë³Žë€ ì°ì ììê° ëë€. - Event Loop: Call StackìŽ ë¹ìŽììŒë©Ž Queueì ìœë°±ì êºŒëŽ Call Stackì ì¬ë €ì£Œë êŽëЬì.
ë¹ëêž°ë¡ ëìíë íµì¬ì ìë°ì€í¬ëŠœíž ìžìŽ ìì²Žê° ìëëŒ, ëžëŒì°ì ëë ë°íì ìíížìšìŽê° ê°ì§ê³ ìë€. Node.jsììë libuv ëŒìŽëžë¬ëŠ¬ê° ìŽ ìí ì ëŽë¹íë€.
Event Loop ëì ìì (1 í±)
- Call StackìŽ ë¹ìŽìëì§ íìžíë€.
- Microtask Queueì ìì ìŽ ììŒë©Ž ì ë¶ ì²ëЬíë€. (Promise ìœë°± ë±)
- AnimationFrame Queueê° ììŒë©Ž ì²ëЬíë€. (ëžëŒì°ì í겜 íì )
- Task Queueìì ìì ì íë êºŒëŽ Call Stackì ì¬ëаë€.
- ë€ì 1ë²ìŒë¡ ëìê°ë€.
ìŽë²€íž 룚íë ìœ ì€í곌 ìœë°± í륌 ì§ìì ìŒë¡ íìžíë©°, ìœ ì€íìŽ ë¹ìŽììŒë©Ž ìœë°± íìì ê°ì¥ ì€ëë ìì (FIFO)ì 꺌ëŽì ìœ ì€íìŒë¡ ì®êžŽë€.
1
2
3
4
5
6
7
8
9
console.log('Start!'); // Call Stackìì ìŠì ì€í
setTimeout(() => console.log('Timeout!'), 0); // Task Queueë¡ ìŽë
Promise.resolve('Promise!').then(res => console.log(res)); // Microtask Queueë¡ ìŽë
console.log('End!'); // Call Stackìì ìŠì ì€í
// ì¶ë ¥ ìì: 1 â 4 â 3 â 2
console.log('Start!')ê° Call Stackì ì¬ëŒê° ìŠì ì€íëê³ âStart!âê° ì¶ë ¥ëë€.
setTimeoutìŽ ì€íë멎 ìœë°± íšìê° Web APIë¡ ì ë¬ëê³ íìŽëšžê° ììëë€. ëë ìŽê° 0ìŽìŽë¯ë¡ íìŽëšžë ê³§ë°ë¡ ì¢
ë£ëë€.
íìŽëšžê° ëë ìœë°±ì Task Queue(MacroTask Queue)ì ë€ìŽê° ëêž°íë€.
Promise.resolve(...).then(...)ìŽ ì€íë멎 .then() ížë€ë¬ì ìœë°±ìŽ Microtask Queueì ë€ìŽê°ë€.
console.log('End!')ê° ì€íëê³ âEnd!âê° ì¶ë ¥ëë€.
ëêž° ìœëê° ëªšë ì€íë멎 Call StackìŽ ë¹ìì§ë€. Event Loopë ìŽ ìê°ì ê°ì§íê³ Microtask Queue륌 뚌ì íìžíë€.
Microtask Queueì ëêž° ì€ìž Promise ìœë°±ìŽ êºŒëŽì ž ì€íëë€. Microtask Queueë ë¹ìì§ ëê¹ì§ í ë²ì ì ë¶ ì²ëЬëë€.
Microtask Queueê° ëªšë ë¹ìì§ë©Ž Task Queueìì setTimeout ìœë°±ì êºŒëŽ ì€ííë€.
Microtask(Promise)ê° Task(setTimeout)ë³Žë€ íì 뚌ì ì²ëЬëë€ë ì ìŽ íµì¬ìŽë€.
3. async/awaitì ì§ì§ ëì ì늬
ë§ì ì¬ëë€ìŽ async/await륌 âë¹ëꞰ륌 ëêž°ì²ëŒ ì°ë 묞ë²âìŒë¡ë§ ìŽíŽíë€. íì§ë§ ëŽë¶ì ìŒë¡ ìŽë»ê² ëìíëì§ë¥Œ 몚륎멎, ì€í ìì륌 ììž¡íêž° ìŽë €ìŽ ìí©ì ë§ëê² ëë€.
async íšìë íì Promise륌 ë°ííë€
async í€ìë륌 ë¶ìŽë©Ž, ë°íê°ìŽ ë¬ŽììŽë ìëìŒë¡ Promiseë¡ ê°ìžì ž ë°íëë€.
1
2
3
4
5
6
7
8
9
10
async function greet() {
return 'Hello';
}
// ì ìœëë ìëì ëìŒíë€
function greet() {
return Promise.resolve('Hello');
}
greet().then(val => console.log(val)); // "Hello"
ìŽ ì ìŽ ì€ìí ìŽì ë, awaitê° Promise륌 êž°ë€ëЬë 묞ë²ìŽêž° ë묞ìŽë€. async íšìëŒëЬ ìë¡ë¥Œ awaití ì ìë ê²ë ìŽ ëë¶ìŽë€.
async/awaitë Promise.thenì syntactic sugarë€
awaitë Promiseê° resolveë ëê¹ì§ íšì ì€íì ìŒì ì€ëšíë 묞ë²ìŽë€. ìŽë âì€ëšâìŽë ì€ë ë륌 ì ë ¹íë©° ëžë¡í¹íë ê²ìŽ ìëëŒ, ëëšžì§ ìœë륌 Microtask Queueì ììœíê³ íšì륌 ë¹ ì žëê°ë ê²ìŽë€.
ìë ë ìœëë ìì í ëìŒíê² ëìíë€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// async/await ë²ì
async function myFunc() {
const res = await one();
console.log(res);
console.log('Done');
}
// ì ìœëë ì¬ì€ ìëì ê°ë€
function myFunc() {
return one().then(res => {
console.log(res);
console.log('Done');
});
}
ìŠ, await ìŽíì ìœëë€ì 몚ë .then() ížë€ë¬ì ìœë°±ìŒë¡ ë³íëìŽ Microtask Queueì ë€ìŽê°ë€.
ì€í ìì륌 ëšê³ë³ë¡ ë°ëŒê°êž°
ìœë륌 í ì€ì© ì€íí멎ì Call Stack곌 Microtask Queueê° ìŽë»ê² ë³íëì§ ì§ì ë°ëŒê° 볎ì.
1
2
3
4
5
6
7
8
9
10
11
12
13
const one = () => Promise.resolve('One!');
async function myFunc() {
console.log('In function!'); // A
const res = await one(); // B: ì¬êž°ì íšì ì€í ìŒì ì€ëš
console.log(res); // C: Microtask Queueì ììœëš
}
console.log('Before Function!'); // â
myFunc(); // â¡
console.log('After Function!'); // â¢
// ì¶ë ¥: Before Function! â In function! â After Function! â One!
Step 1. console.log('Before Function!') ì€í
1
2
3
4
5
Call Stack Microtask Queue
ââââââââââââââââââââ âââââââââââââââ
console.log(...) (ë¹ìŽ ìì)
ââââââââââââââââââââ
ì¶ë ¥: "Before Function!"
Step 2. myFunc() ížì¶ â await륌 ë§ëêž° ì ê¹ì§ ëêž° ì€í
myFuncì ìŒë° íšìì²ëŒ ìŠì ì€íëë€. await륌 ë§ëêž° ì ì ìœë(A ë¶ë¶)ë Call Stackìì ë°ë¡ ì€íëë€.
1
2
3
4
5
6
Call Stack Microtask Queue
ââââââââââââââââââââ âââââââââââââââ
console.log('In...') (ë¹ìŽ ìì)
myFunc()
ââââââââââââââââââââ
ì¶ë ¥: "In function!"
Step 3. await one()ì ë§ëë ìê° â íšìê° ë¹ ì žëê°ë€
one()ìŽ ížì¶ëìŽ Promise륌 ë°ííë€. awaitë ìŽ Promiseê° resolveë ëê¹ì§ C ìŽí ìœë륌 Microtask Queueì ììœíê³ , myFuncì Call Stackìì 꺌ëžë€. ì€ë ë륌 ì ë ¹íë ê² ìëëŒ âëì€ì ë€ì ìì ì€ííŽì€âëŒê³ ììœíê³ ì늬륌 ë¹ì°ë ê²ìŽë€.
1
2
3
4
Call Stack Microtask Queue
ââââââââââââââââââââ ââââââââââââââââââââââââ
(myFunc ë¹ ì žëê°) [console.log(res)] â ììœ
ââââââââââââââââââââ
Step 4. console.log('After Function!') ì€í
myFuncìŽ ë¹ ì žëê°ìŒë¯ë¡ ë€ì ì€ìž console.log('After Function!')ê° ì€íëë€.
1
2
3
4
5
Call Stack Microtask Queue
ââââââââââââââââââââ ââââââââââââââââââââââââ
console.log(...) [console.log(res)]
ââââââââââââââââââââ
ì¶ë ¥: "After Function!"
Step 5. Call StackìŽ ë¹ì, Microtask Queueìì êºŒëŽ ì€í
Event Loopê° Call StackìŽ ë¹ìŽììì ê°ì§íê³ , Microtask Queueì ììœë ìœë°±ì êºŒëŽ ì€ííë€.
1
2
3
4
5
Call Stack Microtask Queue
ââââââââââââââââââââ âââââââââââââââ
console.log(res) (ë¹ìŽ ìì)
ââââââââââââââââââââ
ì¶ë ¥: "One!"
After Function!ìŽ One!ë³Žë€ ëšŒì ì¶ë ¥ëë ìŽì ê° ë°ë¡ ìŽê²ìŽë€. awaitë íšì륌 âì ì ë ë¬ë€ê° Promiseê° ëë멎 ëìì€ëâ êµ¬ì¡°ë¡ ë§ë ë€.
awaitê° ì¬ë¬ ê°ëŒë©Ž?
await륌 ë§ë ëë§ë€ ê·ž ìŽí ìœëê° Microtask Queueì ë€ìŽê°ë 곌ì ìŽ ë°ë³µëë€.
1
2
3
4
5
6
7
async function myFunc() {
console.log('Step 1');
const a = await stepA(); // â 첫 ë²ì§ž ì€ëš, Step 2 ìŽí ììœ
console.log('Step 2');
const b = await stepB(a); // â ë ë²ì§ž ì€ëš, Step 3 ìŽí ììœ
console.log('Step 3');
}
ê²ìŒë¡ë ììëë¡ ì€íëë ê²ì²ëŒ 볎ìŽì§ë§, ëŽë¶ì ìŒë¡ë Microtask Queue륌 ë ë² ê±°ì¹ë€. 결곌ì ìŒë¡ Step 1 â Step 2 â Step 3 ììë 볎ì¥ëë€.
죌ì: ížì¶ë¶ìë await륌 ë¶ìŽë©Ž?
1
2
3
console.log('Before Function!');
await myFunc(); // ížì¶ë¶ìë await
console.log('After Function!');
ìŽ ê²œì° myFunc()ê° ìì í ëë ëê¹ì§ ê·ž ìë ìœëë íšê» êž°ë€ëаë€.
1
2
3
4
5
// ì¶ë ¥ ìì:
// Before Function!
// In function!
// One!
// After Function!
await myFunc() ìŽíì ìœëë .then() ìœë°±ìŒë¡ ë³íëêž° ë묞ìŽë€.
íí ì€ì: await륌 ë¹ ëšëŠ¬ë©Ž?
1
2
3
4
async function myFunc() {
const res = fetchData(); // await ìì!
console.log(res); // Promise { <pending> } ì¶ë ¥ëš
}
await륌 ë¹ ëšëŠ¬ë©Ž fetchData()ê° ë°ííë Promise ê°ì²Ž ìì²Žê° resì ëŽêžŽë€. í¹í forEach ììì ìŽ ì€ìê° ì죌 ë°ìíë€.
1
2
3
4
5
6
7
8
9
10
11
// â forEachë async ìœë°±ì êž°ë€ëŠ¬ì§ ìëë€
rows.forEach(async (row) => {
await db.insert(row); // êž°ë€ëЬë ê²ì²ëŒ 볎ìŽì§ë§, forEachë ì 겜 ì°ì§ ìëë€
});
console.log('Done'); // insertê° ëëêž° ì ì ì¶ë ¥ëš
// â
for...of륌 ì¬ì©íŽìŒ ììê° ë³Žì¥ëë€
for (const row of rows) {
await db.insert(row);
}
console.log('Done'); // 몚ë insert ìë£ í ì¶ë ¥ëš
forEachë ìœë°±ì ë°íê°(Promise)ì ê·žë¥ ë¬Žìíë€. ìì륌 볎ì¥íê³ ì¶ë€ë©Ž for...of륌 ì°ì.
4. Microtask Queueì íšì â 묎í룚í
Task Queueì Microtask Queueì ì°šìŽë ëšìí ì°ì ìì ì°šìŽì²ëŒ 볎ìŽì§ë§, ì€ì ë¡ë ëžëŒì°ì ëì ì 첎ì ìí¥ì ë¯žì¹ ì ìë€.
Microtask Queueë ëžëŒì°ì ê° í멎ì ë ëë§íêž° ì ì ì²ëЬëë€. ë°ëŒì Microtask Queueê° ëìììŽ ì±ìì§ë©Ž, ëžëŒì°ì ë ë ëë§ë 못 íê³ ì¬ì©ì ì ë ¥ìë ë°ìíì§ ëª»í ì± ëš¹íµìŽ ëë€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// â
setTimeout ì¬ê· 묎í룚í â Task Queueì ìì
// ëžëŒì°ì ê° ê° ë£ší ì¬ìŽì ë ëë§, ìŽë²€íž ì²ëЬ ê°ë¥
function loop() {
setTimeout(() => {
console.log('Loop');
loop();
}, 0);
}
loop();
// â Promise ì¬ê· 묎í룚í â Microtask Queueì ìì
// Microtask Queueê° ì ë ë¹ìì§ì§ ìì ëžëŒì°ì ì 첎 ëš¹íµ
function loop() {
Promise.resolve().then(() => {
console.log('Loop');
loop();
});
}
loop(); // ìŽ ìê°ë¶í° ëžëŒì°ì ê° ìëµì ë©ì¶ë€
ë°±ìë êŽì ììë ë§ì°¬ê°ì§ë€. Node.js ìë²ìì Promise 첎ìžìŽ ë¬Žíí ìŽìŽì§ê±°ë Microtask Queue륌 곌ëíê² ì±ì°ë ë¡ì§ìŽ ìë€ë©Ž, ë€ë¥ž ìì²ë€ìŽ ì€ì€ìŽ ë°ëŠ¬ê² ëë€.
5. íë ììí¬ë³ Event Loop ë¹êµ
ìž íë ììí¬ë ê°ìì ìžìŽì ë°íì ììì Event Loop륌 ë€ë¥Žê² 구ííê³ ìë€.
Node.js (NestJSì êž°ë°)
Node.jsë libuvëŒë C ëŒìŽëžë¬ëŠ¬ë¥Œ íµíŽ Event Loop륌 구ííë€. ì±êž ì€ë ëì§ë§ libuvì ì€ë ë íì ìŽì©íŽ íìŒ I/O ë±ì ëžë¡í¹ ìì ì ì²ëЬíë€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Node.js Event Loop 6ëšê³ (Phase)
âââââââââââââââââââââââââââââ
â timers â
âââââââââââââââ¬ââââââââââââââ
â
v
âââââââââââââââââââââââââââââ
ââ>â pending callbacks â
â âââââââââââââââ¬ââââââââââââââ
â âââââââââââââââŽââââââââââââââ
â â idle, prepare â
â âââââââââââââââ¬ââââââââââââââ âââââââââââââââââ
â âââââââââââââââŽââââââââââââââ â incoming: â
â â poll â<âââââ†connections, â
â âââââââââââââââ¬ââââââââââââââ â data, etc. â
â âââââââââââââââŽââââââââââââââ âââââââââââââââââ
â â check â
â âââââââââââââââ¬ââââââââââââââ
â âââââââââââââââŽââââââââââââââ
â â close callbacks â
â âââââââââââââââ¬ââââââââââââââ
â âââââââââââââââŽââââââââââââââ
âââ†timers â
âââââââââââââââââââââââââââââ
NestJSë ìŽ ììì ëìíë©°, async/awaitì Promise륌 ìì°ì€ëœê² ì§ìíë€.
1
2
3
4
5
6
7
// NestJS Controller ìì
@Get('/users/:id')
async getUser(@Param('id') id: string) {
// await ëì Event Loopë ë€ë¥ž ìì²ì ì²ëЬíë€
const user = await this.userService.findById(id);
return user;
}
í¹ì§
- ì±êž ì€ë ë, ì±êž Event Loop
- CPU ì§ìœì ìì ì ì·šìœ (Worker Threadsë¡ ë³Žì)
- I/O ì§ìœì ìë¹ì€ì ë§€ì° ì í©
Python asyncio (FastAPIì êž°ë°)
Pythonì 3.4ë¶í° asyncio íì€ ëŒìŽëžë¬ëŠ¬ë¥Œ íµíŽ Event Loop륌 ì ê³µíë€. FastAPIë ìŽë¥Œ êž°ë°ìŒë¡ íë©°, uvicorn(ASGI ìë²)ìŽ Event Loop륌 ì€ííë€.
1
2
3
4
5
6
7
8
9
# FastAPI ìì
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# await ëì Event Loopë ë€ë¥ž ìì²ì ì²ëЬíë€
user = await db.fetch_one(
query="SELECT * FROM users WHERE id = :id",
values={"id": user_id}
)
return user
Python asyncioì 구조ë Node.jsì ê°ë ì ìŒë¡ ì ì¬íì§ë§, ì€ìí ì°šìŽê° ìë€.
| í목 | Node.js | Python asyncio |
|---|---|---|
| Event Loop ì€í | ìë (ë°íìì ëŽì¥) | ëª
ìì ì€í (asyncio.run()) |
| ìœë£šíŽ | async function | async def |
| ì볎 ìì | await | await |
| ì€ë ë í | libuv ëŽì¥ | ThreadPoolExecutor ì°ë |
â ïž FastAPIìì ì죌 íë ì€ì
1
2
3
4
5
6
7
8
9
10
11
# â ì못ë ì: async def ììì ëêž° ëžë¡í¹ ížì¶
@app.get("/bad")
async def bad_endpoint():
time.sleep(5) # Event Loop ì ì²Žê° 5ìŽ ëì ë©ì¶ë€!
return {"result": "done"}
# â
ì¬ë°ë¥ž ì: ëêž° íšìë defë¡, FastAPIê° ì€ë ëíë¡ ì²ëЬíë€
@app.get("/good")
def good_endpoint():
time.sleep(5) # FastAPIê° ë³ë ì€ë ëìì ì€í
return {"result": "done"}
Project Reactor / Netty (WebFluxì êž°ë°)
WebFluxë JVM ììì ëìíë©°, Project Reactorì Flux/Monoì Nettyì Event Loop륌 êž°ë°ìŒë¡ íë€.
Nettyì 구조 â Boss Group곌 Worker Group
Node.jsì ë¬ëЬ ë©í° ì€ë ë êž°ë°ì Event Loop륌 ì¬ì©íë€. Nettyë ìí ì ë°ëŒ ë ì¢ ë¥ì EventLoopGroupì ë¶ëЬíŽì ìŽìíë€.
- Boss Group: íŽëŒìŽìžížì TCP ì°ê²° ìì²(accept)ë§ ì ëŽ. ë³Žíµ 1ê° ì€ë ë
- Worker Group: ì€ì I/O ìœêž°/ì°êž°ë¥Œ ëŽë¹. CPU ìœìŽ ì à 2ê° ì€ë ëë¡ êµ¬ì±
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
íŽëŒìŽìžíž ì°ê²° ìì²
â
âŒ
ââââââââââââââââââââââââ
â Boss Group â â ì°ê²° ìëœë§ ëŽë¹
â EventLoop Thread â
ââââââââââââ¬ââââââââââââ
â Channel ë±ë¡ (ì°ê²°ì Workerìê² ëê¹)
âŒ
ââââââââââââââââââââââââââââââââââââââââââââââââââââ
â Worker Group â
â âââââââââââââââ âââââââââââââââ ââââââââââââ â
â â EventLoop-1 â â EventLoop-2 â â ... â â
â â Channel A â â Channel B â â â â
â â Channel C â â Channel D â â â â
â âââââââââââââââ âââââââââââââââ ââââââââââââ â
ââââââââââââââââââââââââââââââââââââââââââââââââââââ
Channelì íëì íŽëŒìŽìžíž ì°ê²°ì ëíëŽë©°, í Channelì íì ê°ì EventLoop ì€ë ëì ê³ ì ëë€. ìŠ, í ìì²ì ìëª ì£Œêž° ëì ëŽë¹ ì€ë ëê° ë°ëì§ ìëë€. ìŽ ëë¶ì ì€ë ë ê° ëêž°í ììŽë ì€ë ë ìì ì±ìŽ ë³Žì¥ëë€.
ìì² ì²ëЬ íëŠ â Channel Pipeline
ì°ê²°ìŽ Worker EventLoopì ë°°ì ëê³ ë멎, ìì²ì Channel Pipelineì ë°ëŒ Handler륌 íëì© íµê³Œíë€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
íŽëŒìŽìžíž ìì²
â
âŒ
Worker EventLoop (Channel ê³ ì )
â
âŒ
âââââââââââââââââââââââââââ
â Channel Pipeline â
â âââââââââââââââââââââ â
â â Decoder â â HTTP ë°ìŽíž â ê°ì²Ž ë³í
â ââââââââââââââââââââ†â
â â Business Handler â â 컚ížë¡€ë¬ ë¡ì§ ì€í
â ââââââââââââââââââââ†â
â â Encoder â â ê°ì²Ž â HTTP ë°ìŽíž ë³í
â âââââââââââââââââââââ â
âââââââââââââââââââââââââââ
â
âŒ
íŽëŒìŽìžíž ìëµ
ê° Handlerë ë¹ëêž°ë¡ ëìíê³ , ì Handlerê° ëë멎 ë€ììŒë¡ ëìŽê°ë€.
WebFlux + Reactorì ì€ìŒì€ë¬
Reactorë ìŽë€ ì€ë ëìì ìœë륌 ì€íí ì§ ì ìŽíë ì€ìŒì€ë¬(Scheduler) 륌 ì ê³µíë€.
| ì€ìŒì€ë¬ | ì©ë | í¹ì§ |
|---|---|---|
Schedulers.parallel() | CPU ì°ì° | ìœìŽ ìë§íŒì ê³ ì ì€ë ë í |
Schedulers.boundedElastic() | ëžë¡í¹ I/O | íì ì ì€ë ë ìì±, ìµë ê°ì ì í ìì |
Schedulers.single() | ëšìŒ ì¬ì¬ì© ì€ë ë | ìì 볎ì¥ìŽ íìí ìì |
Netty EventLoop ì€ë ëìì ëžë¡í¹ ìœë륌 ì€íí멎, íŽë¹ ì€ë ëì ë¬¶ìž ëªšë Channel(ì°ê²°)ìŽ íšê» ë©ì¶ë€. ëžë¡í¹ ìì
ì ë°ëì boundedElastic()ìŒë¡ ë¶ëЬíŽìŒ íë€.
1
2
3
4
5
6
7
8
9
10
11
12
13
// â EventLoop ì€ë ëìì ëžë¡í¹ ížì¶ â ê°ì ì€ë ëì 몚ë ì°ê²°ìŽ ë©ì¶ë€
@GetMapping("/bad")
public Mono<String> bad() {
String result = blockingHttpCall(); // ëêž° HTTP ížì¶
return Mono.just(result);
}
// â
subscribeOnìŒë¡ ëžë¡í¹ ìì
ì ë³ë ì€ë ëë¡ ë¶ëЬ
@GetMapping("/good")
public Mono<String> good() {
return Mono.fromCallable(() -> blockingHttpCall())
.subscribeOn(Schedulers.boundedElastic());
}
publishOn vs subscribeOn
Reactorìì ì€í ì€ë ë륌 ë°êŸžë ë°©ë²ì ë ê°ì§ë€.
1
2
3
4
5
Flux.just(1, 2, 3)
.subscribeOn(Schedulers.boundedElastic()) // 구ë
ìì ë¶í° ìŽ ì€ë ë ì¬ì© (ìì€ë¶í° ì ì©)
.map(i -> i * 2) // boundedElastic ì€ë ëìì ì€í
.publishOn(Schedulers.parallel()) // ìŽ ì§ì ìŽíë¡ ì€ë ë ì í
.map(i -> i + 1); // parallel ì€ë ëìì ì€í
subscribeOn: ìì€ ë°ìŽí°ë¥Œ ë°ííë ì€ë ë륌 ì§ì . ì²Žìž ìŽëì ëë ìì€ë¶í° ì ì©ëë€.publishOn: ìŽ ì°ì°ì ìŽíì ìœëê° ì€íë ì€ë ë륌 ì§ì . ìì¹ê° ì€ìíë€.
1
2
3
4
5
6
7
8
9
// WebFlux Controller ìì
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable String id) {
return userRepository.findById(id) // Mono: 0 or 1ê°ì ë¹ëêž° ë°ìŽí°
.map(user -> {
// ë°ìŽí° ë³í (ë
Œëžë¡í¹)
return user;
});
}
í¹ì§
Mono<T>: 0~1ê°ì 결곌륌 ëŽë ë¹ëêž° 컚í ìŽëFlux<T>: 0~Nê°ì 결곌륌 ëŽë ë¹ëêž° ì€ížëŠŒ- 늬ì¡í°ëž íë¡ê·žëë° íšë¬ë€ì â íìµ ê³¡ì ìŽ ê°í륎ë€
- Ʞ졎 Spring MVC ìœëì íŒì©ìŽ ìŽë µë€ (ëžë¡í¹ ëŒìŽëžë¬ëЬ ì¬ì© ë¶ê°)
- Netty EventLoop ì€ë ëìì ëžë¡í¹ ìœë륌 ì€íí멎 ì ëë€ â WebFluxì ê°ì¥ ì€ìí ì ìœ
6. ìž íë ììí¬ íëì ë¹êµ
| í목 | NestJS | FastAPI | WebFlux |
|---|---|---|---|
| ìžìŽ | TypeScript/JS | Python | Java/Kotlin |
| Event Loop 구í | libuv (Node.js) | asyncio (Python) | Netty + Reactor |
| ì€ë ë ëªšëž | ì±êž ì€ë ë | ì±êž ì€ë ë | ë©í° ì€ë ë |
| ë¹ëêž° ë¬žë² | async/await | async/await | Mono/Flux |
| íìµ ê³¡ì | ë®ì | ë®ì | ëì |
| CPU ì§ìœ ìì | Worker Threads | ProcessPoolExecutor | ì€ë ë í |
| 죌ì ì¬ì©ì² | REST API, MSA | ML ìë¹, ë°ìŽí° API | ìí°íëŒìŽìŠ, ì€ížëŠ¬ë° |
7. Event Loop륌 ë§ê°ëšëЬë ìœë
Event Loopë ì±êž ì€ë ëë€. ìŠ, ì€ë 걞늬ë ëêž° ìì ìŽ ë€ìŽì€ë©Ž ê·žëì ë€ë¥ž 몚ë ìì²ìŽ ë©ì¶ë€.
CPU ì§ìœì ìì
1
2
3
4
5
6
7
8
9
10
11
12
// Node.js â â ìŽ íšìê° ì€íëë ëì ìë² ì ì²Žê° ë©ì¶ë€
app.get('/fibonacci', (req, res) => {
const result = fibonacci(45); // ììŽ ê±žëŠ¬ë ì¬ê· ì°ì°
res.json({ result });
});
// â
Worker Threadë¡ ë¶ëЬ
const { Worker } = require('worker_threads');
app.get('/fibonacci', (req, res) => {
const worker = new Worker('./fibonacci-worker.js', { workerData: 45 });
worker.on('message', (result) => res.json({ result }));
});
async륌 ë¶ìžë€ê³ ë¹ëêž°ê° ëì§ ìëë€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Python â â async defìŽì§ë§ ëŽë¶ê° ëžë¡í¹
import requests # ëêž° ëŒìŽëžë¬ëЬ!
async def get_data():
response = requests.get("https://api.example.com") # Event Loop ëžë¡í¹!
return response.json()
# â
ë¹ëêž° ëŒìŽëžë¬ëЬ ì¬ì©
import httpx
async def get_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com")
return response.json()
ê·ì¹: ë¹ëêž°ë¡ ë°ê¿ ì ìë ëžë¡í¹ ìœëë ë³ë ì€ë ëë íë¡ìžì€ íìì ì€ííŽìŒ íë€.
8. ì€ì ìŒìŽì€ â ìì ëì©ë Batch Insert
Event Loopì ì늬륌 ìŽíŽíë€ë©Ž, ì€ë¬Žìì ì죌 ë§ëë ìŒìŽì€ë¥Œ ë¶ìíŽë³Žì. âìì íìŒì ìœìŽ DBì 10ë§ ê±Žì Insertíë APIâë Event Loop êŽì ìì ꜀ ë³µì¡í ìì ìŽë€.
ìì ì 구조
1
2
3
4
5
ìì
ì
ë¡ë ìì (I/O)
â ìì
íì± (CPU) â â ïž ëžë¡í¹ ìí 구ê°
â ë°ìŽí° ê²ìŠ/ê°ê³µ (CPU) â â ïž ëžë¡í¹ ìí 구ê°
â DB INSERT à 10ë§ ê±Ž (I/O) â ë°©ìì ë°ëŒ ì²ì°šë§ë³
â ìëµ ë°í
íì±(CPU)곌 DB Insert(I/O)ê° ììž ìì ìŽëŒ, Event Loop ì ì¥ìì ì 겜 ìšìŒ í 구ê°ìŽ ë ê³³ìŽë€.
1구ê°: ìì íì± â Event Loop륌 ëžë¡í¹íë€
1
2
3
4
// Node.js â â xlsx íì±ì ëêž°(CPU) ìì
const workbook = XLSX.read(buffer); // Call Stackì ììŽê° ì ë ¹
const rows = workbook.Sheets['Sheet1'];
// ìŽ ëì ë€ë¥ž 몚ë ìì²ìŽ ë©ì¶ë€
íŽê²°ì± ì Worker Threadë¡ íì± ìì ì ë¶ëЬíë ê²ìŽë€.
1
2
3
4
5
6
// â
Worker Threadë¡ ë¶ëЬ
const worker = new Worker('./excel-parser.js', { workerData: buffer });
worker.on('message', (rows) => {
// íì± ìë£ í ìœë°±ìŒë¡ ëììŽ. Event Loopë ê·žëì ìì ë¡ë€.
handleRows(rows);
});
Python(FastAPI)ììë ëìŒí 묞ì ê° ìë€.
1
2
3
4
5
6
7
8
9
10
11
12
# â pandas íì±ì ëêž°(CPU) ìì
â Event Loop ëžë¡í¹
@app.post("/upload")
async def upload(file: UploadFile):
content = await file.read()
df = pd.read_excel(io.BytesIO(content)) # ëžë¡í¹!
# â
run_in_executorë¡ ë³ë ì€ë ëìì ì²ëЬ
@app.post("/upload")
async def upload(file: UploadFile):
content = await file.read()
loop = asyncio.get_event_loop()
df = await loop.run_in_executor(None, pd.read_excel, io.BytesIO(content))
2구ê°: DB Insert â ë°©ìì ë°ëŒ ì±ë¥ìŽ ê·¹ëšì ìŒë¡ ê°ëаë€
ê°ì 10ë§ ê±Ž InsertëŒë ìŽë»ê² ì²ëЬíëëì ë°ëŒ Event Loop ëììŽ ìì í ë¬ëŒì§ë€.
â ìµì : ê±Žë³ await (ì§ë ¬)
1
2
3
for (const row of rows) {
await db.insert(row); // 10ë§ ë² ììëë¡ DB ìë³µ ëêž°
}
1
2
3
4
Timeline:
[await insert 1]ââ[await insert 2]ââ[await insert 3]ââ ... Ã 100,000
ê° awaitë§ë€ Event Loopê° ë€ë¥ž ìì²ì ì²ëЬí ì ìì§ë§,
ìŽ API ìì² ì첎ë ìë¶ìŽ ê±žëŠ°ë€.
awaitê° ììŽì Event Loop륌 ëžë¡í¹íì§ ìì§ë§, DB ìë³µ ì§ì°ìŽ 10ë§ ë² ììžë€.
â ïž ì£Œì: Promise.all (묎ì í ë³ë ¬)
1
2
3
await Promise.all(rows.map(row => db.insert(row)));
// 10ë§ ê°ì Promise륌 ëìì ìì± â DB 컀ë¥ì
í ìŠì ê³ ê°
// ì€íë € ë ëë €ì§ê±°ë ìë¬ ë°ì
â ì€ë¬Ž ì ì: Chunk ëšì Bulk Insert
1
2
3
4
5
6
const CHUNK_SIZE = 1000;
for (let i = 0; i < rows.length; i += CHUNK_SIZE) {
const chunk = rows.slice(i, i + CHUNK_SIZE);
await db.bulkInsert(chunk); // 1000걎ì ëš í ë²ì DB 쿌늬ë¡
}
1
2
3
Timeline:
[bulk 1~1000]ââ[bulk 1001~2000]ââ ... Ã 100í
DB ìë³µìŽ 10ë§ ë² â 100ë²ìŒë¡ ì€ìŽë ë€
ëŽë¶ì ìŒë¡ ìŽë° 쿌늬 í ë²ìŒë¡ ì²ëЬëë€.
1
2
3
4
INSERT INTO users (name, email) VALUES
('Alice', 'a@a.com'),
('Bob', 'b@b.com'),
... (1000걎)
ì§ì§ ëì©ëìŽëŒë©Ž: Event Loop ë°ìŒë¡ 꺌ëŽëŒ
ììë§, ìë°±ë§ ê±Ž ìì€ìŽ ë멎 API ìë²ì Event Loop ììì ì²ëЬíë ê² ìì²Žê° ì못ë ì€ê³ë€. Message Queue륌 ì¬ì©íŽ ì²ëŠ¬ë¥Œ ìì í ë¶ëЬíë€.
1
2
3
4
5
6
7
Client
â
âŒ
API ìë² âââ Message Queue (Redis, RabbitMQ) âââ Worker íë¡ìžì€
â (íìŒ IDë§ ì ë¬) (ì€ì ì²ëЬ ëŽë¹)
â
ââââ 202 Accepted + jobId ìŠì ë°í
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// NestJS: APIë íì ìì
ì ë£ê³ ìŠì ë°í
@Post('/upload')
async uploadExcel(@UploadedFile() file) {
const fileId = await this.storage.save(file);
await this.queue.add('batch-insert', { fileId });
return { status: 'accepted', jobId: fileId }; // ìŠì ë°í
}
// ë³ë Worker: Event Loop ê±±ì ììŽ ì²ëЬ
@Process('batch-insert')
async handleBatchInsert(job: Job) {
const rows = await parseExcel(job.data.fileId);
for (let i = 0; i < rows.length; i += 1000) {
await db.bulkInsert(rows.slice(i, i + 1000));
}
}
ìŽë ê² í멎 API ìë²ì Event Loopë íì ê°ë³ê³ , ë¬Žê±°ìŽ ìì ì Workerê° ì ëŽíë€.
ì 늬
| êµ¬ê° | Event Loop ìí¥ | íŽê²°ì± |
|---|---|---|
| ìì íì± (CPU) | â ëžë¡í¹ | Worker Thread / run_in_executor |
| ê±Žë³ INSERT (ì§ë ¬) | ð¢ ë늬ì§ë§ ë¹ëžë¡í¹ | Chunk Bulk INSERTë¡ êµì²Ž |
| 묎ì í Promise.all | â 컀ë¥ì í ê³ ê° | Chunk ëšì ì²ëЬ |
| ìë°±ë§ ê±Ž ëì©ë | â ì€ê³ 묞ì | Message Queue + Worker ë¶ëЬ |
9. ìŽë€ ìí©ì ìŽë€ íë ììí¬?
1
2
3
I/O ì§ìœì + ë¹ ë¥ž ê°ë° + JS ìíê³ â NestJS
I/O ì§ìœì + ML/ë°ìŽí° + Python ìíê³ â FastAPI
ìí°íëŒìŽìŠ + ì€ížëŠ¬ë° + Java ìíê³ â WebFlux
ð¿ ë§ì¹ë©°
Event Loopë ì²ììë ì¶ìì ìž ê°ë ì²ëŒ ë껎ì§ì§ë§, ê²°êµ âêž°ë€ëŠ¬ì§ ë§ê³ , ëë멎 ìë €ì€â ëŒë ëšìí ììŽëìŽì 구íìŽë€.
NestJS, FastAPI, WebFluxë ê°ìì ë°©ììŒë¡ ìŽ ììŽëìŽë¥Œ ì€ííê³ ìë€. ìŽë€ íë ììí¬ë¥Œ ì ííë , Event Loopê° ìžì ëžë¡í¹ëëì§ë¥Œ ìŽíŽíê³ ìë€ë©Ž ì±ë¥ 묞ì ì ì ë°ì ìŽë¯ž íŽê²°í ì ìŽë€.
ê·žëŠ¬ê³ ë¬Žê±°ìŽ ìì ìììë âìŽë»ê² ë¹ëêž°ë¡ ì ì²ëЬí ê¹âë³Žë€ âìŽê±ž Event Loop ë°ìŒë¡ êºŒëŒ ì ììê¹â 륌 뚌ì ë ì¬ëЬë ê²ìŽ ì±ìí ì€ê³ì ìììŽë€.













