๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๊ฐœ๋ฐœ

Node.js์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„

๋…ธ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํšŒ์‚ฌ์— ๊ฐ€๊ฒŒ๋˜์–ด์„œ, ๋…ธ๋“œ๋ฅผ ๋ฐฐ์šฐ๊ณ  ์žˆ๋Š”๋ฐ...

 

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋‚˜ ๋…ธ๋“œ๋‚˜ ๋‹ค ์ฒ˜์Œ ๋ฐฐ์šฐ๊ณ  ์žˆ์–ด์„œ ์–ด๋ ค์šด ๊ฒƒ์ด ๋งŽ์€ ๊ฑฐ ๊ฐ™๋‹ค. ํŠนํžˆ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šด๊ฒŒ ์ด๋ฒคํŠธ ๋ฃจํ”„(Event Loop)์˜€๊ณ , ํ•ต์‹ฌ์ ์ธ ๋ถ€๋ถ„์ธ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ๋А๊ผˆ๋‹ค. ๋…ธ๋“œ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ด์•ผ๊ธฐ๊ฐ€ ์ด๋ฒคํŠธ ๋ฃจํ”„๋กœ ๋ชจ์ด๋Š”๋“ฏํ•œ ๋А๋‚Œ์„ ๋ฐ›์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ ์ด๊ฑธ ์ดํ•ดํ•œ๋‹ค๋ฉด ๋…ธ๋“œ์˜ ๊ตฌ์„ฑ ์š”์†Œ์™€ ๋™์ž‘ ๋ฐฉ์‹์€ ๋ฌผ๋ก ์ด๊ณ  ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚˜๋‰˜์–ด์ ธ์žˆ๋Š” ๋น„๋™๊ธฐ์ž‘์—…๋“ค์„ ์ž˜ ๊ตฌ๋ถ„ํ•˜๊ณ  ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ ์ด์œ ๋กœ ์ด๋ฒˆ ๊ธ€์—์„œ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๋…ธ๋“œ์™€ ๊ด€๋ จ๋œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ง€์‹๋“ค๋„ ํ•จ๊ป˜ ์ •๋ฆฌํ•˜๋ฉด์„œ ๊ณต๋ถ€ํ•ด๋ณผ ์ƒ๊ฐ์ด๋‹ค.

 

๊ณต๋ถ€๋ฅผ ํ•˜๋ฉด์„œ ๊ธ€๋„ ํ•จ๊ป˜ ์ž‘์„ฑํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ถ€์ •ํ™•ํ•œ ์ •๋ณด๊ฐ€ ์žˆ์„ ์ˆ˜๋„ ์žˆ๋‹ค! ๊ทธ๋Ÿฌํ•œ ๋ถ€๋ถ„๋“ค์€ ์‹ค๋ฌด๋ฅผ ๊ฒฝํ—˜ํ•˜๋ฉฐ ์ฐจ์ฐจ ์ˆ˜์ •ํ•ด๋‚˜๊ฐ€๋ณด๊ฒ ๋‹ค.

 

Node.js

๊ธฐ๋ณธ์ ์œผ๋กœ ๋…ธ๋“œ๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ๋กœ ๋™์ž‘ํ•œ๋‹ค. ๋”ฑ ํ•˜๋‚˜์˜ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ชจ๋“  ๊ฒƒ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ด๋‹ค. ์ด๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์–ธ์–ด ์ž์ฒด๊ฐ€ ๋‹จ์ผ ์Šค๋ ˆ๋“œ๋กœ ๋™์ž‘ํ•˜๋Š” ์–ธ์–ด์ด๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ, ํ•˜์ง€๋งŒ ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ƒ๊ฐํ•ด๋ณด๋ฉด ์˜์ƒ์„ ๋ณด๋ฉด์„œ ์—ฌ๋Ÿฌ ์›น ํŽ˜์ด์ง€๋ฅผ ์—ด์–ด ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋…ธ๋“œ๋กœ ์ด๋ฃจ์–ด์ง„ ์„œ๋ฒ„๋„ ๋™์‹œ์— ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ผ๋Š” ๊ฒƒ์ด ๋™์ž‘ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•˜์ง€๋งŒ, ์Šคํ”„๋ง์—์„œ์ฒ˜๋Ÿผ ์š”์ฒญ๋ถ€ํ„ฐ ์‘๋‹ต๊นŒ์ง€ ๋ชจ๋“  ๊ณผ์ •์„ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋…ธ๋“œ๋Š” I/O ์ž‘์—…์— ๋Œ€ํ•ด์„œ๋งŒ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋กœ ๋™์ž‘ํ•œ๋‹ค๋Š” ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค. ์ด๋Š” ๋…ธ๋“œ์˜ ์ฒ ํ•™์˜ ๊ทผ๊ฐ„์ด "I/O ์ž‘์—…์ด ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋А๋ฆฌ๊ณ , ์ž‘์—… ์™„๋ฃŒ๊นŒ์ง€ ํ”„๋กœ๊ทธ๋žจ์ด ๋Œ€๊ธฐํ•˜๋Š”๊ฑด ๋ฐ”๋ณด๊ฐ™์€ ์ผ์ด๋‹ค." ๋ผ๋Š” ๋ง์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. - Ryan Dahl: Node JS

 

๊ทธ๋ž˜์„œ ๋‚˜๋…ธ์ดˆ ๋‹จ์œ„๋กœ ๋๋‚˜๋Š” CPU ์ž‘์—…์— ๋Œ€ํ•ด์„œ๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ด์™ธ์˜ I/O ์ž‘์—…์€ ๋ชจ๋‘ libuv ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€๋กœ ์œ„์ž„ํ•œ๋‹ค. ์ด๋•Œ libuv๋Š” ์•„์ฃผ ์˜๋ฆฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”๋ฐ, ๋„คํŠธ์›Œํฌ ํ†ต์‹  ๊ฐ™์€ ์ž‘์—…์€ ์šด์˜์ฒด์ œ์˜ ์ปค๋„์˜ ๋น„๋™๊ธฐ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์Šค๋ ˆ๋“œ ๋‚ญ๋น„์—†์ด ์ฒ˜๋ฆฌํ•˜๊ณ , ํŒŒ์ผ ์‹œ์Šคํ…œ ์ ‘๊ทผ์ด๋‚˜ ๋ฌด๊ฑฐ์šด ์•”ํ˜ธํ™”/์••์ถ• ์ž‘์—… ๋“ฑ๊ณผ ๊ฐ™์ด OS๊ฐ€ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šด ์ž‘์—…๋งŒ ๋‚ด๋ถ€์˜ ์Šค๋ ˆ๋“œ ํ’€์— ์žˆ๋Š” ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—๊ฒŒ ๋งก๊ฒจ์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ฐจ๋‹จ๋˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”๋‹ค.

https://www.vskills.in/certification/tutorial/node-js-architecture-2/

 

๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. Worker Threads ๋ถ€๋ถ„์„ ์ œ์™ธํ•˜๋ฉด ๋ชจ๋“  ์˜์—ญ์ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ํ•˜๋‚˜๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ์…ˆ์ด๋‹ค. ๊ทผ๋ฐ ์—ฌ๊ธฐ์„œ ์˜๋ฌธ์ด ๋“ค์—ˆ๋˜๊ฑด ๋ณดํ†ต ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜์— ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋ฅผ ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ํ•˜๋Š”๋ฐ, ๋…ธ๋“œ๋Š” ์–ด๋–ป๊ฒŒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ํ•˜๋‚˜๋กœ V8 ์—”์ง„, Node API๋ฅผ ํ†ตํ•œ V8 <-> libuv ๋ฐ”์ธ๋”ฉ, libuv ์‹คํ–‰๊นŒ์ง€ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€๊ฐ€ ๊ถ๊ธˆํ–ˆ๋‹ค.

 

์ด๊ฑด ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์œผ๋กœ ํ•ด์†Œํ–ˆ๋‹ค. ๋…ธ๋“œ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ v8 ์—”์ง„์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ์‹คํ–‰์ด ๋๋‚˜๋ฉด ์ด๋ฒคํŠธ ๋ฃจํ”„ ์ˆ˜ํ–‰์„ ํ˜ธ์ถœํ•˜๊ณ  libuv๋กœ ์ œ์–ด๊ถŒ์ด ๋„˜์–ด๊ฐ„๋‹ค. ๊ทธ๋Ÿผ ์ œ์–ด๊ถŒ์„ ๊ฐ€์ง„ libuv๊ฐ€ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ์ž‘์—…๋“ค์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ฐ ํŽ˜์ด์ฆˆ์— ์ง„์ž…ํ•  ๋•Œ ํ•ด๋‹น ํŽ˜์ด์ฆˆ์˜ ํ์—์„œ ์ฝœ๋ฐฑ์„ ๊บผ๋‚ด V8์—๊ฒŒ ์‹คํ–‰์„ ์œ„์ž„ํ•œ๋‹ค.

 

์ด๋ฅผ node ๋‚ด ์ฝ”๋“œ๋กœ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆœ์„œ๋กœ ์ด์–ด์ง„๋‹ค. ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๋ฉด์„œ, AI์˜ ๋„์›€์„ ๋ฐ›์•„ ํ•ต์‹ฌ ํ˜ธ์ถœ ํ๋ฆ„์„ ์ •๋ฆฌํ•ด ๋ณด์•˜์œผ๋ฉฐ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๊ณต์‹ node github์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

main()                                          # node_main.cc
โ””→ node::Start()                                # node.cc
    โ””→ StartInternal()
        โ”œ→ InitializeOncePerProcessInternal()   # V8, libuv ์ดˆ๊ธฐํ™”
        โ”œ→ NodeMainInstance ์ƒ์„ฑ                 # V8 + libuv ์—ฐ๊ฒฐ
        โ””→ NodeMainInstance::Run()              # node_main_instance.cc

NodeMainInstance::Run()
โ”œ→ LoadEnvironment()                            # app.js ์‹คํ–‰ ๋‹ด๋‹น
โ”‚   โ””→ StartExecution(env, cb)
โ”‚       โ””→ StartExecution(env, "internal/main/run_main_module")
โ”‚           โ””→ ExecuteBootstrapper("internal/main/run_main_module")
โ”‚               โ””→ CompileAndCall()
โ”‚                   โ”œ→ LoadBuiltinSource()      # JS ์†Œ์Šค์ฝ”๋“œ ์ฝ๊ธฐ
โ”‚                   โ”œ→ LookupAndCompile()       # V8์œผ๋กœ ์ปดํŒŒ์ผ (JS → ๋ฐ”์ดํŠธ์ฝ”๋“œ)
โ”‚                   โ””→ fn->Call()               # ← ์‹ค์ œ JS ์‹คํ–‰ (app.js)
โ”‚                       โ””→ process, require ๋“ฑ ์ฃผ์ž…
โ”‚
โ””→ SpinEventLoopInternal()                     # ์ด๋ฒคํŠธ ๋ฃจํ”„ ์‹œ์ž‘
    โ””→ uv_run()                                # libuv/core.c
        โ””→ while(hasPendingTasks) {
               uv__run_timers()                # Phase 1: Timers
               uv__run_pending()              # Phase 2: Pending Callbacks
               uv__io_poll()                  # Phase 3: Poll
               uv__run_check()                # Phase 4: Check (setImmediate)
               uv__run_closing_handles()      # Phase 5: Close Callbacks
           }

 

๊ทธ๋ž˜์„œ ์ •๋ฆฌํ•˜๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋‚˜ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ์ž‘์„ฑ๋œ ์„œ๋ฒ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ๋…ธ๋“œ๋Š” ์„œ๋ฒ„ ๊ตฌ์„ฑ์„ ์œ„ํ•ด ํ•„์š”ํ•œ ์ฝ”๋“œ๋“ค์„ ์‹คํ–‰ํ•˜๊ณ  ์ œ์–ด๊ถŒ์„ libuv์— ๋„˜๊ฒจ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•œ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ๋„ ๊ฒฐ๊ตญ ๋„คํŠธ์›Œํฌ I/O ์ž‘์—…์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ๋ฃจํ”„์—์„œ ์ด๋ฅผ ์บ์น˜ํ•ด v8 ์—”์ง„์—๊ฒŒ ์ˆ˜ํ–‰์„ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ์ฆ‰, node๊ฐ€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ์ด์œ ์— ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ํ•ต์‹ฌ์ ์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ I/O๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹คํ•ด๋„ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ํ•˜๋‚˜๋งŒ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์€ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋ฅผ ์ด์šฉํ•˜๋Š” ์ž๋ฐ”/์Šคํ”„๋ง๋ณด๋‹ค ์š”์ฒญ ์ฒ˜๋ฆฌ์— ๋น„ํšจ์œจ์ ์ผ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ์˜๊ฒฌ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Ryan Dahl: Node JS ์˜์ƒ์—์„œ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋Š”๋ฐ, ์Šค๋ ˆ๋“œ์˜ ์‹คํ–‰์ด ์ž‘์—… ์™„๋ฃŒ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋Š” ์ƒํ™ฉ์ด ๋งˆ์Œ์— ๋“ค์ง€ ์•Š์•˜๊ณ  ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ๋น„์šฉ๊ณผ ์Šค๋ ˆ๋“œ๋กœ ์ธํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋น„์šฉ์ด ๋น„์‹ธ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค๊ณ  ํ•œ๋‹ค. ๋˜ํ•œ, ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ์˜ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ์ง€์ ํ–ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ Ryan Dahl์€ ์›น ์„œ๋ฒ„์ธ Apache์™€ Nginx๋ฅผ ๋น„๊ตํ–ˆ๋Š”๋ฐ, ์˜์ƒ ์† ์ž๋ฃŒ์—์„œ ์ปค๋„ฅ์…˜๋งˆ๋‹ค ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” Apache ๋ฐฉ์‹๋ณด๋‹ค Nginx์˜ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ์ด ๋” ์ ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ๊ฐ™์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธ์‹œ์ผœ์คฌ๋‹ค. ๋˜ํ•œ, ๋ญ”๊ฐ€ ๊ทธ๋ž˜ํ”„๋ฅผ ํ•˜๋‚˜ ๋” ๋ณด์—ฌ์คฌ๋Š”๋ฐ ์†”์งํžˆ ๋งํ•˜๋ฉด ์˜์ƒ์˜ ํ™”์งˆ ๋•Œ๋ฌธ์— ์ดํ•ดํ•˜์ง„ ๋ชปํ–ˆ๋‹ค.

https://stackoverflow.com/questions/5598495/about-how-much-of-a-memory-footprint-reduction-does-nginx-give-over-apache2

 

๊ทธ๋ž˜์„œ Node.js์˜ ์š”์ฒญ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์œผ๋กœ Nginx์˜ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜์— ์ฑ„ํƒํ–ˆ๋‹ค. Nginx์˜ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜๋Š” Node.js์™€ ์œ ์‚ฌํ•˜๋‹ค. ๋งˆ์Šคํ„ฐ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์„ค์ • ํŒŒ์ผ์ด๋‚˜ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๊ณ  ๊ทธ ์•„๋ž˜ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋“ค์ด ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ๋…ผ๋ธ”๋กœํ‚น I/O ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค. ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ํ•˜๋‚˜๊ฐ€ ์ •ํ™•ํžˆ Node.js์˜ ๋™์ž‘ ๋ฐฉ๋ฒ•์ด๋‹ค.

https://natnat1.medium.com/nginx-architecture-c9c93058e8a0

 

Nginx๊ฐ€ ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ์œผ๋กœ Apache๋ฅผ ์••๋„ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, Node.js๋„ ๊ฐ™์€ ์›๋ฆฌ๋กœ ๋™์‹œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ด ์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ์ •ํ™•ํžˆ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฑธ๊นŒ?

 

์ด๋ฒคํŠธ ๋ฃจํ”„

์•ž์„œ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ๊ต‰์žฅํžˆ ์ค‘์š”ํ•œ ๊ตฌ์„ฑ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ž„์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์ •ํ™•ํžˆ ๋ฌด์—‡์ด๊ณ , ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€๋„ ๊ณต๋ถ€ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

 

์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์ž‘์—…๋“ค์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌํ˜„์ฒด์ด๋‹ค. ๋น„๋™๊ธฐ ์ž‘์—… ์ข…๋ฅ˜์— ๋”ฐ๋ผ ์™„๋ฃŒ๋œ ์ฝœ๋ฐฑ์ด ์—ฌ๋Ÿฌ ํŽ˜์ด์ฆˆ์— ๋‚˜๋ˆ  ์ €์žฅ๋˜๊ณ , ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ํ•ด๋‹น ํŽ˜์ด์ฆˆ์— ์ง„์ž…ํ•  ๋•Œ ์‹คํ–‰๋œ๋‹ค. ์ €์žฅํ•œ๋‹ค๋Š” ๋ง์€ ๊ฐ ํŽ˜์ด์ฆˆ๊ฐ€ ์ž์‹ ๋งŒ์˜ ํ๋ฅผ ํ•˜๋‚˜์”ฉ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๋œป์ด๋‹ค.

https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick

 

์œ„ ๊ทธ๋ฆผ์—์„œ ๊ฐ ๋ฐ•์Šค๊ฐ€ ์•ž์„œ ๋งํ•œ 'ํŽ˜์ด์ฆˆ(phase)'์ด๊ณ , ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ํŠน์ • ํŽ˜์ด์ฆˆ์— ์ง„์ž…ํ•˜๋ฉด ํ•ด๋‹น ํŽ˜์ด์ฆˆ์˜ ํ์—์„œ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•˜์—ฌ ํ๊ฐ€ ์†Œ์ง„๋˜๊ฑฐ๋‚˜ ์ตœ๋Œ€ ์ฝœ๋ฐฑ ์ˆ˜๊ฐ€ ์‹คํ–‰๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณตํ•œ๋‹ค. ๊ทธ ํ›„, ํ๊ฐ€ ์†Œ์ง„๋˜๊ฑฐ๋‚˜ ์ฝœ๋ฐฑ ํ•œ๋„์— ๋„๋‹ฌํ•˜๋ฉด ์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ์ด๋™ํ•˜๋ฉด์„œ ์ง„ํ–‰๋˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ํ•œ ๋ฐ”ํ€ด ๋Œ์•˜๋‹ค๋ฉด, ๋‹ค์‹œ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ๋Œ€๊ธฐ ์ค‘์ธ ๋น„๋™๊ธฐ I/O๋‚˜ ํƒ€์ด๋จธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. ์•„๋ฌด๊ฒƒ๋„ ๋‚จ์•„์žˆ์ง€ ์•Š๋‹ค๋ฉด ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ข…๋ฃŒ๋˜๊ณ , ๋‚จ์•„์žˆ๋‹ค๋ฉด ๋‹ค์‹œ pending callbacks ํŽ˜์ด์ฆˆ๋ถ€ํ„ฐ ์ˆœํšŒ๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค. ์‹ค์ œ๋กœ ๋…ธ๋“œ ์ฝ”๋“œ#core.c๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŽ˜์ด์ฆˆ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  // ์ตœ์ดˆ ํƒ€์ด๋จธ ์‹คํ–‰
  if (mode == UV_RUN_DEFAULT && r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    uv__run_timers(loop);
  }
  
  // ์ด๋ฒคํŠธ ๋ฃจํ”„ ํŽ˜์ด์ฆˆ ์ˆœํšŒ
  while (r != 0 && loop->stop_flag == 0) {
    uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);

    uv__io_poll(loop, timeout);

    uv__run_check(loop);
    uv__run_closing_handles(loop);

    uv__update_time(loop);
    uv__run_timers(loop);

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets gcc compile it to a conditional store. Avoids
   * dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

  return r;
}

 

๋ณดํ†ต ์ด๋ฒคํŠธ ๋ฃจํ”„์— ๋Œ€ํ•ด์„œ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์„ ํ•ด๋ณด๋ฉด, timers ํŽ˜์ด์ฆˆ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๊ฒƒ์œผ๋กœ ์„ค๋ช…ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ์ด๋Š” ์œ„ ์ฝ”๋“œ์—์„œ ๋ณด๋“ฏ์ด ์ตœ์ดˆ ํƒ€์ด๋จธ ํŽ˜์ด์ฆˆ๋ฅผ ๋จผ์ € ์ˆ˜ํ–‰ํ•˜๊ณ  ๋ฃจํ”„์— ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ํŽธ์˜์ƒ timers ํŽ˜์ด์ฆˆ๋ฅผ ๊ฐ€์žฅ ์ƒ๋‹จ์— ๋‘๋Š” ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ์ •ํ™•ํžˆ๋Š” ์ง„์ž… ์ „ timers ํŽ˜์ด์ฆˆ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋ฃจํ”„์— ๋“ค์–ด๊ฐ„๋‹ค.

 

์ด์ œ ๊ฐ ํŽ˜์ด์ฆˆ์—์„œ ์–ด๋–ค ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ด€ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.

 

timers phase

ํ•ด๋‹น ํŽ˜์ด์ฆˆ๋Š” setTimeout()๊ณผ setInterval()์— ์˜ํ•ด ์˜ˆ์•ฝ๋œ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค. ๋ณดํ†ต ํƒ€์ด๋จธ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์›ํ•˜๋Š” ๋”œ๋ ˆ์ด ์‹œ๊ฐ„์„ ์ฃผ์–ด ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค.

const timeoutScheduled = Date.now();

setTimeout(() => {
	const delay = Date.now() - timeoutScheduled;
	
	console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

 

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ๋”œ๋ ˆ์ด ์‹œ๊ฐ„์€ ์ •ํ™•ํ•œ ๊ทธ ์‹œ๊ฐ„์ด ์•„๋‹ˆ๋ผ ์ œ๊ณต๋œ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ž„๊ณ„๊ฐ’์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ž„๊ณ„๊ฐ’์€ ์ •ํ™•ํ•œ ์‹คํ–‰ ์‹œ๊ฐ„์ด ์•„๋‹ˆ๋ผ ์‹คํ–‰์„ ์œ„ํ•œ ์ตœ์†Œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, 100ms๋กœ ์„ค์ •ํ•œ ์œ„ ์ฝ”๋“œ๋Š” '์ ์–ด๋„ 100ms๋Š” ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ์‹คํ–‰ํ•ด๋ผ'๋ผ๋Š” ์˜๋ฏธ๊ฐ€ ๋œ๋‹ค.

 

๊ทธ๋ž˜์„œ ์ด๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ์‹คํ–‰ํ•ด๋ณด๋ฉด ํ•ญ์ƒ ๊ฐ™์€ ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์‹คํ–‰๋˜์ง€๋Š” ์•Š๋Š”๋‹ค. ๊ทธ๋ž˜๋„ ์ง€์ •๋œ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๊ฐ€๋Šฅํ•œ ํ•œ ๋นจ๋ฆฌ ์‹คํ–‰๋˜๋„๋ก ํ•˜์ง€๋งŒ, ์šด์˜์ฒด์ œ ์Šค์ผ€์ค„๋ง์ด๋‚˜ ๋‹ค๋ฅธ ์ฝœ๋ฐฑ ์‹คํ–‰์œผ๋กœ ์ธํ•ด ์ง€์—ฐ๋  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 1์ดˆ์˜ ์ž„๊ณ„๊ฐ’์„ ๊ฐ€์ง„ ํƒ€์ž„์•„์›ƒ์„ ์˜ˆ์•ฝํ•ด๋‘๊ณ , ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ•์ œ๋กœ 3์ดˆ๊ฐ„ ๋ฐ”์˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ๋ณด์ž.

const start = Date.now();

// 1์ดˆ(1000ms) ๋’ค์— ์‹คํ–‰ํ•˜๋ผ๊ณ  ์˜ˆ์•ฝ!
setTimeout(() => {
    console.log(`ํƒ€์ด๋จธ ์‹คํ–‰๋จ! ์ง€์—ฐ ์‹œ๊ฐ„: ${Date.now() - start}ms`);
}, 1000);

// ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ•์ œ๋กœ 3์ดˆ ๋™์•ˆ ๋ฐ”์˜๊ฒŒ ๋งŒ๋“ฆ
const blockUntil = Date.now() + 3000;
while (Date.now() < blockUntil) {
    // 3์ดˆ ๋™์•ˆ V8 ์—”์ง„์€ ์ด ๋ฃจํ”„์— ๊ฐ‡ํ˜€์„œ ๋‹ค๋ฅธ ์ผ์„ ๋ชป ํ•จ
}

console.log("๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ž‘์—… ๋!");

 

 

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

 

์ข€ ๋” ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ธฐ์œ„ํ•ด ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ๋ถ„์„ํ•ด๋ณด์ž.

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
	// ...
	uv__update_time(loop);
	uv__run_timers(loop);
	// ...
}

 

์•„๊นŒ ์ด๋ฒคํŠธ ๋ฃจํ”„ ์ฝ”๋“œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, timers ํŽ˜์ด์ฆˆ๋Š” ํ•ญ์ƒ `uv__update_time`์ด๋ผ๋Š” ๋ฉ”์„œ๋“œ์™€ ํ•จ๊ป˜ ์ˆ˜ํ–‰๋œ๋‹ค. `uv__update_time`์€ ์ด๋ฒคํŠธ ๋ฃจํ”„์˜ ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋กœ timers ํŽ˜์ด์ฆˆ๊ฐ€ ๋งŒ๋ฃŒ๋œ ์‹œ๊ฐ„์„ ๊ฒ€์ฆํ•˜๋Š”๋ฐ ๊ธฐ์ค€์ด ๋˜๋Š” ์‹œ๊ฐ„์œผ๋กœ ํ™œ์šฉํ•œ๋‹ค.

 

`uv__run_timers` ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€๋ฅผ ํ™•์ธํ•˜๋ฉด,

void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;
  struct uv__queue* queue_node;
  struct uv__queue ready_queue;

  uv__queue_init(&ready_queue);

  for (;;) {
    heap_node = heap_min(timer_heap(loop));
    if (heap_node == NULL)
      break;

    handle = container_of(heap_node, uv_timer_t, node.heap);
    if (handle->timeout > loop->time)
      break;

    uv_timer_stop(handle);
    uv__queue_insert_tail(&ready_queue, &handle->node.queue);
  }

  while (!uv__queue_empty(&ready_queue)) {
    // ... ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์‹คํ–‰
    handle->timer_cb(handle);
  }
}

 

๋จผ์ € heap_min์„ ํ†ตํ•ด ํž™์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊บผ๋‚ด์˜ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” timers ํŽ˜์ด์ฆˆ๊ฐ€ ์‚ฌ์‹ค์€ ํ๊ฐ€ ์•„๋‹ˆ๋ผ ์ตœ์†Œ ํž™(min heap) ์„ ํ†ตํ•ด ํƒ€์ž„์•„์›ƒ ๊ด€๋ จ ์ •๋ณด๋ฅผ ๋ณด๊ด€ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด ์ •๋ณด๋“ค์—๋Š” ๋งŒ๋ฃŒ ์‹œ๊ฐ„๊ณผ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ๋Œ€ํ‘œ์ ์œผ๋กœ ํฌํ•จ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์ตœ์†Œ ํž™์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์ตœ์†Œ ํž™์— ์ €์žฅํ–ˆ๊ธฐ์— ํž™์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊บผ๋ƒˆ์„ ๋•Œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„๋งŒ์œผ๋กœ ์ข…๋ฃŒ ์กฐ๊ฑด์„ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.

int uv_timer_stop(uv_timer_t* handle) {
  if (uv__is_active(handle)) {
    heap_remove(timer_heap(handle->loop),
                (struct heap_node*) &handle->node.heap,
                timer_less_than);
    uv__handle_stop(handle);
  } else {
    uv__queue_remove(&handle->node.queue);
  }

  // ...
  return 0;
}

 

๋งŒ์•ฝ ์ตœ์†Œ ํž™์—์„œ ๊บผ๋‚ธ ๋ฐ์ดํ„ฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ์•„์ง ๋„๋‹ฌํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์ดํ›„ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋“ค๋„ ์•„์ง ๋„๋‹ฌํ•˜์ง€ ์•Š์•˜์„ํ…Œ๋‹ˆ ํƒ€์ด๋จธ ํŽ˜์ด์ฆˆ๋ฅผ ์ข…๋ฃŒํ•œ๋‹ค. ํ•˜์ง€๋งŒ, ๋งŒ๋ฃŒ ์‹œ๊ฐ„์— ๋„๋‹ฌํ–ˆ๋‹ค๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ํž™์—์„œ ์ œ๊ฑฐํ•˜๊ณ  ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ๊ทธ ํ›„ ๋‹ค์‹œ ์ตœ์†Œ ํž™์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊บผ๋‚ด ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ์ ์ ˆํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, ํž™์ด ๋นŒ ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ timers ํŽ˜์ด์ฆˆ๋„ ์ฝœ๋ฐฑ ํ•œ๋„์— ์˜ํ–ฅ์„ ๋ฐ›์œผ๋ฏ€๋กœ ๋‹ค์Œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ์ง€๋‚ฌ๋‹คํ•ด๋„ ๋‹ค์Œ ๋ฃจํ”„๊นŒ์ง€ ์ง€์—ฐ๋  ์ˆ˜ ์žˆ๋‹ค.

 

์ •๋ฆฌํ•˜๋ฉด,

  • ๋…ธ๋“œ์—์„œ ํƒ€์ด๋จธ๋Š” ์ž„๊ณ„๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋ฉฐ, ์ •ํ™•ํ•œ ์‹œ๊ฐ„์„ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ํƒ€์ด๋จธ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ํ๊ฐ€ ์•„๋‹Œ ์ตœ์†Œ ํž™์— ์ €์žฅํ•˜์—ฌ ๊ด€๋ฆฌํ•œ๋‹ค.

 

pending callbacks

์ด ํŽ˜์ด์ฆˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์˜ ์ผ๋ฐ˜์ ์ธ I/O ์ฝœ๋ฐฑ๋ณด๋‹ค๋Š” ์‹œ์Šคํ…œ ๋ ˆ๋ฒจ์˜ ๋™์ž‘ ์™„๋ฃŒ ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜๋Š” ๊ณณ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, TCP ์†Œ์ผ“์ด ์—ฐ๊ฒฐ์„ ์‹œ๋„ํ•˜๋‹ค๊ฐ€ ECONNREFUSED๋ฅผ ๋ฐ›์•˜์„ ๋•Œ, ์ผ๋ถ€ ๋ฆฌ๋ˆ…์Šค ์‹œ์Šคํ…œ์—์„œ๋Š” ์ด ์—๋Ÿฌ๋ฅผ ์ฆ‰์‹œ ๋ณด๊ณ ํ•˜์ง€ ์•Š๊ณ  ์—ฐ๊ธฐ(pending)ํ–ˆ๋‹ค๊ฐ€ ์ด ํŽ˜์ด์ฆˆ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 

์ผ๋ฐ˜์ ์ธ ์›น ์„œ๋ฒ„ ๋กœ์ง์„ ์ž‘์„ฑํ•  ๋•Œ๋Š” ์ง์ ‘ ๋งˆ์ฃผ์น  ์ผ์ด ๊ฑฐ์˜ ์—†๋Š” ๋‚ด๋ถ€์ ์ธ ํ์ด๋ฏ€๋กœ, "์šด์˜์ฒด์ œ ์‹œ์Šคํ…œ ๋ ˆ๋ฒจ์˜ ์—๋Ÿฌ๋‚˜ ์—ฐ๊ธฐ๋œ ์ฝœ๋ฐฑ๋“ค์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‹จ๊ณ„" ์ •๋„๋กœ ์•Œ๊ณ ์žˆ์œผ๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

 

idle, prepare phase

Node์˜ ๋‚ด๋ถ€์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์—๋„ ๋”ฑํžˆ ์„ค๋ช…์ด ์—†๋‹ค.

 

poll phase

poll phase์—์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ๋‘ ๊ฐ€์ง€ ์ž„๋ฌด๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  1. ๊ณ„์‚ฐ: ์ƒˆ๋กœ์šด I/O ์ด๋ฒคํŠธ๋ฅผ ์œ„ํ•ด ์šด์˜์ฒด์ œ ์ปค๋„์—๊ฒŒ ์–ผ๋งŒํผ ๋Œ€๊ธฐ๋ฅผ ์š”์ฒญํ• (Timeout) ๊ฒƒ์ธ๊ฐ€?
  2. ์‹คํ–‰: poll ํ์— ์žˆ๋Š” ์ด๋ฒคํŠธ ์‹คํ–‰

 

๊ณ„์‚ฐ

์ด๋Š” poll phase์˜ ํ๊ฐ€ ๋น„์–ด์žˆ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. ๋น„์–ด์žˆ์œผ๋ฉด ํ•  ์ผ์ด ์—†๋‹ค๋Š” ๋œป์ด๋ฏ€๋กœ ๋‘ ๊ฐ€์ง€๋กœ ๋ถ„๊ธฐํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, setImmediate()๋กœ ์˜ˆ์•ฝ๋œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋” ์ด์ƒ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  poll phase๋ฅผ ์ข…๋ฃŒ ํ›„ ๋‹ค์Œ ๋‹จ๊ณ„์ธ check phase๋กœ ๋„˜์–ด๊ฐ„๋‹ค. ํ•˜์ง€๋งŒ, ์˜ˆ์•ฝ๋œ ์ฝ”๋“œ๊ฐ€ ์—†๋‹ค๋ฉด ์ƒˆ๋กœ์šด I/O ์ด๋ฒคํŠธ๊ฐ€ ์˜ฌ ๋•Œ๊นŒ์ง€ ๋ฌดํ•œํžˆ ๋Œ€๊ธฐํ•œ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๊ณ  ๋งˆ๋ƒฅ ๋ฌด์ž‘์ • ๋Œ€๊ธฐํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์˜ˆ์•ฝ๋œ ํƒ€์ด๋จธ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ๋˜์—ˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ poll ํ๊ฐ€ ๋น„์–ด์„œ ๋Œ€๊ธฐํ•ด์•ผ ํ•  ๋•Œ, timers phase์˜ ์ตœ์†Œ ํž™์„ ๋จผ์ € ํ™•์ธํ•˜๊ณ  ํƒ€์ด๋จธ๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ wait timeout์„ ์„ค์ •ํ•œ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด poll phase๋Š” ์šด์˜์ฒด์ œ ์ปค๋„์—๊ฒŒ ์ƒˆ๋กœ์šด I/O ์š”์ฒญ์ด ์˜ค๊ฑฐ๋‚˜, ์„ค์ •ํ•œ ํƒ€์ž„์•„์›ƒ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๊นจ์›Œ๋‹ฌ๋ผ๊ณ  ์œ„์ž„ํ•œ๋‹ค. ๋งŒ์•ฝ ์—ฌ๊ธฐ์„œ ํƒ€์ด๋จธ๊ฐ€ ๋งŒ๋ฃŒ๋˜๋ฉด ์ปค๋„์€ ๋‹ค์‹œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๊นจ์šฐ๊ณ , poll phase ์ข…๋ฃŒ ํ›„ ๋‚˜๋จธ์ง€ phase๋ฅผ ๋งˆ์ € ์ˆ˜ํ–‰ํ•œ ํ›„์— timers phase์—์„œ ํƒ€์ด๋จธ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  // ...

  while (r != 0 && loop->stop_flag == 0) {
    // ...

    timeout = 0;
    if ((mode == UV_RUN_ONCE && can_sleep) || mode == UV_RUN_DEFAULT)
      timeout = uv__backend_timeout(loop);

	// ...

    uv__io_poll(loop, timeout);
    
    // ...
  }
}
static int uv__backend_timeout(const uv_loop_t* loop) {
  if (loop->stop_flag == 0 // ๋ฃจํ”„ ์ข…๋ฃŒ ์š”์ฒญ ์—†์Œ 
	  && (uv__has_active_handles(loop) || uv__has_active_reqs(loop)) // ์ฒ˜๋ฆฌํ•  ์ž‘์—…์ด ๋‚จ์•„์žˆ์Œ 
	  && uv__queue_empty(&loop->pending_queue) // pending ํ๊ฐ€ ๋น„์–ด์žˆ์Œ 
	  && uv__queue_empty(&loop->idle_handles) // idle ํ•ธ๋“ค ์—†์Œ 
	  && (loop->flags & UV_LOOP_REAP_CHILDREN) == 0 // ์ข…๋ฃŒ๋œ ์ž์‹ ํ”„๋กœ์„ธ์Šค ์—†์Œ 
	  && loop->closing_handles == NULL // ๋‹ซ์„ ํ•ธ๋“ค ์—†์Œ
	  )
    return uv__next_timeout(loop);
  return 0;
}
int uv__next_timeout(const uv_loop_t* loop) {
  const struct heap_node* heap_node;
  const uv_timer_t* handle;
  uint64_t diff;

  heap_node = heap_min(timer_heap(loop));
  if (heap_node == NULL)
    return -1; /* block indefinitely */

  handle = container_of(heap_node, uv_timer_t, node.heap);
  if (handle->timeout <= loop->time)
    return 0;

  diff = handle->timeout - loop->time;
  if (diff > INT_MAX)
    diff = INT_MAX;

  return (int) diff;
}

 

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, ์—ฌ๋Ÿฌ ์กฐ๊ฑด์„ ๊ฒ€์‚ฌํ•˜๊ณ  ํƒ€์ด๋จธ์˜ ์ตœ์†Œ ํž™์„ ๊ธฐ์ค€์œผ๋กœ timeout ๊ฐ’์„ ์„ค์ •ํ•œ ํ›„์— poll phase๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

์‹คํ–‰

์‹คํ–‰์€ ๋‹จ์ˆœํžˆ ํ๊ฐ€ ๋น„์–ด์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ์ด๋ฏ€๋กœ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ํ์— ์Œ“์ธ ์ž‘์—…๋“ค์„ ์ˆœ์„œ๋Œ€๋กœ ๊บผ๋‚ด ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ด๋•Œ ์ฒ˜๋ฆฌ๋˜๋Š” ์ž‘์—…๋“ค์—๋Š” ํ”ํžˆ ์•„๋Š” 'HTTP ์š”์ฒญ ์ ‘์ˆ˜', 'DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜', 'ํŒŒ์ผ ์ฝ๊ธฐ ์™„๋ฃŒ' ๋“ฑ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๋น„๋™๊ธฐ I/O ๊ฒฐ๊ณผ๋ฌผ๋“ค์ด๋‹ค.

 

๊ทธ๋Ÿฌํ•œ ๋น„๋™๊ธฐ I/O ์ค‘ ํŒŒ์ผ I/O์ฒ˜๋Ÿผ OS๊ฐ€ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šด ์ž‘์—…์€ worker thread(libuv Thread Pool)๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ณ , ์™„๋ฃŒ๋˜๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค.

 

Linux ๊ธฐ์ค€์œผ๋กœ ์ข€ ๋” ์‚ดํŽด๋ณด๊ธฐ

  • ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ธด ํ–ˆ์œผ๋‚˜, ๋ถ€์ •ํ™•ํ•œ ์ •๋ณด์ผ ์ˆ˜ ์žˆ์Œ.
  • ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ •๋ฆฌํ•œ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
[ํŒŒ์ผ I/O ํ๋ฆ„]

1. JS ์ฝ”๋“œ ์‹คํ–‰
   fs.readFile('file.txt', callback)
       โ””→ uv__work_submit()
            โ””→ ๊ธ€๋กœ๋ฒŒ wq์— uv__work ์‚ฝ์ž…
            โ””→ uv_cond_signal() → idle worker thread ๊นจ์›€
            # deps/uv/src/threadpool.c > uv__work_submit()

2. worker thread
   while (uv__queue_empty(&wq)) uv_cond_wait()  // ๊ธ€๋กœ๋ฒŒ wq ๋Œ€๊ธฐ
       โ””→ q = uv__queue_head(&wq)               // ์ž‘์—… ๊บผ๋ƒ„
       โ””→ w = uv__queue_data(q, struct uv__work, wq)
       โ””→ w->work(w)                            // ํŒŒ์ผ ์ฝ๊ธฐ ์‹คํ–‰ (blocking)
       โ””→ uv__queue_insert_tail(&w->loop->wq, &w->wq)  // loop->wq์— ๊ฒฐ๊ณผ ์‚ฝ์ž…
       โ””→ uv_async_send(&w->loop->wq_async)     // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ์‹ ํ˜ธ
       # deps/uv/src/threadpool.c > worker()

3. uv_async_send()
       โ””→ uv__async_send()
            โ””→ Linux:  write(eventfd, &val, 8)  // eventfd์— write
            โ””→ ๊ทธ ์™ธ:  write(async_wfd, "", 1)  // pipe์— write
                โ””→ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ epoll_wait ๊นจ์–ด๋‚จ
       # deps/uv/src/unix/async.c > uv_async_send(), uv__async_send()

4. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ
   wq_async ํ•ธ๋“ค๋Ÿฌ ํ˜ธ์ถœ
       โ””→ uv__queue_move(&loop->wq, &wq)        // loop->wq๋ฅผ ๋กœ์ปฌ๋กœ ์ด๋™
       โ””→ while (!uv__queue_empty(&wq))
            โ””→ w->done(w, err)                  // JS ์ฝœ๋ฐฑ ์‹คํ–‰
       # deps/uv/src/threadpool.c > uv__work_done()


[๋„คํŠธ์›Œํฌ I/O ํ๋ฆ„]

1. JS ์ฝ”๋“œ ์‹คํ–‰
   http.createServer().listen(3000)
       โ””→ watcher_queue์— FD + ์ฝœ๋ฐฑ ๋“ฑ๋ก
       # deps/uv/src/unix/core.c

2. poll phase ์ง„์ž…
       โ””→ watcher_queue ์ˆœํšŒ
            โ””→ epoll_ctl(epfd, EPOLL_CTL_ADD, fd, ...)  // ์ปค๋„์— FD ๊ฐ์‹œ ๋“ฑ๋ก
       โ””→ epoll_wait(epfd, events, timeout)             // ๋Œ€๊ธฐ
       # deps/uv/src/unix/linux/epoll.c > uv__io_poll()

3. ์ปค๋„: FD ์ด๋ฒคํŠธ ๊ฐ์ง€
       โ””→ epoll_wait ๋ฐ˜ํ™˜
       โ””→ pe->fd๋กœ loop->watchers[fd] ์ฐพ๊ธฐ
       โ””→ w->cb(loop, w, pe->events)                   // JS ์ฝœ๋ฐฑ ์‹คํ–‰
       # deps/uv/src/unix/linux/epoll.c > uv__io_poll()


[์ดˆ๊ธฐํ™”]

heap_init(&loop->timer_heap)         // timers ์ตœ์†Œ ํž™
uv__queue_init(&loop->wq)            // ํŒŒ์ผ I/O ์™„๋ฃŒ ํ
uv__queue_init(&loop->watcher_queue) // ๋„คํŠธ์›Œํฌ I/O ๊ฐ์‹œ ํ
uv__queue_init(&loop->pending_queue) // pending callbacks ํ
uv_async_init(loop, &loop->wq_async, uv__work_done)  // worker ์™„๋ฃŒ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
# deps/uv/src/unix/core.c > uv_loop_init()

 

์—ฌ๊ธฐ์„œ poll phase๋Š” wq(work queue)์™€ watcher_queue๋ฅผ ํ™œ์šฉํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ ๋„คํŠธ์›Œํฌ๋ฅผ ํƒ€๋А๋ƒ ํƒ€์ง€ ์•Š๋А๋ƒ์— ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ๋˜๋Š” ํ์™€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ๋‹ค๋ฅด๋‹ค. wq์˜ ๊ฒฝ์šฐ์—๋Š” worker thread(libuv Thread Pool)๋ฅผ ์ด์šฉํ•œ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ด๊ธฐ๋Š” ํ์ด๋‹ค. ๊ทธ๋ž˜์„œ ํŒŒ์ผ ์ฝ๊ธฐ์ฒ˜๋Ÿผ ์†Œ์ผ“์˜ ๊ฐ์‹œ(epoll_ctl)๋กœ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๋Š” ์ž‘์—…์˜ ๊ฒฝ์šฐ์— worker thread๊ฐ€ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ํ•ด๋‹น ํ์— ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์‚ฝ์ž…ํ•œ๋‹ค.

 

watcher_queue๋Š” ์†Œ์ผ“์„ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ๋ฅผ ํƒ€๊ณ  '์–ด๋–ค ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ(File Descriptor)์˜ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์‹œํ•ด์•ผ ํ•˜๋Š”์ง€'์— ๋Œ€ํ•œ ๋“ฑ๋ก ์š”์ฒญ์ด ๋‹ด๊ธด๋‹ค. poll phase ์ง„์ž… ์‹œ ์ด ํ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์ปค๋„์— ๊ฐ์‹œ ๋“ฑ๋ก(epoll_ctl)์„ ์š”์ฒญํ•˜๊ณ , ์ดํ›„ epoll_wait์œผ๋กœ ๋Œ€๊ธฐํ•˜๋‹ค๊ฐ€ ์ปค๋„์ด ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜๋ฉด ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค.

 

์‹ค์ œ๋กœ ํ๊ฐ€ ๋น„์–ด์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ๋ž€ wq์— ์žˆ๋Š” ์ž‘์—…๊ณผ watcher_queue๋กœ ๊ฐ์‹œ๋ฅผ ์š”์ฒญํ•œ epoll์ด ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ–ˆ์„ ๋•Œ๋ฅผ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋„คํŠธ์›Œํฌ I/O์™€ watcher_queue

watcher_queue์—๋Š” ์†Œ์ผ“์„ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ๋ฅผ ํƒ€๊ณ  '์–ด๋–ค ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ(File Descriptor)์˜ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์‹œํ•ด์•ผ ํ•˜๋Š”์ง€'์— ๋Œ€ํ•œ ๋“ฑ๋ก ์š”์ฒญ์ด ์ž„์‹œ๋กœ ๋‹ด๊ธด๋‹ค.

 

๊ฐ„๋‹จํ•˜๊ฒŒ ์˜ˆ๋ฅผ ๋“ค์–ด DB์— ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋น„๋™๊ธฐ ์š”์ฒญ์„ ๋ณด๋ƒˆ๋‹ค๊ณ  ํ•˜์ž. ๊ทธ๋Ÿฌ๋ฉด ํ•ด๋‹น DB์™€ ํ†ต์‹ ์„ ๋‹ด๋‹นํ•˜๋Š” ๋„คํŠธ์›Œํฌ ์†Œ์ผ“(ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ)์˜ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์ด์™€ ๊ด€๋ จ๋œ ์ •๋ณด๋“ค์ด watcher_queue์— ๋“ฑ๋ก๋œ๋‹ค.

 

๊ทธ ํ›„ poll phase์— ์ง„์ž…ํ•˜์—ฌ poll ํ๊ฐ€ ๋น„์–ด์žˆ์„ ๋•Œ, ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ๋Œ€๊ธฐ ์ง์ „์— watcher_queue์— ๋‹ด๊ธด ์ •๋ณด๋“ค์„ ๊บผ๋‚ด ์šด์˜์ฒด์ œ์—๊ฒŒ ํ•ด๋‹น ์†Œ์ผ“๋“ค์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€(epoll_ctl)ํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•œ๋‹ค. ์ดํ›„ ์•ž์„œ ๊ณ„์‚ฐ ๋‹จ๊ณ„์—์„œ ์„ค์ •ํ•œ timeout๋งŒํผ ๋Œ€๊ธฐํ•˜๋‹ค๊ฐ€ ์ปค๋„์ด DB๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ–ˆ๋‹ค๋Š” ์‹ ํ˜ธ๋ฅผ ์ฃผ๋ฉด, ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ํ•ด๋‹น ์ด๋ฒคํŠธ์™€ ๋งคํ•‘๋œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ V8 ์—”์ง„์œผ๋กœ ๊ณง์žฅ ์‹คํ–‰ํ•˜๋Š” ํ๋ฆ„์ด๋‹ค.

 

ํŒŒ์ผ I/O์™€ wq(work queue)

๋ฐ˜๋ฉด์— ํŒŒ์ผ ์ฝ๊ธฐ์™€ ๊ฐ™์ด ์šด์˜์ฒด์ œ์˜ ์†Œ์ผ“์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ์–ด๋ ค์šด ์ž‘์—…์€ worker thread(libuv Thread Pool)๋ฅผ ์ด์šฉํ•œ๋‹ค. ๋‹ค์Œ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž.

const fs = require('fs');

fs.readFile('any-file.txt', () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);

  setImmediate(() => {
    console.log('immediate');
  });
});
// ํ•ด๋‹น ์ฝ”๋“œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋Š”
/*
immediate
timeout
*/


/*
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

ํ•ด๋‹น ์ฝ”๋“œ์˜ ์‹คํ–‰๊ฒฐ๊ณผ๋Š”
timeout
immediate

immediate
timeout

๋‘˜ ์ค‘ ์–ด๋А ๊ฒƒ์ด ๋‚˜์˜ฌ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†๋‹ค.
setTimeout(..., 0);์€ ์‚ฌ์‹ค 0ms๊ฐ€ ์•„๋‹ˆ๋ผ ๋…ธ๋“œ์—์„œ ์ •ํ•œ ์ตœ์†Œ๊ฐ’์ธ 1ms๋กœ ์„ค์ •๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
๊ทธ๋ž˜์„œ ๋…ธ๋“œ๊ฐ€ ์‹œ์ž‘๋˜๋ฉด์„œ ๋‘ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์ด๋ฒคํŠธ ๋ฃจํ”„์— ๋“ค์–ด๊ฐ„๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ€์žฅ ์ฒ˜์Œ timers phase๋ฅผ ๊ฒ€์‚ฌํ–ˆ์„ ๋•Œ 1ms๊ฐ€ ์ง€๋‚˜์ง€ ์•Š์€ ์ƒํ™ฉ์ด๋ฉด ๋น ๋ฅด๊ฒŒ ๋‹ค์Œ phase๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณ check phase์— ๋„๋‹ฌํ•˜์—ฌ immediate๊ฐ€ ๋จผ์ € ์ถœ๋ ฅ๋  ์ˆ˜๋„ ์žˆ๊ณ ,
ํ˜น์€ ์ง€์—ฐ์œผ๋กœ ์ธํ•ด timers phase์—์„œ 1ms๊ฐ€ ์ง€๋‚œ ์ƒํ™ฉ์ด๋ผ timeout์ด ๋จผ์ € ์ถœ๋ ฅ๋  ์ˆ˜๋„ ์žˆ๋‹ค.
*/

 

ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด fs.readFile ๊ตฌ๋ฌธ์„ ์ฝ๊ณ  worker thread(libuv Thread Pool)์—๊ฒŒ ํ•ด๋‹น ์ž‘์—…์„ ์œ„์ž„ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  poll phase์— ์ง„์ž…ํ•˜๋ฉด ํŒŒ์ผ ์ฝ๊ธฐ๊ฐ€ ๋๋‚˜๋Š” ๋™์•ˆ ๋Œ€๊ธฐ ์ƒํƒœ์— ๋“ค์–ด๊ฐ„๋‹ค. (๋งŒ์•ฝ ์˜ˆ์•ฝ๋œ ํƒ€์ด๋จธ๊ฐ€ ์žˆ์—ˆ๋‹ค๋ฉด ํƒ€์ด๋จธ ๋งŒ๋ฃŒ ์‹œ๊ฐ„๊นŒ์ง€๋งŒ ๋Œ€๊ธฐํ•œ๋‹ค.)

 

์ด๋•Œ ๋…ธ๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„์™€ worker thread๋ผ๋ฆฌ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ํŒŒ์ดํ”„(async_wfd)๋ฅผ ์—ด์–ด๋‘๊ณ  ๊ฐ์‹œํ•˜๊ณ  ์žˆ๋‹ค. ์‹œ๊ฐ„์ด ์ง€๋‚˜๊ณ  worker thread์—์„œ ํ•ด๋‹น ์ž‘์—…์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ž‘์—… ๊ฒฐ๊ณผ์™€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ wq์— ์‚ฝ์ž…ํ•˜๊ณ  ๋‚ด๋ถ€ ํŒŒ์ดํ”„๋ฅผ ํ†ตํ•ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—๊ฒŒ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ธ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋น„๋กœ์†Œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ํ•ด๋‹น ํ(wq)์—์„œ ์ž‘์—…์„ ๊บผ๋‚ด ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ๋ฆ„์ด๋‹ค.

uv__queue_insert_tail(&w->loop->wq, &w->wq);
uv_async_send(&w->loop->wq_async);

 

์ด๋ ‡๊ฒŒ ๋‹ค๋ฅธ ํ๋กœ ๋™์ž‘ํ•˜๋Š” ์ด์œ ๋Š” ๋ฆฌ๋ˆ…์Šค ์ž์ฒด์—์„œ ํŒŒ์ผ์— ๋Œ€ํ•œ epoll์„ ์ง€์›ํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ wq๋ผ๋Š” ํ๋ฅผ ํ†ตํ•ด ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ํ•˜์ง€๋งŒ, ์œˆ๋„์šฐ์˜ ๊ฒฝ์šฐ์—๋Š” ๋ฆฌ๋ˆ…์Šค์ฒ˜๋Ÿผ watcher_queue, wq ๊ฐ™์€ ๊ฐœ๋…์ด ์—†๋‹ค. ์ด๋Š” IOCP(I/O completion Port)๋ผ๋Š” ๋น„๋™๊ธฐ I/O ์ปค๋„ ๋ชจ๋ธ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ƒฅ ๊ฐ„๋‹จํžˆ ์šด์˜์ฒด์ œ ์ž์ฒด์—์„œ ๋‹ค ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ํ๋ฆ„์ด๋‹ค. ๋Œ€๋ถ€๋ถ„ ๋ฆฌ๋ˆ…์Šค, ๋งฅ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐœ๋ฐœ, ์šด์˜ํ•˜๊ฒŒ ๋˜๋‹ˆ ์œˆ๋„์šฐ๋Š” ๋”ฐ๋กœ ์ฝ”๋“œ๋ฅผ ํŒŒ๋ณด๊ฑฐ๋‚˜ ํ•˜์ง„ ์•Š์•˜๋‹ค.

 

์ •๋ฆฌํ•˜๋ฉด,

  • ํ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ๊บผ๋‚ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ํ๊ฐ€ ๋น„์–ด์žˆ์„ ๋•Œ๋Š”
    • watcher_queue์— ์Œ“์—ฌ์žˆ๋˜ ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ๋ฅผ ๊บผ๋‚ด ์ปค๋„์— ๊ฐ์‹œ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.
    • ๋‹ค์Œ phase์—์„œ ์ฒ˜๋ฆฌํ•  ์ž‘์—…์ด ์—†๋‹ค๋ฉด, timer phase์˜ ํƒ€์ด๋จธ ๊ธฐ์ค€์œผ๋กœ timeout์„ ์„ค์ •ํ•˜์—ฌ ๋Œ€๊ธฐ๋ฅผ ์š”์ฒญํ•œ๋‹ค.
    • ๋‹ค์Œ phase์—์„œ ์ฒ˜๋ฆฌํ•  ์ž‘์—…์ด ์žˆ๋‹ค๋ฉด ๋ฐ”๋กœ ๋‹ค์Œ phase๋กœ ๋„˜์–ด๊ฐ„๋‹ค.
    • ๋‘˜ ๋‹ค ์—†๋‹ค๋ฉด ์ƒˆ๋กœ์šด I/O ์ด๋ฒคํŠธ๊ฐ€ ๋“ค์–ด์˜ฌ ๋•Œ๊นŒ์ง€ ๋ฌดํ•œ์ • ๋Œ€๊ธฐํ•œ๋‹ค.

 

check phase

ํ•ด๋‹น ํŽ˜์ด์ฆˆ์—์„œ๋Š” ๋‹ค๋ฅธ ์ž‘์—…์€ ์ผ์ ˆ ์—†์ด, ์˜ค์ง setImmediate()๋กœ ๋“ฑ๋กํ•œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋“ค๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์ „์šฉ ๊ณต๊ฐ„์ด๋‹ค. ๋”ฑํžˆ ์„ค๋ช…ํ•  ๋‚ด์šฉ์ด ์—†๋‹ค.

 

close callbacks phase

socket.on('close', cb)์™€ ๊ฐ™์ด close, destory ์ด๋ฒคํŠธ ํƒ€์ž… ์ฝœ๋ฐฑ ์ฒ˜๋ฆฌ

 

์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ ์‚ฌ์‹ค์ƒ ์ด๋ฒคํŠธ ๋ฃจํ”„ ํŽ˜์ด์ฆˆ์˜ ๋์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฒคํŠธ ๋ฃจํ”„์˜ ์ผ๋ถ€๋Š” ์•„๋‹ˆ์ง€๋งŒ ๋…ธ๋“œ์˜ ๋ฃจํ”„ ๊ณผ์ •์—์„œ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋Š” ์นœ๊ตฌ๊ฐ€ ์žˆ๋Š”๋ฐ ๋ฐ”๋กœ nextTickQueue, microTaskQueue์ด๋‹ค.

 

nextTickQueue, microTaskQueue

process.nextTick() ์˜ ์ฝœ๋ฐฑ๋“ค์ด ์ €์žฅ๋˜๋Š” nextTickQueue์™€ Promise, queueMicrotask() ๋“ฑ์˜ ์ฝœ๋ฐฑ์ด ์ €์žฅ๋˜๋Š” microTaskQueue๋Š” ์ด๋ฒคํŠธ ๋ฃจํ”„์˜ ์ผ๋ถ€๋Š” ์•„๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋ฒคํŠธ ๋ฃจํ”„์˜ ํŽ˜์ด์ฆˆ์—์„œ ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์ž‘์—…(์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ)์ด ๋๋‚œ ์งํ›„์— ๊ฐ€์žฅ ์šฐ์„ ์ ์œผ๋กœ ํ™•์ธ๋˜๊ณ  ์‹คํ–‰๋˜๋Š” ํ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ๋‘ ํ๋Š” libuv๊ฐ€ ์•„๋‹ˆ๋ผ Node.js์— ํฌํ•จ๋˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. ๋‘ ๊ฐœ์˜ ํ ๋ชจ๋‘ ์ž‘์—… ํ›„ ์‹คํ–‰๋œ๋‹ค๋Š” ํŠน์ง•์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ๋‘˜ ์‚ฌ์ด์—๋„ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์กด์žฌํ•œ๋‹ค. 1์ˆœ์œ„๋Š” nextTickQueue์ด๊ณ  2์ˆœ์œ„๋Š” microTaskQueue์ด๋‹ค.

 

์ข€ ๋”, ์ž์„ธํ•˜๊ฒŒ ๋งํ•˜์ž๋ฉด ํŽ˜์ด์ฆˆ์—์„œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๋‘ ํ์— ๋“ค์–ด์žˆ๋Š” ์ž‘์—…์ด ์‹คํ–‰๋˜๊ณ  ์›๋ž˜ ํŽ˜์ด์ฆˆ์˜ ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

ํ•ด๋‹น ๊ทธ๋ฆผ์œผ๋กœ ์š”์•ฝ์ด ๊ฐ€๋Šฅํ•  ๊ฑฐ ๊ฐ™๋‹ค. Node.js ๋‚ด๋ถ€์ธ C/C++์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝœ๋ฐฑ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๊ทธ ์ฝœ๋ฐฑ์ด ๋๋‚˜์„œ ๋‹ค์‹œ Node.js์ธ C/C++๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒฝ๊ณ„์—์„œ nextTick์„ ํ™•์ธํ•˜๊ณ  ๋‘ ํ๊ฐ€ ๋ชจ๋‘ ๋น„์–ด์งˆ๋•Œ๊นŒ์ง€ ๋‚จ์€ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค.

 

node v11 ์ด์ „์—๋Š” ํŽ˜์ด์ฆˆ ์‚ฌ์ด์— ์žˆ๋Š” ์ € 'ํ‹ฑ(Tick)' ์ฆ‰, ๊ฐ ํŽ˜์ด์ฆˆ๋ฅผ ๋„˜์–ด๊ฐ€๋Š” ๊ทธ ์‚ฌ์ด์— nextTick์„ ํ™•์ธํ•˜๊ณ  ๋‘ ํ๊ฐ€ ๋นŒ๋•Œ๊นŒ์ง€ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ–ˆ์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €์™€์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด node v11๋ถ€ํ„ฐ๋Š” ์ฝœ๋ฐฑ ์‹คํ–‰ ์งํ›„๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค. - New Changes to the Timers and Microtasks in Node v11.0.0 ( and above), MacroTask and MicroTask execution order

 

์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ nextTickQueue, microTaskQueue ๋ชจ๋‘ ๋นŒ ๋•Œ๊นŒ์ง€ ์ˆ˜ํ–‰์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์žฌ๊ท€์ ์ธ process.nextTick() ํ˜ธ์ถœ๋กœ ์ธํ•ด I/O ๊ธฐ์•„์ƒํƒœ์— ๋น ์งˆ ์ˆ˜ ์žˆ์–ด poll phase๋กœ ๋„๋‹ฌํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์™œ ํ•„์š”ํ•œ๊ฑธ๊นŒ?

๊ณต์‹ ๋ฌธ์„œ์—์„œ ํ•ด๋‹น ๊ธฐ๋Šฅ์ด Node.js์— ํฌํ•จ๋˜์–ด์•ผ ํ•˜๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์—ˆ๋‹ค. ์ด๋Š” ์ผ๋ถ€ API๊ฐ€ ๋ฐ˜๋“œ์‹œ ๋น„๋™๊ธฐ์ ์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ์„ค๊ณ„ ์ฒ ํ•™ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํ•œ๋‹ค. ์ด๊ฒŒ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ๋ฅผ ์ดˆ๋ž˜ํ•˜๋Š” ์ƒํ™ฉ์œผ๋กœ ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ œ์‹œํ–ˆ๋‹ค.

let bar = null;

// this has an asynchronous signature, but calls callback synchronously
function someAsyncApiCall(callback) {
  callback();
}

// the callback is called before `someAsyncApiCall` completes.
someAsyncApiCall(() => {
  // since someAsyncApiCall hasn't completed, bar hasn't been assigned any value
  console.log('bar', bar); // null
});

bar = 1;

 

ํ•ด๋‹น ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ someAsyncApiCall() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๋น„๋™๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๋™๊ธฐ์ ์œผ๋กœ ํ˜ธ์ถœ๋œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด someAsyncApiCall()๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๊ตฌ๋ฌธ์—์„œ ์ฝœ๋ฐฑ์„ ๋“ฑ๋กํ•˜์ง€๋งŒ, ๋‚ด๋ถ€์—์„œ ๊ทธ๋Œ€๋กœ callback()์„ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝœ์Šคํƒ ์ž์ฒด๊ฐ€ someAsyncApiCall() - callback() ์ˆœ์œผ๋กœ ๋“ฑ๋ก๋˜๊ณ  ํ˜ธ์ถœ๋œ๋‹ค. ๊ทธ๋ž˜์„œ bar = 1; ์ฝ”๋“œ์— ์ ‘๊ทผํ•˜๊ธฐ ์ „์— ์ด๋ฏธ bar๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ์ฝ˜์†”์— ์ถœ๋ ฅํ•˜๊ธฐ์— ์˜ˆ์ธกํ•˜์ง€ ๋ชปํ•œ ์˜ˆ์™ธ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.

 

์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

let bar = null;

function someAsyncApiCall(callback) {
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

bar = 1;

 

process.nextTick()์— ์ฝœ๋ฐฑ์„ ๋„ฃ์–ด, ํ•ด๋‹น ์ž๋ฐ” ์Šคํฌ๋ฆฝํŠธ์˜ ์‹คํ–‰์ด ๋ชจ๋‘ ๋๋‚œ ํ›„์— callback์ด ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๊ณ , ์ถœ๋ ฅ ๊ฒฐ๊ณผ ๋˜ํ•œ bar = 1; ์ดํ›„์ด๊ธฐ์— 1์ด ์ถœ๋ ฅ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ทผ๋ฐ ์†”์งํžˆ ์ด ์˜ˆ์ œ๋งŒ์œผ๋กœ๋Š” ์™œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์› ๋‹ค. ๋”๊ตฐ๋‹ค๋‚˜ ๋…ธ๋“œ๋กœ ๊ฐœ๋ฐœ์„ ํ•ด๋ณด์ง€๋„ ์•Š์•„์„œ ๋” ๊ทธ๋žฌ๋˜ ๊ฑฐ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ AI๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ•ด๋‹น ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ์‹ค๋ฌด์ ์ธ ์ด์œ ์— ๋Œ€ํ•ด์„œ ๋ฌผ์–ด๋ดค๋‹ค. ๊ทธ๋Ÿฌ์ž 2๊ฐœ์˜ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๊ธฐ๋Šฅ์˜ ์กด์žฌ ์ด์œ ์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•ด์ฃผ์—ˆ๋‹ค.

 

์บ์‹œ ์‚ฌ์šฉ์— ๋Œ€ํ•œ ๋ฌธ์ œ

์œ ์ €์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” getUser()๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

function getUser(id, callback) {
  if (cache[id]) {
    callback(cache[id]);
  } else {
    fetchFromDB(id, callback);
  }
}

 

์ฒ˜์Œ ์š”์ฒญ์—๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋‹ˆ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค. ํ•˜์ง€๋งŒ ํ•œ ๋ฒˆ ๊ฐ€์ ธ์˜จ ์œ ์ € ์ •๋ณด์— ๋Œ€ํ•ด์„œ๋Š” ๋ฉ”๋ชจ๋ฆฌ(์บ์‹œ)์— ์ €์žฅํ•ด๋‘๊ณ , ๋‹ค์Œ๋ฒˆ ์š”์ฒญ ๋•Œ๋Š” DB ์ ‘๊ทผ์—†์ด ์บ์‹œ์—์„œ ๋™๊ธฐ์ ์œผ๋กœ ์ฆ‰์‹œ ๊บผ๋‚ธ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ ๋‚˜์„œ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด isLoading์ด๋ผ๋Š” ์ƒํƒœ ๋ณ€์ˆ˜๋กœ ์ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

let isLoading = true; // 1. ๋กœ๋”ฉ ์‹œ์ž‘

getUser(1, (data) => {
  console.log('๋ฐ์ดํ„ฐ ๋„์ฐฉ:', data);
  isLoading = false; // 3. ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋ฉด ๋กœ๋”ฉ ๋!
});

// 2. (๊ฐœ๋ฐœ์ž์˜ ์˜๋„: ํ•จ์ˆ˜ ํ˜ธ์ถœ ์งํ›„์— ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ํ™•์‹คํžˆ ์ผœ๋‘ )
isLoading = true;

 

์ฒซ ๋ฒˆ์งธ ํ˜ธ์ถœ ์‹œ์—๋Š” DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋ฏ€๋กœ ์ฝ”๋“œ๋Š” ์•„๋ž˜๋กœ ์ญ‰ ๋‚ด๋ ค๊ฐ€์„œ isLoading = true; ๊ตฌ๋ฌธ์ด ๋จผ์ € ์‹คํ–‰๋œ๋‹ค. ๋‚˜์ค‘์— DB์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋ฉด ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜์–ด isLoading = false; ๊ตฌ๋ฌธ์ด ์‹คํ–‰๋˜๋ฉด์„œ ๋กœ๋”ฉ ๋๋‚˜๋ฉฐ ์˜ˆ์ƒ๋Œ€๋กœ ํ˜๋Ÿฌ๊ฐ„๋‹ค.

 

ํ•˜์ง€๋งŒ ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ๋ถ€ํ„ฐ๋Š” ์กฐ๊ธˆ ์ด์ƒํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”๋ฐ, ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ๋ถ€ํ„ฐ๋Š” ์บ์‹œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜๋‹ˆ ๊ณง๋ฐ”๋กœ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๊ทธ๋ž˜์„œ isLoading = false; ๊ตฌ๋ฌธ์ด ์‹คํ–‰๋˜๋ฉด์„œ ๋กœ๋”ฉ์ด ๋๋‚œ๋‹ค. ํ•˜์ง€๋งŒ ๊ณง๋ฐ”๋กœ getUser() ๋ฉ”์„œ๋“œ ์ดํ›„ isLoading = true;๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ ๋‹ค์‹œ ๋กœ๋”ฉ ์ƒํƒœ๋กœ ๋“ค์–ด๊ฐ€๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค.

 

๊ทธ๋ž˜์„œ ์œ„์˜ ์ฝ”๋“œ์—์„œ ์บ์‹œ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋”๋ผ๋„ ์ฝœ๋ฐฑ์˜ ์‹คํ–‰์„ ๋’ค๋กœ ๋ฏธ๋ฃจ์–ด์•ผ ํ•œ๋‹ค. ์ด๋•Œ process.nextTick()์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

function getUser(id, callback) {
  if (cache[id]) {
    process.nextTick(() => callback(cache[id]));
  } else {
    fetchFromDB(id, callback);
  }
}

 

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ฌธ์ œ

๋‘ ๋ฒˆ์งธ ์˜ˆ์ œ๋Š” 8080 ํฌํŠธ๋กœ ์„œ๋ฒ„๋ฅผ ์—ด๊ณ (listen), ์„œ๋ฒ„๊ฐ€ ์ค€๋น„๋˜์—ˆ๋‹ค๋Š” ์ด๋ฒคํŠธ(listening)๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

const server = net.createServer(() => {}).listen(8080);

server.on('listening', () => {});

 

listen(8080)์„ ํ˜ธ์ถœํ•˜๋ฉด Node.js ๋‚ด๋ถ€์ ์œผ๋กœ ํฌํŠธ ๋ฐ”์ธ๋”ฉ ์ž‘์—…์ด ์ฆ‰์‹œ, ๋™๊ธฐ์ ์œผ๋กœ ์™„๋ฃŒ๋œ๋‹ค. ๋งŒ์•ฝ ํฌํŠธ๊ฐ€ ๋ฐ”์ธ๋”ฉ๋˜์ž๋งˆ์ž ์„œ๋ฒ„๊ฐ€ ์ค€๋น„๋˜์—ˆ๋‹ค๋ผ๋Š” 'listening' ์ด๋ฒคํŠธ๋ฅผ ๊ทธ ์ฆ‰์‹œ(๋™๊ธฐ์ ์œผ๋กœ) ํ„ฐ๋œจ๋ ค ๋ฒ„๋ฆฐ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

 

์ปดํ“จํ„ฐ๋Š” ์•„์ง ๋‹ค์Œ ์ค„์ธ server.on(...) ์ฝ”๋“œ๋ฅผ ์ฝ๊ธฐ๋„ ์ „์ด๋‹ค. ์ฆ‰, ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ์•„์ง ์žฅ์ฐฉ๋˜์ง€๋„ ์•Š์•˜๋Š”๋ฐ ์ด๋ฒคํŠธ๊ฐ€ ๋จผ์ € ๋ฐœ์‚ฌ๋˜์–ด ํ—ˆ๊ณต์œผ๋กœ ์ฆ๋ฐœํ•ด ๋ฌด์‹œ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Node.js ๊ฐœ๋ฐœ์ž๋“ค์€ ๋‚ด๋ถ€์ ์œผ๋กœ ํฌํŠธ ๋ฐ”์ธ๋”ฉ์€ ์ฆ‰์‹œ ์ˆ˜ํ–‰ํ•˜๋˜, "์ด๋ฒคํŠธ๋ฅผ ํ„ฐ๋œจ๋ฆฌ๋Š” ์ž‘์—…(emit)"๋งŒ process.nextTick()์œผ๋กœ ๊ฐ์‹ธ์„œ ๋’ค๋กœ ๋ฏธ๋ค„๋‘์—ˆ๋‹ค. ๋•๋ถ„์— ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋๊นŒ์ง€ ๋ชจ๋‘ ์‹คํ–‰๋˜์–ด ์ˆ˜์‹ ๊ธฐ(.on)๊ฐ€ ๋ฌด์‚ฌํžˆ ์žฅ์ฐฉ๋  ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์„ ๋ฒŒ์–ด์ฃผ๋ฉฐ, ์ดํ›„์— ์•ˆ์ „ํ•˜๊ฒŒ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์žฅํ•ด ์ค€๋‹ค.

 

์ด์™ธ์—๋„ ๋งŽ์€ ์˜ˆ์ œ๊ฐ€ ์žˆ๊ฒ ์ง€๋งŒ, ๋…ธ๋“œ๋กœ ์ง์ ‘ ๊ฐœ๋ฐœ๊นŒ์ง€ ํ•ด๋ณธ ์ƒํƒœ๋Š” ์•„๋‹ˆ๋ผ์„œ ๋‹ค๋ฅธ ์˜ˆ์ œ๋ฅผ ๋‹ด๊ธฐ์—” ์–ด๋ ค์› ๋‹ค. ์‹ค๋ฌด ๊ฒฝํ—˜์ด ์Œ“์ด๋ฉด ์ด ๋˜ํ•œ ์ถ”๊ฐ€ํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๋…ธ๋“œ ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š” ์›ฌ๋งŒํ•˜๋ฉด ๋ชจ๋“  ์ƒํ™ฉ์—์„œ setImmediate()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. ๊ทธ๊ฒŒ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ดํ•ดํ•˜๊ธฐ ๋” ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํ•œ๋‹ค. ๋˜ํ•œ, ์žฌ๋ฐŒ๋Š”๊ฑด process.nextTick()๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์ดํ›„ ๊ณง์žฅ ์‹คํ–‰๋˜๊ณ , setImmediate()๋Š” check phase์— ์ด๋ฅด๋Ÿฌ์„œ์•ผ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋‘˜์˜ ์ด๋ฆ„์„ ๋ฐ”๊ฟ”์•ผ ํ•˜๋Š”๊ฒŒ ๋งž์ง€๋งŒ ๋ฐ”๊พธ๋Š” ์ˆœ๊ฐ„ ์ˆ˜๋ฐฑ๋งŒ๊ฐœ์˜ npm ํŒจํ‚ค์ง€๊ฐ€ ์žฅ์• ๋ฅผ ์ผ์œผํ‚ค๋‹ˆ ๊ทธ๋Œ€๋กœ ๋‘๋Š” ๊ฒƒ์ด๋ผ๊ณ  ํ•œ๋‹ค.

 

๊ฒฐ๋ก 

 

๋…ธ๋“œ์˜ ์ „์ฒด ํ๋ฆ„์„ ์ •๋ฆฌํ•˜๋ฉด ๊ทธ๋ฆผ๊ณผ ๊ฐ™๋‹ค.

 

Node.js๋Š” V8์ด ์•ฑ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ ๋’ค ์ œ์–ด๊ถŒ์„ libuv์— ๋„˜๊ฒจ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค. ๋ฃจํ”„๋Š” timers → pending callbacks → idle/prepare → poll → check → close callbacks ์ˆœ์œผ๋กœ ํŽ˜์ด์ฆˆ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ, ๊ฐ ์ฝœ๋ฐฑ ์‹คํ–‰ ์งํ›„๋งˆ๋‹ค nextTickQueue์™€ microTaskQueue๋ฅผ ์šฐ์„  ์†Œ์ง„ํ•œ๋‹ค. I/O๋Š” ๋„คํŠธ์›Œํฌ์˜ ๊ฒฝ์šฐ OS ์ปค๋„(epoll)์ด, ํŒŒ์ผ ์‹œ์Šคํ…œ์€ libuv Thread Pool์ด ๋‹ด๋‹นํ•˜๊ณ , ์™„๋ฃŒ ์‹ ํ˜ธ๋ฅผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ๋ณด๋‚ด poll ํŽ˜์ด์ฆˆ์—์„œ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ Node.js์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ด๋‹ค. ๋น„๋™๊ธฐ I/O์— ๊ฐ•๋ ฅํ•œ ์ด์œ ๋Š” ์œ„์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ๊ธฐ๋ฐ˜์„ ํ™•์‹คํžˆ ์žก๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ Node.js ํŠน์„ฑ์ƒ I/O ์ž‘์—…๋งŒ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ CPU Boundํ•œ ๋กœ์ง์— ๋Œ€ํ•ด์„œ๋Š” ๋น„ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

 

๋‚ด๋ถ€ C/C++ ๊ตฌ์กฐ๊นŒ์ง€ ํŒŒ๊ณ ๋“ค๋‹ค ๋ณด๋‹ˆ Node.js๊ฐ€ ์™œ ์ด๋ ‡๊ฒŒ ์„ค๊ณ„๋˜์—ˆ๋Š”์ง€, ๊ทธ ์ฒ ํ•™์  ์ด์œ ๋ฅผ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋‹จ์ˆœํžˆ "๋…ธ๋“œ๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋‹ค.", "๋น„๋™๊ธฐ๋ผ์„œ ๋น ๋ฅด๋‹ค"๋Š” ๋ง์ด libuv์˜ epoll, Thread Pool, ์ด๋ฒคํŠธ ๋ฃจํ”„ ํŽ˜์ด์ฆˆ ์„ค๊ณ„ ์ „๋ฐ˜์— ๊ฑธ์ณ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š”์ง€ ์ดํ•ดํ•˜๊ณ  ๋‚˜๋‹ˆ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์ธ๋‹ค. ์ด์ œ๋Š” ๊ฒฝํ—˜์œผ๋กœ ์ข€ ์ฑ„์›Œ๋ณผ ์ฐจ๋ก€์ธ ๊ฒƒ ๊ฐ™๋‹ค.