<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Node.js on 엔지니어 생존기록</title><link>https://netscout.github.io/tags/Node.js/</link><description>Recent content in Node.js on 엔지니어 생존기록</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sat, 14 Feb 2026 10:05:42 +0900</lastBuildDate><atom:link href="https://netscout.github.io/tags/Node.js/index.xml" rel="self" type="application/rss+xml"/><item><title>분산 시스템 설계(1) - Paxos 알고리즘</title><link>https://netscout.github.io/posts/%EB%B6%84%EC%82%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-1-paxos-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/</link><pubDate>Sat, 14 Feb 2026 10:05:42 +0900</pubDate><guid>https://netscout.github.io/posts/%EB%B6%84%EC%82%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-1-paxos-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/</guid><description>오래전 시스템은 메인 프레임으로 대표되는 중앙 시스템으로 구성되었습니다. 메인 프레임에 터미널로 연결해서 각자의 작업을 진행했었죠.</description><content>&lt;h2 id="분산-시스템이란">분산 시스템이란?&lt;/h2>
&lt;p>오래전 시스템은 메인 프레임으로 대표되는 중앙 시스템으로 구성되었습니다. 메인 프레임에 터미널로 연결해서 각자의 작업을 진행했었죠.&lt;/p>
&lt;p align="center">&lt;img src="./ibm_main_frame.jpg">&lt;/p>
(IBM의 701 메인프레임 컴퓨터와 콘솔, 출처: IBM)
&lt;br>&lt;br>
그런데 메인 프레임은 엄청난 덩치 뿐만 아니라 취약한 문제가 있었습니다. 메인 프레임이 고장나면 모든 사용자들이 장애의 영향을 받았던 거죠. 그래서 장애를 좀 더 유연하게 견딜 수 있는, 그리고 메인 프레임의 물리적인 한계를 넘어서 처리 용량을 확장할 수 있는 분산 시스템이 등장하기 시작합니다. 제가 기억하는 아주 유명했던 분산 시스템으로 SETI@home(Search for Extra-Terrestrial at home)이 있습니다.
&lt;br>&lt;br>
&lt;p align="center">&lt;img src="./setiathome.jpg">&lt;/p>
(출처: https://setiathome.berkeley.edu/sah_graphics.php)
&lt;br>&lt;br>
1997년에 UC 버클리에서 시작된 프로젝트로 푸에르토 리코에 위치한 아레시보 전파 망원경의 데이터를 통해 외계 생명체의 신호를 탐색하고자 했습니다. 전파 데이터가 너무 방대했기 때문에 이를 처리하기 위해 많은 컴퓨터가 필요했었는데요, 인터넷 연결이 가능한 PC를 가지고 있는 사람 누구나 프로젝트에 참여할 수 있도록 했습니다. 전세계 226개국에서 500만명 이상의 사람들이 자원해서 참여했고, 2020년 데이터 수집이 중단되었다고 합니다.
&lt;br>&lt;br>
&lt;p>이렇게 컴퓨팅 파워의 한계와 장애 극복을 위한 분산 컴퓨팅은 계속 발전해왔고 DCOM, CORBA 등의 벤더 종속적인 기술이 등장했습니다. 그리고 SOAP 웹서비스 등의 벤더 독립적인 기술이 등장해 세상을 바꾸나 싶었지만, 찻잔 속의 태풍으로 끝이 났죠. 그리고 SOA 등의 현학적인 용어가 난무하는 시대를 지나 지금 우리는 클라우드가 보편적인 시대를 살고 있습니다. 이제는 분산 시스템이라는 말을 잘 쓰지 않긴합니다. 그게 기본이니까요.&lt;/p>
&lt;p>하지만 기본이라는 말은 반대로 하면 원리를 모르는 사람들이 많다는 말도 되겠죠. 물론&amp;hellip;. 저도 그 중에 하나입니다. 하하하하&amp;hellip;.&lt;/p>
&lt;h2 id="cap-pacelc">CAP, PACELC&lt;/h2>
&lt;p>분산 시스템의 유명한 이론적 정리는 CAP, PACELC가 있습니다. 우선 CAP을 볼까요?&lt;/p>
&lt;h3 id="cap">CAP&lt;/h3>
&lt;pre class="mermaid">graph TD
CAP[&amp;#34;CAP Theorem&amp;lt;br/&amp;gt;(Brewer&amp;#39;s Theorem, 2000)&amp;#34;]
CAP --&amp;gt; C[&amp;#34;Consistency&amp;lt;br/&amp;gt;모든 노드가 같은 데이터를 봄&amp;#34;]
CAP --&amp;gt; A[&amp;#34;Availability&amp;lt;br/&amp;gt;모든 요청이 응답을 받음&amp;#34;]
CAP --&amp;gt; P[&amp;#34;Partition Tolerance&amp;lt;br/&amp;gt;네트워크 분할에도 동작&amp;#34;]
C ~~~ A ~~~ P
subgraph CP[&amp;#34;CP Systems&amp;#34;]
CP1[&amp;#34;MongoDB&amp;#34;] ~~~ CP2[&amp;#34;HBase&amp;#34;] ~~~ CP3[&amp;#34;Redis Cluster&amp;#34;]
end
subgraph AP[&amp;#34;AP Systems&amp;#34;]
AP1[&amp;#34;Cassandra&amp;#34;] ~~~ AP2[&amp;#34;DynamoDB&amp;#34;] ~~~ AP3[&amp;#34;CouchDB&amp;#34;]
end
subgraph CA[&amp;#34;CA Systems (이론적)&amp;#34;]
CA1[&amp;#34;Traditional RDBMS&amp;lt;br/&amp;gt;(single node)&amp;#34;]
end
C --&amp;gt; CP
P --&amp;gt; CP
A --&amp;gt; AP
P --&amp;gt; AP
C --&amp;gt; CA
A --&amp;gt; CA
style C fill:#ff9999,stroke:#333
style A fill:#99cc99,stroke:#333
style P fill:#9999ff,stroke:#333
style CP fill:#ffcccc,stroke:#333
style AP fill:#ccffcc,stroke:#333
style CA fill:#ccccff,stroke:#333
style CAP fill:#ffffcc,stroke:#333&lt;/pre>&lt;p>Consistency, Availability, Partition Tolerance 이 세가지 요소 중에서 2가지를 선택해야 한다는 게 CAP 이론입니다. 하지만 이 이론에는 함정이 있는데요, 세상에 장애가 발생하지 않은 완벽한 네트워크는 없으므로 반드시 Partition Tolerance는 챙겨야 하기 때문에 CP 또는 AP 둘 중의 하나로 강제된다는 거죠. 그리고 요즘 대세인 결과적 일관성(Eventual Consistency)의 경우 AP에 해당하기는 하지만 일관성(C)을 포기한 건 아니므로 엄밀히 말하면 CAP의 이분법적 분류로는 정확히 설명하기 어렵다는 문제가 있습니다.&lt;/p>
&lt;h3 id="pacelc">PACELC&lt;/h3>
&lt;p>그래서 대안적으로 제시된 게 PACELC 입니다.&lt;/p>
&lt;pre class="mermaid">graph TD
PACELC[&amp;#34;PACELC Theorem&amp;lt;br/&amp;gt;(Daniel Abadi, 2012)&amp;#34;]
PACELC --&amp;gt; Q{&amp;#34;네트워크 Partition&amp;lt;br/&amp;gt;발생?&amp;#34;}
Q --&amp;gt;|&amp;#34;Yes (P)&amp;#34;| PAC{&amp;#34;Partition 상황&amp;#34;}
Q --&amp;gt;|&amp;#34;No (E)&amp;#34;| ELC{&amp;#34;정상 상황 (Else)&amp;#34;}
PAC --&amp;gt;|&amp;#34;Choose&amp;#34;| PA[&amp;#34;Availability&amp;lt;br/&amp;gt;가용성 우선&amp;#34;]
PAC --&amp;gt;|&amp;#34;Choose&amp;#34;| PC[&amp;#34;Consistency&amp;lt;br/&amp;gt;일관성 우선&amp;#34;]
ELC --&amp;gt;|&amp;#34;Choose&amp;#34;| EL[&amp;#34;Latency&amp;lt;br/&amp;gt;낮은 지연 우선&amp;#34;]
ELC --&amp;gt;|&amp;#34;Choose&amp;#34;| EC[&amp;#34;Consistency&amp;lt;br/&amp;gt;일관성 우선&amp;#34;]
subgraph PA_EC[&amp;#34;PA/EC Systems&amp;#34;]
direction LR
S1[&amp;#34;DynamoDB&amp;#34;] ~~~ S2[&amp;#34;Cassandra&amp;#34;] ~~~ S3[&amp;#34;CouchDB&amp;#34;]
end
subgraph PA_EL[&amp;#34;PA/EL Systems&amp;#34;]
direction LR
S4[&amp;#34;Cassandra (tunable)&amp;#34;] ~~~ S5[&amp;#34;Riak&amp;#34;]
end
subgraph PC_EC[&amp;#34;PC/EC Systems&amp;#34;]
direction LR
S6[&amp;#34;MongoDB&amp;#34;] ~~~ S7[&amp;#34;HBase&amp;#34;] ~~~ S8[&amp;#34;BigTable&amp;#34;]
end
subgraph PC_EL[&amp;#34;PC/EL Systems&amp;#34;]
direction LR
S9[&amp;#34;PNUTS (Yahoo)&amp;#34;]
end
PA --&amp;gt; PA_EC
PA --&amp;gt; PA_EL
PC --&amp;gt; PC_EC
PC --&amp;gt; PC_EL
EC --&amp;gt; PA_EC
EC --&amp;gt; PC_EC
EL --&amp;gt; PA_EL
EL --&amp;gt; PC_EL
style PACELC fill:#ffffcc,stroke:#333
style Q fill:#fff0e0,stroke:#333
style PAC fill:#ffdddd,stroke:#333
style ELC fill:#ddddff,stroke:#333
style PA fill:#ff9999,stroke:#333
style PC fill:#99ccff,stroke:#333
style EL fill:#99ff99,stroke:#333
style EC fill:#cc99ff,stroke:#333
style PA_EC fill:#ffe0f0,stroke:#333
style PA_EL fill:#e0ffe0,stroke:#333
style PC_EC fill:#e0e0ff,stroke:#333
style PC_EL fill:#fff0e0,stroke:#333&lt;/pre>&lt;p>선택지는 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>네트워크 장애시(P) -&amp;gt; 가용성(A)이냐 일관성(C)이냐.&lt;/li>
&lt;li>정상 상황(E)에서 -&amp;gt; 일관성(C)이냐 낮은 지연(L)이냐.&lt;/li>
&lt;/ul>
&lt;p>예를 들면 이번에 알아볼 Paxos 알고리즘은 &lt;code>PC/EC&lt;/code> 에 해당합니다.&lt;/p>
&lt;ul>
&lt;li>네트워크 장애시(P) -&amp;gt; 다수의 노드는 동작하지만, 소수의 노드는 동작을 멈춘다. 따라서 일관성(C)을 가용성(A)보다 우선한다.&lt;/li>
&lt;li>정상 상황(E)에서 -&amp;gt; 다수의 노드 간에 합의 과정을 거치는데 이 과정에서 보다 많은 지연시간이 발생한다. 따라서 일관성(C)을 낮은 지연(L)보다 우선한다.&lt;/li>
&lt;/ul>
&lt;h2 id="paxos-알고리즘">Paxos 알고리즘&lt;/h2>
&lt;p>앞선 정리에서 볼 수 있듯이 Paxos 알고리즘은 합의를 통한 일관성을 가장 중요한 가치로 생각하며, 분산 시스템에서 모든 서버가 같은 상태와 결정을 공유하고 유지할 수 있는 방법을 제안합니다.&lt;/p>
&lt;p>Paxos 알고리즘의 핵심 구성 요소는 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>제안자(Proposer): 제안자는 합의해야 할 값을 제안하고, 시스템의 다른 노드에 제안값을 전파합니다.&lt;/li>
&lt;li>수용자(Acceptor): 제안자에게서 제안을 받고, 제안값의 수용 여부를 다른 노드에 알립니다.&lt;/li>
&lt;li>학습자(Learner): 합의된 값을 수용하는 노드.&lt;/li>
&lt;/ul>
&lt;p>그럼 이 노드들이 어떻게 서로 합의를 진행하는지 알아볼까요?&lt;/p>
&lt;pre class="mermaid">sequenceDiagram
participant P as 제안자&amp;lt;br/&amp;gt;(Proposer)
participant A1 as 수용자 1&amp;lt;br/&amp;gt;(Acceptor)
participant A2 as 수용자 2&amp;lt;br/&amp;gt;(Acceptor)
participant A3 as 수용자 3&amp;lt;br/&amp;gt;(Acceptor)
participant L as 학습자&amp;lt;br/&amp;gt;(Learner)
Note over P, L: 📌 1단계: 준비 (Prepare)
rect rgb(220, 240, 255)
P-&amp;gt;&amp;gt;A1: Prepare(N)
P-&amp;gt;&amp;gt;A2: Prepare(N)
P-&amp;gt;&amp;gt;A3: Prepare(N)
A1--&amp;gt;&amp;gt;P: Promise(N)
A2--&amp;gt;&amp;gt;P: Promise(N)
A3--&amp;gt;&amp;gt;P: Promise(N)
Note over P: 과반수 약속 확인 ✅
end
Note over P, L: 📌 2단계: 수락 + 학습 (Accept + Learn)
rect rgb(220, 255, 220)
P-&amp;gt;&amp;gt;A1: Accept(N, 값)
P-&amp;gt;&amp;gt;A2: Accept(N, 값)
P-&amp;gt;&amp;gt;A3: Accept(N, 값)
par 제안자에게 수락 응답
A1--&amp;gt;&amp;gt;P: Accepted ✅
A2--&amp;gt;&amp;gt;P: Accepted ✅
A3--&amp;gt;&amp;gt;P: Accepted ✅
and 학습자에게 동시 전달
A1-&amp;gt;&amp;gt;L: Accepted(N, 값)
A2-&amp;gt;&amp;gt;L: Accepted(N, 값)
A3-&amp;gt;&amp;gt;L: Accepted(N, 값)
end
Note over P: 과반수 수락 → 값 선택 확정 🎉
Note over L: 과반수 수락 확인 → 합의된 값 학습 완료 ✅
end&lt;/pre>&lt;p>여기서 중요한 점은 1단계에서는 제안 번호(N)을 제시하고, 2단계에서 제안 번호(N)와 함께 제안 값을 전달한다는 점입니다. 하나씩 알아볼까요?&lt;/p>
&lt;blockquote>
&lt;p>여기서는 논리적인 구분을 위해서 제안자, 수용자, 학습자를 나눠놓았지만 사실 Paxos에 참여하는 각 노드가 3개의 역할을 모두 수행하는 경우도 있습니다. 이 글의 후반부에서 관련된 예시를 다룰 예정입니다!&lt;/p>&lt;/blockquote>
&lt;h3 id="1단계-준비prepare">1단계: 준비(Prepare)&lt;/h3>
&lt;p>1단계에서는 제안자가 수용자들에게 자신의 제안번호(N)를 제시하며, 자신의 제안번호(N)에 대해서 &amp;lsquo;수락할 것을 약속해달라(promise)&amp;lsquo;고 요청합니다.&lt;/p>
&lt;p>수용자들은 제시 받은 제안번호(N)과 이전에 이미 약속한 제안번호(B)에 대해 다음과 같이 반응합니다.&lt;/p>
&lt;ul>
&lt;li>이미 약속한 제안번호(B)가 존재하는가?&lt;/li>
&lt;li>존재하지 않는다면, 현재의 제안번호(N)에 대해 수용할 것을 약속한다.&lt;/li>
&lt;li>존재한다면, 이미 약속한 제안번호(B)과 현재의 제안번호(N)을 비교한다.
&lt;ul>
&lt;li>이미 약속한 제안번호가 더 크다면(B &amp;gt; N), 현재의 제안번호(N)을 거절한다.&lt;/li>
&lt;li>현재의 제안번호(N)이 이미 약속한 제안번호(B)보다 더 크다면, 이전 약속(B)을 파기하고 현재의 제안번호(N)에 대해 수용할 것을 약속한다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;pre class="mermaid">flowchart TD
START([수용자가 제안 수신&amp;lt;br/&amp;gt;Prepare N]) --&amp;gt; CHECK{이미 약속한&amp;lt;br/&amp;gt;제안 번호 B가&amp;lt;br/&amp;gt;존재하는가?}
CHECK --&amp;gt;|존재하지 않음| ACCEPT_NEW[✅ 현재 제안 번호 N에 대해&amp;lt;br/&amp;gt;수용할 것을 약속&amp;lt;br/&amp;gt;Promise N]
CHECK --&amp;gt;|존재함| COMPARE{현재 제안 번호 N과&amp;lt;br/&amp;gt;이미 약속한 제안 번호 B&amp;lt;br/&amp;gt;비교}
COMPARE --&amp;gt;|B &amp;gt; N&amp;lt;br/&amp;gt;이미 약속한 번호가 더 큼| REJECT[❌ 현재 제안 거절&amp;lt;br/&amp;gt;Reject N]
COMPARE --&amp;gt;|N &amp;gt; B&amp;lt;br/&amp;gt;현재 제안 번호가 더 큼| ACCEPT_HIGHER[✅ 이전 약속 B 파기&amp;lt;br/&amp;gt;현재 제안 번호 N에 대해&amp;lt;br/&amp;gt;수용할 것을 약속&amp;lt;br/&amp;gt;Promise N]
ACCEPT_NEW --&amp;gt; WAIT([수용 요청 대기&amp;lt;br/&amp;gt;Accept N, 값])
ACCEPT_HIGHER --&amp;gt; WAIT
style START fill:#4A90D9,stroke:#333,color:#fff
style CHECK fill:#F5A623,stroke:#333,color:#fff
style COMPARE fill:#F5A623,stroke:#333,color:#fff
style ACCEPT_NEW fill:#7ED321,stroke:#333,color:#fff
style REJECT fill:#D0021B,stroke:#333,color:#fff
style ACCEPT_HIGHER fill:#7ED321,stroke:#333,color:#fff
style WAIT fill:#9B9B9B,stroke:#333,color:#fff&lt;/pre>&lt;p>어떤 값이든 수용할 것을 약속한 노드들은 이제 수용 요청 대시 상태로 진입합니다.&lt;/p>
&lt;h3 id="2단계-수용accept">2단계: 수용(Accept)&lt;/h3>
&lt;p>이제 제안자는 각 수용자에게 자신있게 자신의 제안 번호(N)와 함께 값을 전달합니다. 이제 수용자들은 자신이 수용할 것을 약속한 제안 번호(B)와 비교하며 다음과 같이 반응합니다.&lt;/p>
&lt;ul>
&lt;li>수용할 것을 약속한 제안 번호(B)가 존재하는지 확인&lt;/li>
&lt;li>B가 존재하지 않는다면, 제안 번호(N)의 값을 수용(Prepare 없이 Accept가 진행되는 경우는 생각하기 어렵긴 하지만!)&lt;/li>
&lt;li>B가 존재한다면, 제안 번호(N)과 비교
&lt;ul>
&lt;li>N &amp;lt; B 이면, 이미 약속한 제안 번호(B)가 더 크기 때문에 N을 거절&lt;/li>
&lt;li>N = B 이면, 현재 제안 번호(N)가 수용할 것을 약속한 제안 번호이므로 값을 수용하고, 제안자 및 학습자에게 수용되었음을 통지&lt;/li>
&lt;li>N &amp;gt; B 이면, 현재 제안 번호(N)가 이미 약속한 제안 번호(B)보다 크므로 이전 약속을 파기하고 N을 수용할 것을 약속&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;pre class="mermaid">flowchart TD
START([수용자가 수락 요청 수신&amp;lt;br/&amp;gt;Accept N, 값]) --&amp;gt; CHECK{현재 약속한&amp;lt;br/&amp;gt;제안번호 B가&amp;lt;br/&amp;gt;존재하는가?}
CHECK --&amp;gt;|존재하지 않음| ACCEPT[✅ 수락&amp;lt;br/&amp;gt;Accepted N, 값]
CHECK --&amp;gt;|존재함| COMPARE{현재 수락 요청의&amp;lt;br/&amp;gt;제안번호 N과&amp;lt;br/&amp;gt;약속한 제안번호 B&amp;lt;br/&amp;gt;비교}
COMPARE --&amp;gt;|B &amp;gt; N&amp;lt;br/&amp;gt;약속한 번호가 더 큼| REJECT[❌ 거절&amp;lt;br/&amp;gt;더 높은 번호에 이미 약속함]
COMPARE --&amp;gt;|N = B&amp;lt;br/&amp;gt;약속한 번호와 동일| ACCEPT2[✅ 수락&amp;lt;br/&amp;gt;Accepted N, 값]
COMPARE --&amp;gt;|N &amp;gt; B&amp;lt;br/&amp;gt;수락 요청 번호가 더 큼| ACCEPT3[✅ 수락&amp;lt;br/&amp;gt;Accepted N, 값]
ACCEPT --&amp;gt; NOTIFY([제안자 + 학습자에게&amp;lt;br/&amp;gt;Accepted 전달])
ACCEPT2 --&amp;gt; NOTIFY
ACCEPT3 --&amp;gt; NOTIFY
REJECT --&amp;gt; WAIT([제안자에게 거절 응답])
style START fill:#4A90D9,stroke:#333,color:#fff
style CHECK fill:#F5A623,stroke:#333,color:#fff
style COMPARE fill:#F5A623,stroke:#333,color:#fff
style ACCEPT fill:#7ED321,stroke:#333,color:#fff
style ACCEPT2 fill:#7ED321,stroke:#333,color:#fff
style ACCEPT3 fill:#7ED321,stroke:#333,color:#fff
style REJECT fill:#D0021B,stroke:#333,color:#fff
style NOTIFY fill:#9B9B9B,stroke:#333,color:#fff
style WAIT fill:#9B9B9B,stroke:#333,color:#fff&lt;/pre>&lt;p>위 다이어그램에서 볼 수 있듯이 Prepare 단계에서 과반수의 약속을 받았더라도 Accept 단계에서 거절될 수도 있습니다. 더 높은 제안 번호의 Prepare가 중간에 끼어들 수도 있기 때문이죠.&lt;/p>
&lt;h2 id="좀-더-쓸모있는-예시를-들어보자-분산-시스템의-리더-노드-선출">좀 더 쓸모있는 예시를 들어보자: 분산 시스템의 리더 노드 선출&lt;/h2>
&lt;p>자, 알고리즘은 뭐 이런 식으로 진행됩니다. 그런데 제가 이 내용을 처음 봤을 때는 솔직히 &lt;code>뭐 어쩌라고?&lt;/code> 싶었습니다. 이게 도대체 뭔 소린지, 그리고 어디다가 써먹을 수 있다는 건지 영 감이 안잡혔죠. 그래서 이리저리 머리를 굴려보다가 그럴듯한 예시를 하나 생각해냈습니다.&lt;/p>
&lt;p>우리가 쓰는 디비든 쿠버네티스 같은 인프라 운영 시스템이든 각종 실패를 견디기 위해서 노드를 분산해둡니다. 쿠버네티스의 경우 클러스터 운영을 위한 마스터 노드(또는 컨트롤 플레인)와 서비스를 돌리기 위한 워커 노드가 있죠. 특히 마스터 노드의 경우 전체 시스템 관리 및 워커 노드를 제어하는 등의 핵심 역할을 맡고 있습니다. 이런 마스터 노드가 죽으면 곤란하기 때문에 여러 마스터 노드를 실행해서 특정 노드에 장애가 생겨도 서비스 운영에 문제가 없도록 하고 있죠.&lt;/p>
&lt;p>그런데 이 글을 시작하면서 분산 시스템에서는 합의가 중요하다고 했었습니다. 여러대의 마스터 노드가 있을 때 각자 서로 다른 결정을 하면 안되겠죠. 그래서 리더 노드를 선출하고 다른 노드들은 리더가 결정한 내용을 따르도록 하고 있습니다. 지금 부터 살펴볼 예제는 바로 그 리더를 선출하는 과정을 Paxos 알고리즘으로 구현해 볼 겁니다!&lt;/p>
&lt;h3 id="시스템-개요">시스템 개요&lt;/h3>
&lt;p>간결한 구현을 위해서 시작점이 되는 cli 앱이 3개의 웹 서버 클러스터를 실행하고, 3개의 웹 서버가 서로 통신하면서 Paxos 알고리즘을 통해 리더를 선출하게 됩니다. 그리고 필요에 따라서 각 서버를 죽이거나 재실행해서 다양한 경우를 실험해 볼 수 있도록 할 겁니다.&lt;/p>
&lt;p>원래 이런 시스템은 멀티 Paxos로 구현해야 합니다. Paxos는 원래 합의를 한 번 하면 그걸로 끝이기 때문에 여러번의 합의를 하려면 계속 새로운 Paxos 인스턴스를 생성해야 합니다. 그런데 그렇게 하면 너무 복잡하니까, 단일 Paxos로 구현해서 매 라운드 마다 상태 값을 초기화 하고 다시 설정하는 형태로 구현해서 멀티 Paxos를 흉내내는 형태로 구현할 겁니다.&lt;/p>
&lt;p>node.js와 typescript를 이용해서 2개의 파일로 구현할 겁니다.&lt;/p>
&lt;ul>
&lt;li>runCluster.ts: 3개의 웹 서버를 실행하고 관리하는 클러스터 관리자 cli 구현&lt;/li>
&lt;li>node.ts: 각 웹서버를 express로 구현&lt;/li>
&lt;/ul>
&lt;p>그리고 시스템 아키텍처는 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">graph TD
USER[&amp;#34;사용자 터미널&amp;#34;] --&amp;gt; CLI[&amp;#34;runCluster.ts&amp;lt;br/&amp;gt;(클러스터 관리자 + 대화형 CLI)&amp;lt;br/&amp;gt;━━━━━━━━━━━━━━━━&amp;lt;br/&amp;gt;- child_process.spawn으로 3개 노드 생성&amp;lt;br/&amp;gt;- 각 노드 stdout에 색상 적용 출력&amp;lt;br/&amp;gt;- readline으로 대화형 CLI 제공&amp;#34;]
CLI --&amp;gt;|&amp;#34;spawn: npx tsx node.ts 1 3001 ...&amp;#34;| N1[&amp;#34;node.ts (노드 1)&amp;lt;br/&amp;gt;:3001&amp;#34;]
CLI --&amp;gt;|&amp;#34;spawn: npx tsx node.ts 2 3002 ...&amp;#34;| N2[&amp;#34;node.ts (노드 2)&amp;lt;br/&amp;gt;:3002&amp;#34;]
CLI --&amp;gt;|&amp;#34;spawn: npx tsx node.ts 3 3003 ...&amp;#34;| N3[&amp;#34;node.ts (노드 3)&amp;lt;br/&amp;gt;:3003&amp;#34;]
N1 &amp;lt;--&amp;gt;|&amp;#34;HTTP POST&amp;#34;| N2
N2 &amp;lt;--&amp;gt;|&amp;#34;HTTP POST&amp;#34;| N3
N1 &amp;lt;--&amp;gt;|&amp;#34;HTTP POST&amp;#34;| N3
style CLI fill:#f9f,stroke:#333,stroke-width:2px
style N1 fill:#36c,stroke:#333,color:#fff
style N2 fill:#c93,stroke:#333,color:#fff
style N3 fill:#c3c,stroke:#333,color:#fff&lt;/pre>&lt;p>&lt;code>runCluster.ts&lt;/code>가 3개의 웹서버 노드 프로세스를 생성하고, 각 노드는 Express 서버를 실행하여 HTTP로 통신합니다.&lt;/p>
&lt;h4 id="클러스터-관리자-cli-구현">클러스터 관리자 cli 구현&lt;/h4>
&lt;p>클러스터 관리자 cli는 각 웹서버 노드 프로세스를 실행하고, 특정 노드 프로세스를 죽이거나 다시 시작하거나 할 수 있습니다.&lt;/p>
&lt;pre class="mermaid">graph LR
CLI[&amp;#34;대화형 CLI&amp;lt;br/&amp;gt;(readline)&amp;#34;] --&amp;gt; KILL[&amp;#34;kill &amp;amp;lt;id&amp;amp;gt;&amp;#34;]
CLI --&amp;gt; START[&amp;#34;start &amp;amp;lt;id&amp;amp;gt;&amp;#34;]
CLI --&amp;gt; STATUS[&amp;#34;status&amp;#34;]
CLI --&amp;gt; EXIT[&amp;#34;exit&amp;#34;]
KILL --&amp;gt; K_ACTION[&amp;#34;child.kill(&amp;#39;SIGTERM&amp;#39;)&amp;lt;br/&amp;gt;→ 노드 프로세스 종료&amp;#34;]
START --&amp;gt; S_ACTION[&amp;#34;startNode(nodeConfig)&amp;lt;br/&amp;gt;→ 새 child_process.spawn&amp;lt;br/&amp;gt;→ 노드 재시작&amp;#34;]
STATUS --&amp;gt; ST_ACTION[&amp;#34;fetch GET /status&amp;lt;br/&amp;gt;→ 각 노드 상태 조회&amp;lt;br/&amp;gt;→ 콘솔 출력&amp;#34;]
EXIT --&amp;gt; E_ACTION[&amp;#34;processes.forEach(p =&amp;gt; p.kill(&amp;#39;SIGTERM&amp;#39;))&amp;lt;br/&amp;gt;→ 모든 노드 종료&amp;lt;br/&amp;gt;→ process.exit(0)&amp;#34;]
style CLI fill:#f9f,stroke:#333
style K_ACTION fill:#fcc,stroke:#333
style S_ACTION fill:#cfc,stroke:#333
style ST_ACTION fill:#ccf,stroke:#333
style E_ACTION fill:#ffc,stroke:#333&lt;/pre>&lt;p>&lt;code>runCluster.ts&lt;/code>의 전체 코드는 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">spawn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ChildProcess&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;child_process&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">readline&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;readline&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">fileURLToPath&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;url&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">dirname&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;path&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">__filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fileURLToPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">import&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">meta&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">__dirname&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">__filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">NodeConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">port&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 각 노드 아이디와 포트 번호 정의
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">NODES&lt;/span>: &lt;span class="kt">NodeConfig&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>: &lt;span class="kt">3001&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>: &lt;span class="kt">3002&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>: &lt;span class="kt">3003&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 노드 프로세스 관리를 위한 맵 정의
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">processes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Map&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">number&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">ChildProcess&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 가독성을 위해 각 노드의 아이디별 출력 색상 정의
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">colors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;\x1b[36m&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 파란색
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;\x1b[33m&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 노란색
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;\x1b[35m&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 핑크색
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">reset&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;\x1b[0m&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 기본 색상
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 프로세스를 시작하는 함수
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param nodeConfig - 노드 아이디와 포트 번호 정의
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">startNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>: &lt;span class="kt">NodeConfig&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 노드가 통신해야 할 다른 노드의 URL 목록 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">peers&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">NODES&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">n&lt;/span>: &lt;span class="kt">NodeConfig&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">n&lt;/span>: &lt;span class="kt">NodeConfig&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="sb">`http://localhost:&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 시작...&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 프로세스 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">child&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">spawn&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;npx&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;tsx&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;node.ts&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cwd&lt;/span>: &lt;span class="kt">__dirname&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stdio&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;ignore&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;pipe&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;pipe&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 프로세스의 출력에 색상을 적용 하여 출력
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">child&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stdout&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;data&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>: &lt;span class="kt">Buffer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">color&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stdout&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">color&lt;/span>&lt;span class="si">}${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 프로세스의 에러 출력에 색상을 적용 하여 출력
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">child&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stderr&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;data&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>: &lt;span class="kt">Buffer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">color&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stderr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">color&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 오류] &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 프로세스가 종료될 때 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">child&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;exit&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">code&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">signal&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 프로세스 종료 (code: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, signal: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">signal&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 프로세스를 관리하는 맵에 추가
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">processes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodeConfig&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">child&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 프로세스 종료
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param id - 종료할 노드의 아이디
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">killNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">child&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">processes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 프로세스가 존재하면 종료
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">child&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">child&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">kill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SIGTERM&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">[클러스터] 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 종료&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n[클러스터] 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 실행 중이 아님&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 클러스터 상태 출력
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">printStatus() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\n=== 클러스터 상태 ===&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">node&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">NODES&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`http://localhost:&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/status`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>: &lt;span class="kt">AbortSignal.timeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">color&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">leaderMark&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isLeader&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s1">&amp;#39;👑&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39; &amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 상태 출력
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">color&lt;/span>&lt;span class="si">}${&lt;/span>&lt;span class="nx">leaderMark&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">: 리더=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="s1">&amp;#39;없음&amp;#39;&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, `&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`턴=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, 리더=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isLeader&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, `&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`약속한 제안 번호=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="s1">&amp;#39;없음&amp;#39;&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, 수락한 제안 번호=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="s1">&amp;#39;없음&amp;#39;&lt;/span>&lt;span class="si">}${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">color&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">color&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">: ❌ 다운&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">colors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reset&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 대화형 CLI 구현
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">rl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">readline&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createInterface&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span>: &lt;span class="kt">process.stdin&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">output&lt;/span>: &lt;span class="kt">process.stdout&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 모든 노드 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\n🚀 Starting Paxos Leader Election Cluster...\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">node&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">NODES&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">startNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 모든 노드가 시작될 때까지 대기 후 명령어 출력
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\n&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;=&amp;#39;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;📋 사용 가능한 명령어:&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; kill &amp;lt;id&amp;gt; - 노드 종료 (특정 노드에 장애가 발생하여 종료되는 걸 시뮬레이션)&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; start &amp;lt;id&amp;gt; - 종료된 노드 재시작&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; status - 클러스터 상태 출력&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; exit - 모든 노드 종료 및 종료&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;=&amp;#39;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">},&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 대화형 CLI 명령어 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">rl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;line&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">line&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">cmd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">arg&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">line&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">trim&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; &amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 명령어 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">switch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">cmd&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s1">&amp;#39;kill&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">killId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">parseInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arg&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">NODES&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">find&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nx">killId&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">killNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">killId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`잘못된 노드 ID: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">arg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s1">&amp;#39;start&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">startId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">parseInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arg&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">node&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">NODES&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">find&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nx">startId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">processes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">has&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">startId&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">startId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">: 이미 실행 중&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">startNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`잘못된 노드 ID: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">arg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s1">&amp;#39;status&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">printStatus&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s1">&amp;#39;exit&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\n🛑 모든 노드 종료...\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">kill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SIGTERM&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s1">&amp;#39;help&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\n📋 사용 가능한 명령어:&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; kill &amp;lt;id&amp;gt; - 노드 종료&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; start &amp;lt;id&amp;gt; - 종료된 노드 재시작&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; status - 클러스터 상태 출력&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; exit - 모든 노드 종료 및 종료\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">line&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">trim&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`알 수 없는 명령어: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">cmd&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">. &amp;#39;help&amp;#39; 명령어를 사용하여 사용 가능한 명령어를 확인하세요.&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Ctrl+C 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SIGINT&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\n\n🛑 모든 노드 종료...\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">kill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SIGTERM&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="웹-서버-노드-구현">웹 서버 노드 구현&lt;/h4>
&lt;p>웹 서버 노드는 paxos 알고리즘을 위한 변수들과 각종 타이머, API 라우트 등을 포함하고 있습니다.&lt;/p>
&lt;pre class="mermaid">graph TD
subgraph CONFIG[&amp;#34;설정 (Configuration)&amp;#34;]
C1[&amp;#34;nodeId: number&amp;lt;br&amp;gt;자신의 노드ID&amp;#34;] ~~~ C2[&amp;#34;port: number&amp;#34;] ~~~ C3[&amp;#34;peers: string[]&amp;lt;br&amp;gt;자신을 제외한 클러스터의 노드&amp;#34;] ~~~ C4[&amp;#34;allNodes: string[]&amp;lt;br&amp;gt;자신을 포함한 클러스터의 모든 노드&amp;#34;] ~~~ C5[&amp;#34;majority: number&amp;lt;br&amp;gt;다수 노드의 수&amp;#34;]
end
subgraph PAXOS[&amp;#34;Paxos Acceptor 상태&amp;#34;]
P1[&amp;#34;promisedProposal: number | null&amp;lt;br&amp;gt;수락을 약속한 제안번호&amp;#34;] ~~~ P2[&amp;#34;acceptedProposal: number | null&amp;lt;br&amp;gt;수락한 제안번호&amp;#34;] ~~~ P3[&amp;#34;acceptedValue: number | null&amp;lt;br&amp;gt;수락한 값(리더의 노드ID)&amp;#34;]
end
subgraph LEADER[&amp;#34;Leader 상태&amp;#34;]
L1[&amp;#34;currentLeaderId: number | null&amp;#34;] ~~~ L2[&amp;#34;currentTurn: number&amp;#34;] ~~~ L3[&amp;#34;electionRound: number&amp;#34;]
end
subgraph TIMERS[&amp;#34;타이머&amp;#34;]
T1[&amp;#34;heartbeatTimer: NodeJS.Timeout | null&amp;lt;br/&amp;gt;(setInterval 1초 주기, 리더가 자신이 살아있음을 전송)&amp;#34;] ~~~ T2[&amp;#34;electionTimer: NodeJS.Timeout | null&amp;lt;br/&amp;gt;(setTimeout 3~5초, 리더는 중지)&amp;#34;]
end
subgraph ROUTES[&amp;#34;Express Routes&amp;#34;]
R1[&amp;#34;POST /paxos/prepare&amp;#34;] ~~~ R2[&amp;#34;POST /paxos/accept&amp;#34;] ~~~ R3[&amp;#34;POST /leader/heartbeat&amp;#34;] ~~~ R4[&amp;#34;GET /status&amp;#34;]
end
subgraph LOGIC[&amp;#34;핵심 로직 함수&amp;#34;]
F1[&amp;#34;runElection()&amp;#34;] ~~~ F2[&amp;#34;startHeartbeat()&amp;#34;] ~~~ F3[&amp;#34;stopHeartbeat()&amp;#34;] ~~~ F4[&amp;#34;resetElectionTimeout()&amp;#34;] ~~~ F5[&amp;#34;scheduleNextElection()&amp;#34;]
end
CONFIG --&amp;gt; PAXOS --&amp;gt; LEADER --&amp;gt; TIMERS --&amp;gt; ROUTES --&amp;gt; LOGIC
style CONFIG fill:#e0f0ff,stroke:#333
style PAXOS fill:#ffe0f0,stroke:#333
style LEADER fill:#f0ffe0,stroke:#333
style TIMERS fill:#fff0e0,stroke:#333
style ROUTES fill:#f0e0ff,stroke:#333
style LOGIC fill:#e0fff0,stroke:#333&lt;/pre>&lt;p>각 API는 paxos 알고리즘의 Prepare, Accept 단계를 위한 &lt;code>/paxos/prepare&lt;/code>, &lt;code>/paxos/accept&lt;/code>와 리더의 생존 신호를 수신할 &lt;code>/leader/heartbeat&lt;/code>를 구현합니다.&lt;/p>
&lt;pre class="mermaid">graph LR
N[&amp;#34;노드 (Proposer/Acceptor)&amp;#34;]
N --&amp;gt;|&amp;#34;POST /paxos/prepare&amp;lt;br/&amp;gt;{proposalNumber: N}&amp;#34;| PREP[&amp;#34;Phase 1: PREPARE&amp;#34;]
PREP --&amp;gt;|&amp;#34;{promise: boolean,&amp;lt;br/&amp;gt;acceptedProposal: number | null,&amp;lt;br/&amp;gt;acceptedValue: number | null}&amp;#34;| N
N --&amp;gt;|&amp;#34;POST /paxos/accept&amp;lt;br/&amp;gt;{proposalNumber: N, value: V}&amp;#34;| ACC[&amp;#34;Phase 2: ACCEPT&amp;#34;]
ACC --&amp;gt;|&amp;#34;{accepted: boolean}&amp;#34;| N
N --&amp;gt;|&amp;#34;POST /leader/heartbeat&amp;lt;br/&amp;gt;{leaderId: L, term: T}&amp;#34;| HB[&amp;#34;하트비트 수신&amp;#34;]
HB --&amp;gt;|&amp;#34;{ok: true}&amp;#34;| N
N --&amp;gt;|&amp;#34;GET /status&amp;#34;| ST[&amp;#34;상태 조회&amp;#34;]
ST --&amp;gt;|&amp;#34;{nodeId, currentLeaderId,&amp;lt;br/&amp;gt;currentTerm, promisedProposal,&amp;lt;br/&amp;gt;acceptedProposal, acceptedValue,&amp;lt;br/&amp;gt;isLeader}&amp;#34;| N
style PREP fill:#ffcccc,stroke:#333
style ACC fill:#ccffcc,stroke:#333
style HB fill:#ccccff,stroke:#333
style ST fill:#ffffcc,stroke:#333&lt;/pre>&lt;p>&lt;strong>엔드포인트 요약&lt;/strong>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>엔드포인트&lt;/th>
&lt;th>메서드&lt;/th>
&lt;th>요청&lt;/th>
&lt;th>응답&lt;/th>
&lt;th>용도&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>/paxos/prepare&lt;/code>&lt;/td>
&lt;td>POST&lt;/td>
&lt;td>&lt;code>{ proposalNumber }&lt;/code>&lt;/td>
&lt;td>&lt;code>{ promise, acceptedProposal, acceptedValue }&lt;/code>&lt;/td>
&lt;td>Paxos Phase 1 - 약속 요청&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/paxos/accept&lt;/code>&lt;/td>
&lt;td>POST&lt;/td>
&lt;td>&lt;code>{ proposalNumber, value }&lt;/code>&lt;/td>
&lt;td>&lt;code>{ accepted }&lt;/code>&lt;/td>
&lt;td>Paxos Phase 2 - 수락 요청&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/leader/heartbeat&lt;/code>&lt;/td>
&lt;td>POST&lt;/td>
&lt;td>&lt;code>{ leaderId, term }&lt;/code>&lt;/td>
&lt;td>&lt;code>{ ok }&lt;/code>&lt;/td>
&lt;td>리더 생존 신호 전송&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/status&lt;/code>&lt;/td>
&lt;td>GET&lt;/td>
&lt;td>-&lt;/td>
&lt;td>전체 상태 객체&lt;/td>
&lt;td>노드 상태 조회 (디버깅용)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;code>node.ts&lt;/code>의 전체 코드는 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;express&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// === 타입 정의 ===
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">NodeConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">port&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">peers&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">PrepareRequest&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proposalNumber&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promise&lt;/span>: &lt;span class="kt">boolean&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedProposal&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedValue&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">AcceptRequest&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proposalNumber&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">value&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">accepted&lt;/span>: &lt;span class="kt">boolean&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">HeartbeatMessage&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">leaderId&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">term&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// cli 매개변수로 전달받은 노드 아이디, 포트, 통신해야 할 다른 노드의 URL 목록 등의 값을 변수에 저장
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">nodeId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">parseInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">parseInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">peers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">argv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">allNodes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="sb">`http://localhost:&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">majority&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">floor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 시작 with 통신해야 할 다른 노드의 URL 목록: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 다수 필요: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 중 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">majority&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 이상`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// === Paxos Acceptor 상태 변수들 ===
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">promisedProposal&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">acceptedProposal&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 수락된 값(리더의 노드 아이디)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">acceptedValue&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// === Leader 상태와 Heartbeat ===
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">currentLeaderId&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">currentTerm&lt;/span>: &lt;span class="kt">number&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 리더의 하트비트 전송 타이머
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">heartbeatTimer&lt;/span>: &lt;span class="kt">NodeJS.Timeout&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 리더 부재시 선출을 진행하기 위한 타이머
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">electionTimer&lt;/span>: &lt;span class="kt">NodeJS.Timeout&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">electionRound&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// === Paxos Proposer 로직 ===
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 리더 선출 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 리더 선출 성공 여부, 실패시 null 반환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">runElection&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">boolean&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">null&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 제안 번호가 이미 있는 것보다 큰지 확인
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 이전에 약속된 제안 번호가 있는 경우, 라운드 조정(다른 노드가 이미 제안한 제안 번호보다 높은 제안 번호를 사용하도록 함)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * ex: Math.max(0, Math.ceil(6 / 3)) = Math.max(0, 2) = 2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionRound&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">max&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">electionRound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ceil&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionRound&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 제안 번호 = 노드 아이디 + (라운드 * 노드 개수) -&amp;gt; 각 라운드 마다 노드의 제안 번호의 고유성을 보장하기 위함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 예를 들어, 노드 1, 2, 3이 있고, 라운드가 1인 경우,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 1, 라운드 1: 1 + (1 * 3) = 4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 2, 라운드 1: 2 + (1 * 3) = 5
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 3, 라운드 1: 3 + (1 * 3) = 6
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 1, 라운드 2: 1 + (2 * 3) = 7
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 2, 라운드 2: 2 + (2 * 3) = 8
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 노드 3, 라운드 2: 3 + (2 * 3) = 9
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">proposalNumber&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">nodeId&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">electionRound&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="sb">n[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 🗳️ 리더 선출 시작 / 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Phase 1: PREPARE 요청 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] Phase 1: 모든 노드에 PREPARE 요청 전송...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">preparePromises&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">nodeUrl&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 자신을 포함한 모든 노드에 PREPARE 요청 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// PREPARE 요청 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeUrl&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/paxos/prepare`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">proposalNumber&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">PrepareRequest&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>: &lt;span class="kt">AbortSignal.timeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// PREPARE 요청 실패 시 null 반환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ok&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// PREPARE 요청 실패 시 null 반환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// PREPARE 요청 결과 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">prepareResults&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">allSettled&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">preparePromises&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// PREPARE 요청 결과 중 성공한 것들 중 약속을 받은 것들만 추출
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">promises&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">prepareResults&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="nx">PromiseFulfilledResult&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">PrepareResponse&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;fulfilled&amp;#39;&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="o">!!&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">promise&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] Phase 1: 약속을 받은 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">promises&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 개 (필요 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">majority&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 개)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">promises&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">majority&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] ❌ Phase 1 실패: 약속을 받은 노드가 다수 필요 개수보다 적습니다. 리더 선출을 중단하고 다음 선거 시작.`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">scheduleNextElection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 수락된 값 중 가장 높은 값을 채택, 없으면 자신을 제안
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kd">let&lt;/span> &lt;span class="nx">proposedValue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">nodeId&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">highestAcceptedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">p&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">promises&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 수락된 값 중 가장 높은 값을 채택
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">highestAcceptedProposal&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">highestAcceptedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proposedValue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedValue&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">highestAcceptedProposal&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] Phase 1: 이전에 수락된 값 (노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposedValue&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">) 채택 / 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">highestAcceptedProposal&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] Phase 1: 이전에 수락된 값이 없으므로 자신을 제안 (노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposedValue&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Phase 2: Accept
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] Phase 2: 모든 노드에 ACCEPT (값: 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposedValue&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">) 요청 전송...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">acceptPromises&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">nodeUrl&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 자신을 포함한 모든 노드에 ACCEPT 요청 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ACCEPT 요청 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeUrl&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/paxos/accept`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">value&lt;/span>: &lt;span class="kt">proposedValue&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">AcceptRequest&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>: &lt;span class="kt">AbortSignal.timeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ACCEPT 요청 실패 시 null 반환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ok&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ACCEPT 요청 실패 시 null 반환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ACCEPT 요청 결과 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">acceptResults&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">allSettled&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">acceptPromises&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ACCEPT 요청 결과 중 성공한 것들 중 수락된 것들만 추출
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">accepts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">acceptResults&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;fulfilled&amp;#39;&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">accepted&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] Phase 2: 수락된 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">accepts&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 개 (필요 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">majority&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 개)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">accepts&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="nx">majority&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] ✅ 리더 선출 성공! 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposedValue&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 가 리더가 되었습니다. (제안 번호 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">proposedValue&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nx">nodeId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 👑 나는 리더!`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 리더는 자신의 선출 타임아웃을 중지 (팔로워만 타임아웃을 감시해야 함)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clearTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionTimer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">startHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] ❌ Phase 2 실패: 수락된 노드가 다수 필요 개수보다 적습니다. 리더 선출을 중단하고 다음 선거 시작.`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">scheduleNextElection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 다음 리더 선출 예약
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">scheduleNextElection() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 다음 선거 시작 전 랜덤 딜레이 적용
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">delay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2000&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">3000&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 리더가 없으면 다음 선거 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">runElection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">delay&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 하트비트 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param term 현재 턴
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">startHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">term&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 하트비트 중지
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">stopHeartbeat&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">heartbeatTimer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setInterval&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 💓 하트비트 전송 (턴 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">term&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 자신(리더)를 제외한 나머지 노드에 하트비트 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">peers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">peerUrl&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">peerUrl&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/leader/heartbeat`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">leaderId&lt;/span>: &lt;span class="kt">nodeId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">term&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">HeartbeatMessage&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>: &lt;span class="kt">AbortSignal.timeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 다운 시 무시
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 하트비트 중지
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">stopHeartbeat() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">heartbeatTimer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clearInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">heartbeatTimer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">heartbeatTimer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 리더 선출 타임아웃 리셋
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 타임아웃 이후, 리더가 없으면 리더 선출 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">resetElectionTimeout() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clearTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 리더 선출 타임아웃 랜덤 적용 (3-5초)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3000&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">2000&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionTimer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] ⏰ 하트비트 타임아웃 - 리더 없음 감지, 리더 선출 시작...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 리더로 선출된 노드가 서비스 다운되는 경우에 해당 노드는 리더 선출에서 제외되어야 함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 따라서 수락된 상태를 리셋하여 진행하며, 각 리더 선출은 사실상 새로운 Paxos 인스턴스가 되도록 함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedValue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">runElection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">timeout&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// === Express Routes ===
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">express&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Paxos Phase 1: Prepare
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/paxos/prepare&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">proposalNumber&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">PrepareRequest&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] PREPARE 요청 수신 / 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">proposalNumber&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">promisedProposal&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 수락된 상태를 리셋하여 진행하며, 각 리더 선출은 사실상 새로운 Paxos 인스턴스가 되도록 함(죽은 리더 재선출 방지)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedValue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] → 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 약속`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promise&lt;/span>: &lt;span class="kt">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedProposal&lt;/span>: &lt;span class="kt">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedValue&lt;/span>: &lt;span class="kt">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] → 이미 약속한 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">promisedProposal&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 거절`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promise&lt;/span>: &lt;span class="kt">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedProposal&lt;/span>: &lt;span class="kt">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedValue&lt;/span>: &lt;span class="kt">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Paxos Phase 2: ACCEPT 요청 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/paxos/accept&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">value&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">AcceptRequest&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] ACCEPT 요청 수신 / 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">, 값: 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 약속된 제안 번호가 없거나, 현재 제안 번호가 약속된 제안 번호보다 크거나 같으면 수락
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">proposalNumber&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="nx">promisedProposal&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 약속된 제안 번호 갱신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 수락된 제안 번호 갱신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 수락된 값 갱신(리더의 노드 아이디)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">acceptedValue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 리더 아이디 갱신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 턴 갱신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">currentTerm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">proposalNumber&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] → 수락! 새로운 리더: 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> (제안 번호 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">proposalNumber&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 리더가 아니라면 하트비트 중지
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">nodeId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stopHeartbeat&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 리더 선출 타임아웃 리셋
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">resetElectionTimeout&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">accepted&lt;/span>: &lt;span class="kt">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] → 이미 약속한 제안 번호 #&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">promisedProposal&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 거절`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">accepted&lt;/span>: &lt;span class="kt">false&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 리더가 전송하는 하트비트 수신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/leader/heartbeat&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">leaderId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">term&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">HeartbeatMessage&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 새로운 턴이 시작되었다면,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">term&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="nx">currentTerm&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 새로운 리더가 등장했다면,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">leaderId&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">currentLeaderId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 💓 새로운 리더로부터 하트비트 수신 / 리더: 노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">leaderId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> (턴 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">term&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 리더 아이디 갱신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">leaderId&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 현재 턴 갱신
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">currentTerm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">term&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 리더 선출 타임아웃 리셋(타임아웃 이후, 리더가 없으면 리더 선출 시작)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 리더의 하트비트가 안정적으로 수신된다면, 리더 선출의 타임아웃은 계속 리셋된다. -&amp;gt; 현재 상태 유지.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resetElectionTimeout&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">ok&lt;/span>: &lt;span class="kt">true&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 상태 조회 엔드포인트
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/status&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">nodeId&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentLeaderId&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentTerm&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promisedProposal&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedProposal&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedValue&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">isLeader&lt;/span>: &lt;span class="kt">currentLeaderId&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nx">nodeId&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// === 메인 함수 ===
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 🚀 포트 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">에서 수신 중...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 리더 선출 예약(랜덤 딜레이 적용)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">delay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1000&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">2000&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 리더 조회 전 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">round&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">delay&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">ms 대기...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 랜덤 딜레이 이후, 리더가 없으면 리더 선출 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 리더 없음, 리더 선출 시작...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">runElection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">delay&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 리더 선출 타임아웃 리셋(타임아웃 이후, 리더가 없으면 리더 선출 시작)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">resetElectionTimeout&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="이제-돌려보자">이제 돌려보자!&lt;/h3>
&lt;p>이제 모든 준비가 끝났습니다. &lt;code>npm start&lt;/code> 명령으로 예제를 실행해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">❯ npm start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; paxos-leader-election@1.0.0 start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; npx tsx runCluster.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚀 Starting Paxos Leader Election Cluster...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">노드 &lt;span class="m">1&lt;/span> 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">노드 &lt;span class="m">2&lt;/span> 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">노드 &lt;span class="m">3&lt;/span> 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 시작 with 통신해야 할 다른 노드의 URL 목록: http://localhost:3002, http://localhost:3003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 시작 with 통신해야 할 다른 노드의 URL 목록: http://localhost:3001, http://localhost:3003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 다수 필요: &lt;span class="m">3&lt;/span> 중 &lt;span class="m">2&lt;/span> 이상
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 시작 with 통신해야 할 다른 노드의 URL 목록: http://localhost:3001, http://localhost:3002
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 다수 필요: &lt;span class="m">3&lt;/span> 중 &lt;span class="m">2&lt;/span> 이상
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 다수 필요: &lt;span class="m">3&lt;/span> 중 &lt;span class="m">2&lt;/span> 이상
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 🚀 포트 3002에서 수신 중...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 리더 조회 전 2639ms 대기...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 🚀 포트 3003에서 수신 중...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 리더 조회 전 2096ms 대기...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 🚀 포트 3001에서 수신 중...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 리더 조회 전 1722ms 대기...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">============================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">📋 사용 가능한 명령어:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">kill&lt;/span> &amp;lt;id&amp;gt; - 노드 종료 &lt;span class="o">(&lt;/span>특정 노드에 장애가 발생하여 종료되는 걸 시뮬레이션&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> start &amp;lt;id&amp;gt; - 종료된 노드 재시작
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status - 클러스터 상태 출력
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">exit&lt;/span> - 모든 노드 종료 및 &lt;span class="nv">종료&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">============================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 리더 없음, 리더 선출 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 🗳️ 리더 선출 시작 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 1: 모든 노드에 PREPARE 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#4 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#4 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#4 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 1: 약속을 받은 노드 3/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 1: 이전에 수락된 값이 없으므로 자신을 제안 &lt;span class="o">(&lt;/span>노드 1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 2: 모든 노드에 ACCEPT &lt;span class="o">(&lt;/span>값: 노드 1&lt;span class="o">)&lt;/span> 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#4, 값: 노드 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#4, 값: 노드 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#4, 값: 노드 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 2: 수락된 노드 3/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> ✅ 리더 선출 성공! 노드 &lt;span class="m">1&lt;/span> 가 리더가 되었습니다. &lt;span class="o">(&lt;/span>제안 번호 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 👑 나는 리더!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ki&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ll&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>클러스터&lt;span class="o">]&lt;/span> 노드 &lt;span class="m">1&lt;/span> 종료
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 프로세스 종료 &lt;span class="o">(&lt;/span>code: 143, signal: null&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> ⏰ 하트비트 타임아웃 - 리더 없음 감지, 리더 선출 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 🗳️ 리더 선출 시작 / 제안 번호 &lt;span class="c1">#12&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> Phase 1: 모든 노드에 PREPARE 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#12&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#12 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#12&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#12 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> Phase 1: 약속을 받은 노드 2/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> Phase 1: 이전에 수락된 값이 없으므로 자신을 제안 &lt;span class="o">(&lt;/span>노드 3&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> Phase 2: 모든 노드에 ACCEPT &lt;span class="o">(&lt;/span>값: 노드 3&lt;span class="o">)&lt;/span> 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#12, 값: 노드 3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#12, 값: 노드 3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> Phase 2: 수락된 노드 2/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> ✅ 리더 선출 성공! 노드 &lt;span class="m">3&lt;/span> 가 리더가 되었습니다. &lt;span class="o">(&lt;/span>제안 번호 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 👑 나는 리더!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">st&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">art &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">노드 &lt;span class="m">1&lt;/span> 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 시작 with 통신해야 할 다른 노드의 URL 목록: http://localhost:3002, http://localhost:3003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 다수 필요: &lt;span class="m">3&lt;/span> 중 &lt;span class="m">2&lt;/span> 이상
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 🚀 포트 3001에서 수신 중...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 리더 조회 전 1960ms 대기...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 💓 새로운 리더로부터 하트비트 수신 / 리더: 노드 &lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">k&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ill 3&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>클러스터&lt;span class="o">]&lt;/span> 노드 &lt;span class="m">3&lt;/span> 종료
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 3&lt;span class="o">]&lt;/span> 프로세스 종료 &lt;span class="o">(&lt;/span>code: 143, signal: null&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> ⏰ 하트비트 타임아웃 - 리더 없음 감지, 리더 선출 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> 🗳️ 리더 선출 시작 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 1: 모든 노드에 PREPARE 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 이미 약속한 제안 번호 &lt;span class="c1">#12 거절&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#4 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> Phase 1: 약속을 받은 노드 1/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> ❌ Phase &lt;span class="m">1&lt;/span> 실패: 약속을 받은 노드가 다수 필요 개수보다 적습니다. 리더 선출을 중단하고 다음 선거 시작.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> ⏰ 하트비트 타임아웃 - 리더 없음 감지, 리더 선출 시작...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 🗳️ 리더 선출 시작 / 제안 번호 &lt;span class="c1">#17&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> Phase 1: 모든 노드에 PREPARE 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#17&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#17 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> PREPARE 요청 수신 / 제안 번호 &lt;span class="c1">#17&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 제안 번호 &lt;span class="c1">#17 약속&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> Phase 1: 약속을 받은 노드 2/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> Phase 1: 이전에 수락된 값이 없으므로 자신을 제안 &lt;span class="o">(&lt;/span>노드 2&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> Phase 2: 모든 노드에 ACCEPT &lt;span class="o">(&lt;/span>값: 노드 2&lt;span class="o">)&lt;/span> 요청 전송...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#17, 값: 노드 2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">2&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> ACCEPT 요청 수신 / 제안 번호 &lt;span class="c1">#17, 값: 노드 2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 1&lt;span class="o">]&lt;/span> → 수락! 새로운 리더: 노드 &lt;span class="m">2&lt;/span> &lt;span class="o">(&lt;/span>제안 번호 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> Phase 2: 수락된 노드 2/3 개 &lt;span class="o">(&lt;/span>필요 &lt;span class="m">2&lt;/span> 개&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> ✅ 리더 선출 성공! 노드 &lt;span class="m">2&lt;/span> 가 리더가 되었습니다. &lt;span class="o">(&lt;/span>제안 번호 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 👑 나는 리더!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>노드 2&lt;span class="o">]&lt;/span> 💓 하트비트 전송 &lt;span class="o">(&lt;/span>턴 17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">^C
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🛑 모든 노드 종료...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>클러스터가 실행된 뒤, 1번 노드가 리더로 선출이 되었습니다. 그리고 게속해서 1번 노드가 자신이 살아있음을 다른 노드에서 전송하죠. 이 동안에는 다른 노드들이 리더가 살아있기 때문에 리더 선출을 시도하지 않습니다.&lt;/p>
&lt;p>그리고 중간에 &lt;code>kill 1&lt;/code>을 입력해서 1번 노드를 제거했습니다. 그러자 리더의 생존 신고(하트비트)가 더 이상 도착하지 않게되고, 이를 감지한 다른 노드들이 리더 선출을 시작합니다. 그리고 노드 3번이 리더가 됩니다.&lt;/p>
&lt;p>다시 &lt;code>start 1&lt;/code>을 입력해서 1번 노드를 시작하면, 1번 노드는 현재 3번 노드가 리더라는 걸 파악하고 따르기로 합니다. 그리고 다시 3번 노드를 제거하면, 리더 선출이 시작되고 이번엔 2번 노드가 리더가 됩니다.&lt;/p>
&lt;p>이 과정을 다이어그램으로 도식화 해보면 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">flowchart TD
START[&amp;#34;runElection() 호출&amp;#34;] --&amp;gt; CALC[&amp;#34;제안 번호 계산&amp;lt;br/&amp;gt;proposalNumber = nodeId + (electionRound * 3)&amp;#34;]
CALC --&amp;gt; PHASE1[&amp;#34;Phase 1: PREPARE&amp;lt;br/&amp;gt;모든 노드에 PREPARE 전송&amp;#34;]
PHASE1 --&amp;gt; PROMISE_CHECK{&amp;#34;과반수 약속&amp;lt;br/&amp;gt;받았는가?&amp;#34;}
PROMISE_CHECK --&amp;gt;|&amp;#34;No (promises &amp;lt; majority)&amp;#34;| FAIL1[&amp;#34;scheduleNextElection()&amp;lt;br/&amp;gt;2~5초 후 재시도&amp;#34;]
PROMISE_CHECK --&amp;gt;|&amp;#34;Yes (promises &amp;gt;= majority)&amp;#34;| VALUE_CHECK[&amp;#34;이전에 수락된 값 확인&amp;lt;br/&amp;gt;없으면 자신을 제안&amp;#34;]
VALUE_CHECK --&amp;gt; PHASE2[&amp;#34;Phase 2: ACCEPT&amp;lt;br/&amp;gt;모든 노드에 ACCEPT 전송&amp;lt;br/&amp;gt;(제안 번호, 제안 값)&amp;#34;]
PHASE2 --&amp;gt; ACCEPT_CHECK{&amp;#34;과반수 수락&amp;lt;br/&amp;gt;받았는가?&amp;#34;}
ACCEPT_CHECK --&amp;gt;|&amp;#34;No (accepts &amp;lt; majority)&amp;#34;| FAIL2[&amp;#34;scheduleNextElection()&amp;lt;br/&amp;gt;2~5초 후 재시도&amp;#34;]
ACCEPT_CHECK --&amp;gt;|&amp;#34;Yes (accepts &amp;gt;= majority)&amp;#34;| LEADER_CHECK{&amp;#34;proposedValue&amp;lt;br/&amp;gt;=== nodeId?&amp;#34;}
LEADER_CHECK --&amp;gt;|&amp;#34;Yes (나를 제안)&amp;#34;| LEADER[&amp;#34;리더 확정! 👑&amp;lt;br/&amp;gt;electionTimer 중지&amp;lt;br/&amp;gt;startHeartbeat()&amp;#34;]
LEADER_CHECK --&amp;gt;|&amp;#34;No (다른 노드 제안)&amp;#34;| FOLLOWER[&amp;#34;팔로워로 전환&amp;lt;br/&amp;gt;resetElectionTimeout()&amp;#34;]
LEADER --&amp;gt; END1[&amp;#34;하트비트 전송 시작&amp;lt;br/&amp;gt;(1초 주기)&amp;#34;]
FOLLOWER --&amp;gt; END2[&amp;#34;하트비트 수신 대기&amp;lt;br/&amp;gt;(3~5초 타임아웃)&amp;#34;]
FAIL1 --&amp;gt; WAIT1[&amp;#34;랜덤 딜레이 대기&amp;#34;]
FAIL2 --&amp;gt; WAIT2[&amp;#34;랜덤 딜레이 대기&amp;#34;]
WAIT1 --&amp;gt; CHECK1{&amp;#34;currentLeaderId&amp;lt;br/&amp;gt;=== null?&amp;#34;}
WAIT2 --&amp;gt; CHECK2{&amp;#34;currentLeaderId&amp;lt;br/&amp;gt;=== null?&amp;#34;}
CHECK1 --&amp;gt;|Yes| START
CHECK1 --&amp;gt;|No| END3[&amp;#34;이미 리더 존재&amp;lt;br/&amp;gt;재시도 안 함&amp;#34;]
CHECK2 --&amp;gt;|Yes| START
CHECK2 --&amp;gt;|No| END3
style START fill:#e0f0ff,stroke:#333
style PHASE1 fill:#ffcccc,stroke:#333
style PHASE2 fill:#ccffcc,stroke:#333
style LEADER fill:#ffd700,stroke:#333,stroke-width:3px
style FOLLOWER fill:#ccccff,stroke:#333
style FAIL1 fill:#ffccaa,stroke:#333
style FAIL2 fill:#ffccaa,stroke:#333&lt;/pre>&lt;p>혹시 더 상세한 설명을 원하는 분들 위해서(음.. 미래의 저를 위해서&amp;hellip;!) 더 자세한 알고리즘의 흐름을 이 글 마지막의 Appendix에 추가해두었습니다.&lt;/p>
&lt;h2 id="appendix---더-상세한-설명">Appendix - 더 상세한 설명&lt;/h2>
&lt;p>미래의 저를 위해서 더 상세한 설명을 남겨두겠습니다!&lt;/p>
&lt;h3 id="구체적-시나리오-노드-3이-리더로-선출">구체적 시나리오: 노드 3이 리더로 선출&lt;/h3>
&lt;h4 id="초기-상태">초기 상태&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>노드&lt;/th>
&lt;th>promisedProposal&lt;/th>
&lt;th>acceptedProposal&lt;/th>
&lt;th>acceptedValue&lt;/th>
&lt;th>currentLeaderId&lt;/th>
&lt;th>currentTerm&lt;/th>
&lt;th>electionRound&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>null&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>노드 3이 가장 먼저 랜덤 딜레이가 만료되어 선출을 시작한다고 가정합니다.&lt;/p>
&lt;h4 id="phase-1-prepare-시퀀스">Phase 1: PREPARE 시퀀스&lt;/h4>
&lt;pre class="mermaid">sequenceDiagram
participant P as 노드 3 (Proposer)
participant A1 as 노드 1 (Acceptor)
participant A2 as 노드 2 (Acceptor)
participant A3 as 노드 3 (Acceptor)
Note over P: electionRound++ → 1&amp;lt;br/&amp;gt;proposalNumber = 3+(1*3) = 6
P-&amp;gt;&amp;gt;A1: PREPARE(#6)
P-&amp;gt;&amp;gt;A2: PREPARE(#6)
P-&amp;gt;&amp;gt;A3: PREPARE(#6)
Note over A1: promisedProposal=null&amp;lt;br/&amp;gt;6 &amp;gt; null → 약속!&amp;lt;br/&amp;gt;promisedProposal=6&amp;lt;br/&amp;gt;accepted 리셋 (죽은 리더 방지)
Note over A2: promisedProposal=null&amp;lt;br/&amp;gt;6 &amp;gt; null → 약속!&amp;lt;br/&amp;gt;promisedProposal=6&amp;lt;br/&amp;gt;accepted 리셋
Note over A3: promisedProposal=null&amp;lt;br/&amp;gt;6 &amp;gt; null → 약속!&amp;lt;br/&amp;gt;promisedProposal=6&amp;lt;br/&amp;gt;accepted 리셋
A1--&amp;gt;&amp;gt;P: {promise:true, accepted:null}
A2--&amp;gt;&amp;gt;P: {promise:true, accepted:null}
A3--&amp;gt;&amp;gt;P: {promise:true, accepted:null}
Note over P: 약속 3/3 (필요 2) ✅&amp;lt;br/&amp;gt;이전 수락 값 없음&amp;lt;br/&amp;gt;→ 자신(노드 3) 제안&lt;/pre>&lt;p>&lt;strong>핵심 포인트&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>promisedProposal === null || proposalNumber &amp;gt; promisedProposal&lt;/code> 조건 충족 시 약속&lt;/li>
&lt;li>&lt;strong>PREPARE 처리 시 &lt;code>acceptedProposal&lt;/code>과 &lt;code>acceptedValue&lt;/code>를 null로 리셋&lt;/strong> (node.ts:295-296)
&lt;ul>
&lt;li>이유: 리더 선출에서는 각 선거가 새로운 Paxos 인스턴스처럼 동작&lt;/li>
&lt;li>죽은 리더가 재선출되는 것을 방지&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>응답에 이전 수락 값 포함 (표준 Paxos 메커니즘)&lt;/li>
&lt;/ul>
&lt;h4 id="phase-1-이후-상태">Phase 1 이후 상태&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>노드&lt;/th>
&lt;th>promisedProposal&lt;/th>
&lt;th>acceptedProposal&lt;/th>
&lt;th>acceptedValue&lt;/th>
&lt;th>currentLeaderId&lt;/th>
&lt;th>currentTerm&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>null (리셋)&lt;/td>
&lt;td>null (리셋)&lt;/td>
&lt;td>null&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>null (리셋)&lt;/td>
&lt;td>null (리셋)&lt;/td>
&lt;td>null&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>null (리셋)&lt;/td>
&lt;td>null (리셋)&lt;/td>
&lt;td>null&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="phase-2-accept-시퀀스">Phase 2: ACCEPT 시퀀스&lt;/h4>
&lt;pre class="mermaid">sequenceDiagram
participant P as 노드 3 (Proposer)
participant A1 as 노드 1 (Acceptor)
participant A2 as 노드 2 (Acceptor)
participant A3 as 노드 3 (Acceptor)
P-&amp;gt;&amp;gt;A1: ACCEPT(#6, value:3)
P-&amp;gt;&amp;gt;A2: ACCEPT(#6, value:3)
P-&amp;gt;&amp;gt;A3: ACCEPT(#6, value:3)
Note over A1: 6 &amp;gt;= promisedProposal(6) → 수락!&amp;lt;br/&amp;gt;acceptedProposal=6&amp;lt;br/&amp;gt;acceptedValue=3&amp;lt;br/&amp;gt;currentLeaderId=3&amp;lt;br/&amp;gt;currentTerm=6&amp;lt;br/&amp;gt;resetElectionTimeout()
Note over A2: 6 &amp;gt;= 6 → 수락!&amp;lt;br/&amp;gt;currentLeaderId=3&amp;lt;br/&amp;gt;currentTerm=6&amp;lt;br/&amp;gt;resetElectionTimeout()
Note over A3: 6 &amp;gt;= 6 → 수락!&amp;lt;br/&amp;gt;currentLeaderId=3&amp;lt;br/&amp;gt;currentTerm=6
A1--&amp;gt;&amp;gt;P: {accepted:true}
A2--&amp;gt;&amp;gt;P: {accepted:true}
A3--&amp;gt;&amp;gt;P: {accepted:true}
Note over P: 수락 3/3 (필요 2) ✅&amp;lt;br/&amp;gt;리더 선출 성공!&amp;lt;br/&amp;gt;proposedValue(3) === nodeId(3)&amp;lt;br/&amp;gt;→ &amp;#34;나는 리더!&amp;#34; 👑&amp;lt;br/&amp;gt;→ electionTimer 중지&amp;lt;br/&amp;gt;→ startHeartbeat(6)&lt;/pre>&lt;p>&lt;strong>핵심 포인트&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>promisedProposal === null || proposalNumber &amp;gt;= promisedProposal&lt;/code> 조건 충족 시 수락&lt;/li>
&lt;li>모든 Acceptor가 상태 업데이트:
&lt;ul>
&lt;li>&lt;code>acceptedProposal&lt;/code>, &lt;code>acceptedValue&lt;/code> 설정&lt;/li>
&lt;li>&lt;code>currentLeaderId&lt;/code>, &lt;code>currentTerm&lt;/code> 갱신&lt;/li>
&lt;li>리더가 아닌 노드는 &lt;code>stopHeartbeat()&lt;/code> 호출&lt;/li>
&lt;li>&lt;strong>&lt;code>resetElectionTimeout()&lt;/code> 호출&lt;/strong> → 3~5초 타이머 리셋&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>노드 3은 &lt;code>proposedValue === nodeId&lt;/code> 확인 후:
&lt;ul>
&lt;li>&lt;strong>&lt;code>electionTimer&lt;/code> 중지&lt;/strong> (node.ts:192-194) ← 중요! 리더는 타이머 불필요&lt;/li>
&lt;li>&lt;code>startHeartbeat(6)&lt;/code> 호출&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="phase-2-이후-최종-상태">Phase 2 이후 최종 상태&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>노드&lt;/th>
&lt;th>promisedProposal&lt;/th>
&lt;th>acceptedProposal&lt;/th>
&lt;th>acceptedValue&lt;/th>
&lt;th>currentLeaderId&lt;/th>
&lt;th>currentTerm&lt;/th>
&lt;th>heartbeatTimer&lt;/th>
&lt;th>electionTimer&lt;/th>
&lt;th>역할&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>3&lt;/td>
&lt;td>&lt;strong>3&lt;/strong>&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>null&lt;/td>
&lt;td>활성 (3~5초)&lt;/td>
&lt;td>팔로워&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>3&lt;/td>
&lt;td>&lt;strong>3&lt;/strong>&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>null&lt;/td>
&lt;td>활성 (3~5초)&lt;/td>
&lt;td>팔로워&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>3&lt;/td>
&lt;td>&lt;strong>3&lt;/strong>&lt;/td>
&lt;td>&lt;strong>6&lt;/strong>&lt;/td>
&lt;td>&lt;strong>활성 (1초)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>null (중지)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>리더&lt;/strong> 👑&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="선출-후-하트비트-흐름">선출 후 하트비트 흐름&lt;/h3>
&lt;pre class="mermaid">sequenceDiagram
participant L as 노드 3 (리더)
participant F1 as 노드 1 (팔로워)
participant F2 as 노드 2 (팔로워)
loop 매 1초 (setInterval 1000ms)
L-&amp;gt;&amp;gt;F1: POST /leader/heartbeat {leaderId:3, term:6}
L-&amp;gt;&amp;gt;F2: POST /leader/heartbeat {leaderId:3, term:6}
Note over F1: term(6) &amp;gt;= currentTerm(6)&amp;lt;br/&amp;gt;resetElectionTimeout()&amp;lt;br/&amp;gt;타임아웃 리셋: 3~5초
Note over F2: term(6) &amp;gt;= currentTerm(6)&amp;lt;br/&amp;gt;resetElectionTimeout()&amp;lt;br/&amp;gt;타임아웃 리셋: 3~5초
F1--&amp;gt;&amp;gt;L: {ok:true}
F2--&amp;gt;&amp;gt;L: {ok:true}
end
Note over L,F2: 하트비트(1초) &amp;lt;&amp;lt; 타임아웃(3~5초)&amp;lt;br/&amp;gt;→ 정상 상태에서는 타임아웃이 절대 만료되지 않음&lt;/pre>&lt;p>&lt;strong>하트비트 전송 코드&lt;/strong> (node.ts:225-245):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">startHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">term&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stopHeartbeat&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 기존 타이머 제거
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">heartbeatTimer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setInterval&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 💓 하트비트 전송 (턴 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">term&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ★ 자신(리더)를 제외한 peers에만 전송
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">peers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">peerUrl&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">peerUrl&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/leader/heartbeat`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">leaderId&lt;/span>: &lt;span class="kt">nodeId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">term&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>: &lt;span class="kt">AbortSignal.timeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 노드 다운 시 무시
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>하트비트 수신 핸들러&lt;/strong> (node.ts:352-375):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/leader/heartbeat&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">leaderId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">term&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">term&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="nx">currentTerm&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">leaderId&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">currentLeaderId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 💓 새로운 리더로부터 하트비트 수신`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">leaderId&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentTerm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">term&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resetElectionTimeout&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// ★ 핵심! 타임아웃 리셋
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">ok&lt;/span>: &lt;span class="kt">true&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>타이밍 관계&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>하트비트 전송: &lt;strong>1초&lt;/strong> (&lt;code>setInterval(1000)&lt;/code>)&lt;/li>
&lt;li>선출 타임아웃: &lt;strong>3~5초&lt;/strong> (&lt;code>3000 + Math.random() * 2000&lt;/code>)&lt;/li>
&lt;li>&lt;strong>하트비트 주기 &amp;laquo; 타임아웃 범위&lt;/strong> → 정상 상태에서는 타임아웃 절대 만료 안 됨&lt;/li>
&lt;/ul>
&lt;h3 id="리더-장애-감지와-재선출">리더 장애 감지와 재선출&lt;/h3>
&lt;h4 id="시나리오-kill-3--노드-2가-새-리더로-선출">시나리오: kill 3 → 노드 2가 새 리더로 선출&lt;/h4>
&lt;pre class="mermaid">sequenceDiagram
participant CLI as 클러스터 CLI
participant N3 as 노드 3 (리더)
participant N1 as 노드 1 (팔로워)
participant N2 as 노드 2 (팔로워)
CLI-&amp;gt;&amp;gt;N3: kill 3 (SIGTERM)
Note over N3: 프로세스 종료&amp;lt;br/&amp;gt;하트비트 중단
destroy N3
Note over N1: 하트비트 안 옴...
Note over N2: 하트비트 안 옴...
Note over N2: 3.5초 후 타임아웃 만료!&amp;lt;br/&amp;gt;currentLeaderId = null&amp;lt;br/&amp;gt;accepted 상태 리셋&amp;lt;br/&amp;gt;→ runElection()
Note over N2: promisedProposal = 6&amp;lt;br/&amp;gt;electionRound = max(0, ceil(6/3)) = 2&amp;lt;br/&amp;gt;electionRound++ = 3&amp;lt;br/&amp;gt;proposalNumber = 2+(3*3) = 11
N2-&amp;gt;&amp;gt;N1: PREPARE(#11)
N2-&amp;gt;&amp;gt;N2: PREPARE(#11) (자신)
N2--xN3: PREPARE(#11) (타임아웃)
Note over N1: 11 &amp;gt; 6 → 약속!&amp;lt;br/&amp;gt;promisedProposal=11&amp;lt;br/&amp;gt;accepted 리셋
N1--&amp;gt;&amp;gt;N2: {promise:true}
Note over N2: 자신도 약속
Note over N2: 약속 2/3 (필요 2) ✅
N2-&amp;gt;&amp;gt;N1: ACCEPT(#11, value:2)
N2-&amp;gt;&amp;gt;N2: ACCEPT(#11, value:2) (자신)
N2--xN3: ACCEPT(#11, value:2) (타임아웃)
Note over N1: 11 &amp;gt;= 11 → 수락!&amp;lt;br/&amp;gt;currentLeaderId=2&amp;lt;br/&amp;gt;currentTerm=11&amp;lt;br/&amp;gt;resetElectionTimeout()
N1--&amp;gt;&amp;gt;N2: {accepted:true}
Note over N2: 자신도 수락
Note over N2: 수락 2/3 (필요 2) ✅&amp;lt;br/&amp;gt;새 리더 확정! 👑&amp;lt;br/&amp;gt;electionTimer 중지&amp;lt;br/&amp;gt;startHeartbeat(11)
loop 매 1초
N2-&amp;gt;&amp;gt;N1: heartbeat {leaderId:2, term:11}
Note over N1: resetElectionTimeout()
end&lt;/pre>&lt;p>&lt;strong>electionRound 조정 로직&lt;/strong> (node.ts:70-71):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionRound&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">max&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">electionRound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ceil&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">electionRound&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>예시&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>promisedProposal = 6&lt;/code>, &lt;code>allNodes.length = 3&lt;/code>&lt;/li>
&lt;li>&lt;code>electionRound = max(0, ceil(6/3)) = max(0, 2) = 2&lt;/code>&lt;/li>
&lt;li>&lt;code>electionRound++ = 3&lt;/code>&lt;/li>
&lt;li>&lt;code>proposalNumber = 2 + (3*3) = 11&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>과반수 요구&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>3개 노드 중 1개 다운 → 2개 응답 (= 과반수) → 선출 가능!&lt;/li>
&lt;li>&lt;strong>Paxos의 장애 허용 능력&lt;/strong>: N개 노드에서 (N-1)/2개 장애 허용&lt;/li>
&lt;/ul>
&lt;h4 id="재선출-후-상태">재선출 후 상태&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>노드&lt;/th>
&lt;th>promisedProposal&lt;/th>
&lt;th>acceptedProposal&lt;/th>
&lt;th>acceptedValue&lt;/th>
&lt;th>currentLeaderId&lt;/th>
&lt;th>currentTerm&lt;/th>
&lt;th>heartbeatTimer&lt;/th>
&lt;th>electionTimer&lt;/th>
&lt;th>역할&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>2&lt;/td>
&lt;td>&lt;strong>2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>11&lt;/strong>&lt;/td>
&lt;td>null&lt;/td>
&lt;td>활성 (3~5초)&lt;/td>
&lt;td>팔로워&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>2&lt;/td>
&lt;td>&lt;strong>2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>11&lt;/strong>&lt;/td>
&lt;td>&lt;strong>활성 (1초)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>null (중지)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>리더&lt;/strong> 👑&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>-&lt;/td>
&lt;td>-&lt;/td>
&lt;td>-&lt;/td>
&lt;td>-&lt;/td>
&lt;td>-&lt;/td>
&lt;td>-&lt;/td>
&lt;td>-&lt;/td>
&lt;td>&lt;strong>DOWN&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="노드-재시작과-팔로워-합류">노드 재시작과 팔로워 합류&lt;/h3>
&lt;h4 id="시나리오-start-3--노드-3이-팔로워로-자연스럽게-합류">시나리오: start 3 → 노드 3이 팔로워로 자연스럽게 합류&lt;/h4>
&lt;pre class="mermaid">sequenceDiagram
participant CLI as 클러스터 CLI
participant N2 as 노드 2 (리더)
participant N3 as 노드 3 (재시작)
CLI-&amp;gt;&amp;gt;N3: start 3 (새 프로세스 spawn)
Note over N3: 모든 상태 초기화&amp;lt;br/&amp;gt;currentLeaderId=null&amp;lt;br/&amp;gt;currentTerm=0&amp;lt;br/&amp;gt;Express 서버 시작 (:3003)&amp;lt;br/&amp;gt;랜덤 딜레이: 1.8초 대기
N2-&amp;gt;&amp;gt;N3: heartbeat {leaderId:2, term:11}
Note over N3: term(11) &amp;gt;= currentTerm(0)&amp;lt;br/&amp;gt;새 리더 감지!&amp;lt;br/&amp;gt;currentLeaderId=2&amp;lt;br/&amp;gt;currentTerm=11&amp;lt;br/&amp;gt;resetElectionTimeout()
N3--&amp;gt;&amp;gt;N2: {ok:true}
Note over N3: 딜레이 만료 (1.8초)&amp;lt;br/&amp;gt;currentLeaderId=2 (null 아님)&amp;lt;br/&amp;gt;→ runElection() 호출 안 함! ✅
N2-&amp;gt;&amp;gt;N3: heartbeat {leaderId:2, term:11}
Note over N3: resetElectionTimeout()&amp;lt;br/&amp;gt;안정적으로 팔로워 동작&lt;/pre>&lt;p>&lt;strong>재시작 초기화 흐름&lt;/strong> (node.ts:393-409):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 🚀 포트 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">에서 수신 중...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 1~3초 랜덤 딜레이
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">delay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1000&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">2000&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 리더 조회 전 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">round&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">delay&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">ms 대기...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 딜레이 후 리더가 없으면 선출 시작
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">currentLeaderId&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// ★ 핵심 조건
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`[노드 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">nodeId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">] 리더 없음, 리더 선출 시작...`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">runElection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">delay&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resetElectionTimeout&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>핵심 메커니즘&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>노드 3이 재시작하면 모든 상태 변수가 초기값 (&lt;code>currentLeaderId = null&lt;/code>)&lt;/li>
&lt;li>랜덤 딜레이 (1~3초) 동안 대기&lt;/li>
&lt;li>이 시간 동안 리더(노드 2)의 하트비트(1초 주기)가 도착&lt;/li>
&lt;li>하트비트 수신 시 &lt;code>currentLeaderId = 2&lt;/code>로 설정&lt;/li>
&lt;li>딜레이 만료 시 &lt;code>currentLeaderId !== null&lt;/code> → 선출 시작 안 함!&lt;/li>
&lt;li>자연스럽게 팔로워로 합류&lt;/li>
&lt;/ol>
&lt;h4 id="재시작-후-안정-상태">재시작 후 안정 상태&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>노드&lt;/th>
&lt;th>promisedProposal&lt;/th>
&lt;th>acceptedProposal&lt;/th>
&lt;th>acceptedValue&lt;/th>
&lt;th>currentLeaderId&lt;/th>
&lt;th>currentTerm&lt;/th>
&lt;th>electionTimer&lt;/th>
&lt;th>역할&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>2&lt;/td>
&lt;td>2&lt;/td>
&lt;td>11&lt;/td>
&lt;td>활성&lt;/td>
&lt;td>팔로워&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>2&lt;/td>
&lt;td>2&lt;/td>
&lt;td>11&lt;/td>
&lt;td>null (중지)&lt;/td>
&lt;td>리더 👑&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>&lt;strong>null&lt;/strong>&lt;/td>
&lt;td>&lt;strong>null&lt;/strong>&lt;/td>
&lt;td>&lt;strong>null&lt;/strong>&lt;/td>
&lt;td>&lt;strong>2 (학습)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>11 (학습)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>활성&lt;/strong>&lt;/td>
&lt;td>&lt;strong>팔로워 (새로 합류)&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>주목&lt;/strong>: 노드 3은 Paxos 상태가 null이지만 하트비트만으로 리더 정보를 학습합니다. 메모리 기반 구현이므로 재시작 시 상태 복구 없음.&lt;/p>
&lt;h3 id="타이밍-관계-요약">타이밍 관계 요약&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>타이머&lt;/th>
&lt;th>시간&lt;/th>
&lt;th>구현&lt;/th>
&lt;th>용도&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>하트비트 전송 주기&lt;/td>
&lt;td>&lt;strong>1초&lt;/strong>&lt;/td>
&lt;td>&lt;code>setInterval(1000)&lt;/code>&lt;/td>
&lt;td>리더가 팔로워에게 생존 신호 전송&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>하트비트 타임아웃 (선출 트리거)&lt;/td>
&lt;td>&lt;strong>3~5초&lt;/strong>&lt;/td>
&lt;td>&lt;code>3000 + Math.random() * 2000&lt;/code>&lt;/td>
&lt;td>팔로워가 리더 장애 감지&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>초기 선출 딜레이&lt;/td>
&lt;td>&lt;strong>1~3초&lt;/strong>&lt;/td>
&lt;td>&lt;code>1000 + Math.random() * 2000&lt;/code>&lt;/td>
&lt;td>클러스터 시작 시 동시 선출 방지&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>선거 실패 후 재시도 딜레이&lt;/td>
&lt;td>&lt;strong>2~5초&lt;/strong>&lt;/td>
&lt;td>&lt;code>2000 + Math.random() * 3000&lt;/code>&lt;/td>
&lt;td>Phase 실패 후 재시도 간격&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HTTP 요청 타임아웃&lt;/td>
&lt;td>&lt;strong>1초&lt;/strong>&lt;/td>
&lt;td>&lt;code>AbortSignal.timeout(1000)&lt;/code>&lt;/td>
&lt;td>네트워크 요청 타임아웃&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>타이밍 설계 원칙&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>하트비트 주기 &amp;laquo; 타임아웃&lt;/strong> → 정상 상태에서 타임아웃 절대 만료 안 됨&lt;/li>
&lt;li>&lt;strong>모든 딜레이/타임아웃에 랜덤 범위&lt;/strong> → 선출 충돌 방지 (livelock 방지)&lt;/li>
&lt;li>&lt;strong>HTTP 타임아웃 &amp;laquo; 하트비트 주기&lt;/strong> → 빠른 장애 감지&lt;/li>
&lt;/ul>
&lt;h3 id="전체-흐름-한눈에-보기">전체 흐름 한눈에 보기&lt;/h3>
&lt;pre class="mermaid">flowchart TD
START[&amp;#34;클러스터 시작&amp;lt;br/&amp;gt;runCluster.ts 실행&amp;#34;] --&amp;gt; SPAWN[&amp;#34;3개 노드 프로세스 spawn&amp;lt;br/&amp;gt;(child_process.spawn)&amp;#34;]
SPAWN --&amp;gt; NODE1[&amp;#34;노드 1: Express 서버 시작&amp;lt;br/&amp;gt;랜덤 딜레이 (1.5초)&amp;#34;]
SPAWN --&amp;gt; NODE2[&amp;#34;노드 2: Express 서버 시작&amp;lt;br/&amp;gt;랜덤 딜레이 (2.1초)&amp;#34;]
SPAWN --&amp;gt; NODE3[&amp;#34;노드 3: Express 서버 시작&amp;lt;br/&amp;gt;랜덤 딜레이 (1.2초)&amp;#34;]
NODE1 --&amp;gt; TIMEOUT1[&amp;#34;resetElectionTimeout()&amp;lt;br/&amp;gt;타임아웃: 4.1초&amp;#34;]
NODE2 --&amp;gt; TIMEOUT2[&amp;#34;resetElectionTimeout()&amp;lt;br/&amp;gt;타임아웃: 3.5초&amp;#34;]
NODE3 --&amp;gt; TIMEOUT3[&amp;#34;resetElectionTimeout()&amp;lt;br/&amp;gt;타임아웃: 4.2초&amp;#34;]
NODE3 --&amp;gt; FIRST[&amp;#34;딜레이 만료 (1.2초)&amp;lt;br/&amp;gt;가장 먼저!&amp;#34;]
FIRST --&amp;gt; ELECTION1[&amp;#34;runElection()&amp;lt;br/&amp;gt;제안 번호 #6&amp;#34;]
ELECTION1 --&amp;gt; P1[&amp;#34;Phase 1: PREPARE(#6)&amp;lt;br/&amp;gt;→ 3개 약속&amp;#34;]
P1 --&amp;gt; P2[&amp;#34;Phase 2: ACCEPT(#6, val:3)&amp;lt;br/&amp;gt;→ 3개 수락&amp;#34;]
P2 --&amp;gt; LEADER1[&amp;#34;노드 3 리더 확정 👑&amp;lt;br/&amp;gt;electionTimer 중지&amp;lt;br/&amp;gt;startHeartbeat(6)&amp;#34;]
LEADER1 --&amp;gt; HB_LOOP[&amp;#34;하트비트 루프&amp;lt;br/&amp;gt;(1초마다)&amp;#34;]
HB_LOOP --&amp;gt; HB1[&amp;#34;노드 1 수신&amp;lt;br/&amp;gt;resetElectionTimeout()&amp;#34;]
HB_LOOP --&amp;gt; HB2[&amp;#34;노드 2 수신&amp;lt;br/&amp;gt;resetElectionTimeout()&amp;#34;]
HB1 --&amp;gt; STABLE[&amp;#34;안정 상태&amp;lt;br/&amp;gt;리더: 노드 3, 턴: 6&amp;#34;]
HB2 --&amp;gt; STABLE
STABLE --&amp;gt; KILL[&amp;#34;사용자: kill 3&amp;lt;br/&amp;gt;리더 프로세스 종료&amp;#34;]
KILL --&amp;gt; STOP_HB[&amp;#34;하트비트 중단&amp;#34;]
STOP_HB --&amp;gt; DETECT1[&amp;#34;노드 1: 하트비트 안 옴&amp;lt;br/&amp;gt;타임아웃 대기 (4.1초)&amp;#34;]
STOP_HB --&amp;gt; DETECT2[&amp;#34;노드 2: 하트비트 안 옴&amp;lt;br/&amp;gt;타임아웃 대기 (3.5초)&amp;#34;]
DETECT2 --&amp;gt; REELECTION[&amp;#34;노드 2 타임아웃 만료 (3.5초)&amp;lt;br/&amp;gt;accepted 상태 리셋&amp;lt;br/&amp;gt;runElection()&amp;#34;]
REELECTION --&amp;gt; RE_P1[&amp;#34;Phase 1: PREPARE(#11)&amp;lt;br/&amp;gt;→ 2개 약속 (노드 3 DOWN)&amp;#34;]
RE_P1 --&amp;gt; RE_P2[&amp;#34;Phase 2: ACCEPT(#11, val:2)&amp;lt;br/&amp;gt;→ 2개 수락&amp;#34;]
RE_P2 --&amp;gt; LEADER2[&amp;#34;노드 2 리더 확정 👑&amp;lt;br/&amp;gt;startHeartbeat(11)&amp;#34;]
LEADER2 --&amp;gt; HB_LOOP2[&amp;#34;하트비트 루프&amp;lt;br/&amp;gt;(1초마다)&amp;#34;]
HB_LOOP2 --&amp;gt; STABLE2[&amp;#34;안정 상태&amp;lt;br/&amp;gt;리더: 노드 2, 턴: 11&amp;#34;]
STABLE2 --&amp;gt; RESTART[&amp;#34;사용자: start 3&amp;lt;br/&amp;gt;새 프로세스 spawn&amp;#34;]
RESTART --&amp;gt; NODE3_NEW[&amp;#34;노드 3 재시작&amp;lt;br/&amp;gt;모든 상태 초기화&amp;lt;br/&amp;gt;랜덤 딜레이 (1.8초)&amp;#34;]
NODE3_NEW --&amp;gt; HB_RECEIVE[&amp;#34;하트비트 수신&amp;lt;br/&amp;gt;currentLeaderId=2&amp;lt;br/&amp;gt;currentTerm=11&amp;#34;]
HB_RECEIVE --&amp;gt; DELAY_EXPIRE[&amp;#34;딜레이 만료 (1.8초)&amp;lt;br/&amp;gt;currentLeaderId !== null&amp;lt;br/&amp;gt;→ 선출 안 함&amp;#34;]
DELAY_EXPIRE --&amp;gt; FOLLOWER[&amp;#34;노드 3 팔로워로 합류&amp;lt;br/&amp;gt;안정적으로 동작&amp;#34;]
FOLLOWER --&amp;gt; FINAL[&amp;#34;최종 클러스터 상태&amp;lt;br/&amp;gt;리더: 노드 2&amp;lt;br/&amp;gt;팔로워: 노드 1, 3&amp;#34;]
style START fill:#e0f0ff,stroke:#333
style LEADER1 fill:#ffd700,stroke:#333,stroke-width:3px
style LEADER2 fill:#ffd700,stroke:#333,stroke-width:3px
style KILL fill:#ffcccc,stroke:#333
style REELECTION fill:#ffddaa,stroke:#333
style FOLLOWER fill:#ccffcc,stroke:#333
style FINAL fill:#ccccff,stroke:#333,stroke-width:2px&lt;/pre>&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>요즘 개발자를 위한 시스템 설계 수업. 디렌드라 신하, 테자스 초프라 저. 길벗&lt;/li>
&lt;li>&lt;a href="https://www.ibm.com/history/700">https://www.ibm.com/history/700&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://setiathome.berkeley.edu/sah_graphics.php">https://setiathome.berkeley.edu/sah_graphics.php&lt;/a>&lt;/li>
&lt;li>CLAUDE&lt;/li>
&lt;li>Perpexity&lt;/li>
&lt;/ul></content></item><item><title>CommonJS와 ESM의 차이점</title><link>https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/</link><pubDate>Sat, 12 Jul 2025 01:07:40 +0000</pubDate><guid>https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/</guid><description>&lt;ol>
&lt;li>개요 2. CommonJS 3. ESM (ECMAScript Modules) 4. 핵심 차이점 정리 5. Tree-shaking 6. Node.js 22의 새로운 기능 7. 참고자료&lt;/li>
&lt;/ol></description><content>&lt;h2 id="-목차-table-of-contents">📋 목차 (Table of Contents)&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#%ea%b0%9c%ec%9a%94">개요&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#commonjs">CommonJS&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#commonjs-%ed%8a%b9%ec%a7%95">특징&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#commonjs-%eb%8f%99%ec%a0%81-%eb%a1%9c%eb%94%a9-%ec%98%88%ec%a0%9c">동적 로딩 예제&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#esm-ecmascript-modules">ESM (ECMAScript Modules)&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#esm-%ed%8a%b9%ec%a7%95">특징&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#esm-%ec%a0%95%ec%a0%81-%ec%9e%84%ed%8f%ac%ed%8a%b8-%ec%98%88%ec%a0%9c">정적 임포트 예제&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#esm-%eb%8f%99%ec%a0%81-%ec%9e%84%ed%8f%ac%ed%8a%b8-%ec%98%88%ec%a0%9c">동적 임포트 예제&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#%ed%95%b5%ec%8b%ac-%ec%b0%a8%ec%9d%b4%ec%a0%90-%ec%a0%95%eb%a6%ac">핵심 차이점 정리&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#tree-shaking">Tree-shaking&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#tree-shaking-%ea%b0%9c%eb%85%90">개념 설명&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#tree-shaking-%ec%8b%a4%ec%8a%b5-%ec%98%88%ec%a0%9c">실습 예제&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#%eb%b2%88%eb%93%a4-%ed%81%ac%ea%b8%b0-%eb%b9%84%ea%b5%90">번들 크기 비교&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#nodejs-22%ec%9d%98-%ec%83%88%eb%a1%9c%ec%9a%b4-%ea%b8%b0%eb%8a%a5">Node.js 22의 새로운 기능&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#requireing-synchronous-esm-graphs">require()ing Synchronous ESM Graphs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#%ec%a0%9c%ed%95%9c%ec%82%ac%ed%95%ad">제한사항&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#nodejs-22-%ec%8b%a4%ec%8a%b5-%ec%98%88%ec%a0%9c">실습 예제&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#%ec%8b%a4%ec%a0%9c-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8-%ec%a0%81%ec%9a%a9-%ec%82%ac%eb%a1%80">실제 프로젝트 적용 사례&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/commonjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/#%ec%b0%b8%ea%b3%a0%ec%9e%90%eb%a3%8c">참고자료&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="개요">개요&lt;/h2>
&lt;p>Javascript는 원래 브라우저에 동작을 구현하기 위한 스크립트 언어였습니다. 따라서 복잡한 설계가 들어가지 않았고, 2000년대 말에 Flash, Silverlight 등의 RIA 붐이 일어나기 전에는 웹 페이지를 구현하기 위해 5000줄이 넘어가는 스크립트를 작성하는 경우도 자주 볼 수 있었습니다.&lt;/p>
&lt;pre class="mermaid">timeline
title JavaScript 모듈 시스템의 발전사
1995 : JavaScript 탄생. 브라우저 스크립트 언어
2000s : 복잡한 웹 애플리케이션 등장. 모듈 시스템 부재로 인한 문제
2009 : Node.js &amp;amp; CommonJS 등장. 서버사이드 JavaScript 시대
2014 : Webpack 1.0 출시. 모듈 번들러 시대
2015 : ES6 &amp;amp; ESM 표준화. 공식 모듈 시스템 도입
2020s : 현대적 도구들. Vite, ESBuild
2024 : Node.js 22. require() ESM 지원&lt;/pre>&lt;p>그 후 CPU의 병렬 처리 능력 향상과 V8 엔진 등의 발전으로 Javascript가 처리할 수 있는 영역이 급속도로 넓어지면서 2009년에 Node.js가 발표되었고, Node.js가 2009년에 처음 발표될 때, CommonJS 모듈 시스템이 같이 공개되었습니다. 덩치가 큰 서버 어플리케이션을 만들 때는 필수적으로 코드를 여러 개의 모듈로 쪼개서 프로젝트를 구성해야 하는데 원래 Javascript에는 그런 개념이 없었기 때문이죠.&lt;/p>
&lt;p>그 후 Javascript는 아주 느린 속도로 현대적인 언어가 되어갔습니다. 그리고 2015년, ES6 표준과 함께 ESM(ECMAScript Module)이 도입되었습니다. 드디어 Javascript를 활용하는 환경 어디든(브라우저, 서버) 모두 공통적으로 활용할 수 있는 모듈 시스템이 생긴거죠.&lt;/p>
&lt;h2 id="commonjs">CommonJS&lt;/h2>
&lt;h3 id="commonjs-특징">CommonJS 특징&lt;/h3>
&lt;p>CommonJS는 module.exports를 통해 모듈의 기능을 외부로 내보내고, require()를 통해 다른 모듈을 임포트 합니다. require()를 통한 임포트는 다음과 같은 특성이 있습니다.&lt;/p>
&lt;ul>
&lt;li>모듈 로드는 동기적(synchronous)으로 이루어집니다. 따라서 require()를 통히 모듈 임포트가 모두 끝나기 전에는 코드 실행이 중지됩니다.&lt;/li>
&lt;li>어떤 모듈을 로드할지 컴파일 타임에는 알 수 없으며, 코드가 실행되는 런타임에 동적(dynamic)으로 결정됩니다. 따라서 webpack과 같은 번들러는 어떤 모듈이 실제로 로드될지 알 수 없으므로 모듈의 일부만 사용되더라도 전체 모듈을 번들링해야 합니다. 즉, tree-shaking이 불가능합니다.&lt;/li>
&lt;/ul>
&lt;p>즉, 모듈을 동기적(synchronous)으로 로드하지만, 실제로 어떤 모듈이 로드될지 컴파일 시점에는 알 수 없는 동적(dynamic) 로딩이 진행된다는 겁니다. 다음 예제를 볼까요?&lt;/p>
&lt;h3 id="commonjs-동적-로딩-예제">CommonJS 동적 로딩 예제&lt;/h3>
&lt;p>우선 모듈을 2개 만들어 줍니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/math-utils.cjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;math-utils.js 로딩 중&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">subtract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">add&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">subtract&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/string-utils.cjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;string-utils.js 로딩 중&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">charAt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toUpperCase&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">truncate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">truncate&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>각각 간단한 함수 몇 가지를 선언하고, module.exports를 통해 외부로 내보내고 있습니다. 파일의 확장자가 &lt;code>cjs&lt;/code>인 이유는 이 파일이 CommonJS 모듈 시스템을 사용하는 파일이라는 것을 명시하기 위해서입니다.&lt;/p>
&lt;p>이제 런타임에 모듈을 로드하는 코드를 작성해 보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/commonjs-runtime.cjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== CommonJS 런타임 해석 데모 ===&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 사용자의 입력을 받습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">userChoice&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`사용자가 선택한 값: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">userChoice&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 런타임에 로드할 모듈을 결정 - 사용자의 입력에 따라 어떤 모듈을 로드할지 결정합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;math&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (1) 사용자가 math를 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;런타임에 수학 유틸리티 로딩 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (2) 사용자가 string을 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;런타임에 문자열 유틸리티 로딩 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./string-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (3) 사용자가 값을 입력하지 않은 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;런타임에 두 유틸리티 모두 로딩 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">mathUtils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">stringUtils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./string-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">mathUtils&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">stringUtils&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 런타임에 동적으로 로드된 모듈을 사용합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;math&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (1) 사용자가 math를 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (2) 사용자가 string을 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello world&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (3) 사용자가 값을 입력하지 않은 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello world&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 또 다른 예시: 함수 내에서 조건부 require를 사용합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="nx">getFormatterForType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">type&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">type&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;number&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./string-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n함수 내에서 동적 로딩:&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">formatter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">getFormatterForType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;포맷된 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">formatter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;dynamic loading&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>코드가 좀 복잡해보이지만 위에서 부터 천천히 확인해보면 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>사용자의 입력을 파라미터로 받습니다.&lt;/li>
&lt;li>입력에 따라 어떤 모듈을 로드할지 결정합니다.&lt;/li>
&lt;li>로드된 모듈을 통해 결과를 출력합니다.&lt;/li>
&lt;li>함수 내에서 조건부로 모듈을 로드할 수도 있습니다.&lt;/li>
&lt;/ol>
&lt;p>이제 코드를 실행해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node resolution-time/commonjs-runtime.cjs &lt;span class="nv">math&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> CommonJS 런타임 해석 &lt;span class="nv">데모&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">사용자가 선택한 값: math
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">런타임에 수학 유틸리티 로딩 중...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">math-utils.js 로딩 중
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">수학 결과: &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">함수 내에서 동적 로딩:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">string-utils.js 로딩 중
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">포맷된 결과: Dynamic loading
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>파라미터로 math를 입력하자 &lt;code>math-utils.cjs&lt;/code> 모듈만 로드되었고, 이후 함수 내에서 동적으로 &lt;code>string-utils.cjs&lt;/code> 모듈을 로드하는 코드가 실행되었습니다.&lt;/p>
&lt;h2 id="esm-ecmascript-modules">ESM (ECMAScript Modules)&lt;/h2>
&lt;h3 id="esm-특징">ESM 특징&lt;/h3>
&lt;p>ESM은 모듈의 기능을 export 키워드를 통해 외부로 내보내고, import 키워드를 통해 다른 모듈을 임포트 합니다. import 키워드를 통한 임포트는 다음과 같은 특성이 있습니다.&lt;/p>
&lt;ul>
&lt;li>모듈의 첫 번째 코드가 실행되기 전에 모든 import가 진행됩니다. 따라서 어떤 모듈을 로드하는지 정적(static)으로 결정되므로, 실제로 로드되는 항목만 번들링하는 tree-shaking이 가능합니다.&lt;/li>
&lt;li>비동기적(asynchronous)으로 모듈을 로드할 수 있습니다. 따라서 모듈을 로드하는 동안 중단없이 코드를 실행할 수도 있습니다.&lt;/li>
&lt;/ul>
&lt;p>즉, 모듈을 비동기적(asynchronous)으로 로드하고, 어떤 모듈을 로드할지 정적(static)으로 결정되므로, 실제로 로드되는 항목만 번들링하는 tree-shaking이 가능하다는 겁니다. 다음 예제를 볼까요?&lt;/p>
&lt;h3 id="esm-정적-임포트-예제">ESM 정적 임포트 예제&lt;/h3>
&lt;p>이번에도 모듈을 2개 먼저 선언하겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/math-utils.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;math-utils.mjs 로딩 중&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">subtract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/string-utils.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;string-utils.mjs 로딩 중&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">charAt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toUpperCase&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">truncate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>각각의 함수를 export 키워드를 통해 외부로 내보내고 있습니다. 파일의 확장자가 &lt;code>mjs&lt;/code>인 이유는 이 파일이 ESM 모듈 시스템을 사용하는 파일이라는 것을 명시하기 위해서입니다.&lt;/p>
&lt;p>이제 런타임에 모듈을 로드하는 코드를 작성해 보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/esm-static.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// esm-static.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== ESM 정적 해석 데모 ===&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ESM을 임포트는 컴파일 타임에 로드할 모듈이 결정됩니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">multiply&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s2">&amp;#34;./math-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">reverse&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s2">&amp;#34;./string-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;모든 임포트가 컴파일 타임에 해석되었습니다!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 사용자의 입력을 받습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">userChoice&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`사용자가 선택한 값: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">userChoice&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 모듈이 이미 로드되어 있으므로, 사용할 함수를 선택합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;math&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (1) 사용자가 math를 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (2) 사용자가 string을 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello world&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello world&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>코드는 다음과 같은 동작을 수행합니다.&lt;/p>
&lt;ul>
&lt;li>모듈에서 사용할 함수만 선택적으로 임포트합니다.&lt;/li>
&lt;li>사용자의 입력을 받습니다.&lt;/li>
&lt;li>사용자의 입력에 따라 로드된 모듈의 함수를 사용합니다.&lt;/li>
&lt;/ul>
&lt;p>코드를 실행해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node resolution-time/esm-static.mjs math
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">math-utils.mjs 로딩 중
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">string-utils.mjs 로딩 &lt;span class="nv">중&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> ESM 정적 해석 &lt;span class="nv">데모&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">모든 임포트가 컴파일 타임에 해석되었습니다!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">사용자가 선택한 값: math
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">수학 결과: &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">수학 결과: &lt;span class="m">28&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>앞서 설명 드린 대로, 첫 번째 코드가 실행되기 전에 모듈의 임포트가 진행되었습니다. 코드를 실행하지 않아도 어떤 모듈이 로드될지 정적(static)으로 알 수 있다는 거죠!&lt;/p>
&lt;h3 id="esm-동적-임포트-예제">ESM 동적 임포트 예제&lt;/h3>
&lt;p>자 그러면, 앞선 CommonJS 예제 처럼 동적으로 모듈을 로드해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// resolution-time/esm-dynamic.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// esm-dynamic.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== ESM 동적 임포트 데모 ===&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 사용자의 입력을 받습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">userChoice&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`사용자가 선택한 값: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">userChoice&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ESM에서 동적으로 모듈을 로드합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;math&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (1) 사용자가 math를 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 유틸리티를 동적으로 임포트 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (2) 사용자가 string을 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 유틸리티를 동적으로 임포트 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./string-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (3) 사용자가 값을 입력하지 않은 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;두 유틸리티를 모두 동적으로 임포트 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">mathUtils&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">stringUtils&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nb">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">all&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./string-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">mathUtils&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">stringUtils&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 런타임에 동적으로 로드된 모듈을 사용합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;math&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (1) 사용자가 math를 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">userChoice&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (2) 사용자가 string을 선택한 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello world&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (3) 사용자가 값을 입력하지 않은 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;수학 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문자열 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello world&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 함수 내에서 동적으로 모듈을 로드합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">getFormatterForType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">type&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">type&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;number&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./string-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n함수 내에서 동적 로딩:&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">formatter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">getFormatterForType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;포맷된 결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">formatter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;dynamic loading&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>사용자의 입력에 따라 동적으로 async import를 수행하고 있습니다. 결과를 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> ESM 동적 임포트 &lt;span class="nv">데모&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">사용자가 선택한 값: math
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">수학 유틸리티를 동적으로 임포트 중...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">math-utils.mjs 로딩 중
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">수학 결과: &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">함수 내에서 동적 로딩:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">string-utils.mjs 로딩 중
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">포맷된 결과: Dynamic loading
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>앞선 CommonJS 예제와 동일한 결과를 확인할 수 있습니다.&lt;/p>
&lt;h2 id="핵심-차이점-정리">핵심 차이점 정리&lt;/h2>
&lt;h3 id="-commonjs-vs-esm-비교표">📊 CommonJS vs ESM 비교표&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>특성&lt;/th>
&lt;th>CommonJS&lt;/th>
&lt;th>ESM&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>문법&lt;/strong>&lt;/td>
&lt;td>&lt;code>require()&lt;/code> / &lt;code>module.exports&lt;/code>&lt;/td>
&lt;td>&lt;code>import&lt;/code> / &lt;code>export&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>로딩 방식&lt;/strong>&lt;/td>
&lt;td>동기적 (Synchronous)&lt;/td>
&lt;td>비동기적 (Asynchronous)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>해석 시점&lt;/strong>&lt;/td>
&lt;td>런타임 (Dynamic)&lt;/td>
&lt;td>컴파일 타임 (Static)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tree-shaking&lt;/strong>&lt;/td>
&lt;td>❌ 불가능&lt;/td>
&lt;td>✅ 가능&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>브라우저 지원&lt;/strong>&lt;/td>
&lt;td>❌ 번들러 필요&lt;/td>
&lt;td>✅ 네이티브 지원&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Node.js 지원&lt;/strong>&lt;/td>
&lt;td>✅ 기본 지원&lt;/td>
&lt;td>✅ 지원 (v14+)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>파일 확장자&lt;/strong>&lt;/td>
&lt;td>&lt;code>.js&lt;/code>, &lt;code>.cjs&lt;/code>&lt;/td>
&lt;td>&lt;code>.js&lt;/code>, &lt;code>.mjs&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="-모듈-로딩-프로세스-비교">🔄 모듈 로딩 프로세스 비교&lt;/h3>
&lt;pre class="mermaid">graph TB
subgraph &amp;#34;CommonJS 로딩 프로세스&amp;#34;
A1[코드 실행 시작] --&amp;gt; B1[require 호출]
B1 --&amp;gt; C1[런타임에 모듈 경로 해석]
C1 --&amp;gt; D1[모듈 파일 로드]
D1 --&amp;gt; E1[모듈 코드 실행]
E1 --&amp;gt; F1[module.exports 반환]
F1 --&amp;gt; G1[다음 코드 실행]
end&lt;/pre>&lt;pre class="mermaid">graph TB
subgraph &amp;#34;ESM 로딩 프로세스&amp;#34;
A2[파싱 단계] --&amp;gt; B2[import 문 발견]
B2 --&amp;gt; C2[컴파일 타임에 모듈 그래프 구성]
C2 --&amp;gt; D2[모든 모듈 비동기 로드]
D2 --&amp;gt; E2[모듈 평가 및 바인딩]
E2 --&amp;gt; F2[코드 실행 시작]
end&lt;/pre>&lt;h3 id="핵심-요약">핵심 요약&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>CommonJS&lt;/strong>: 모듈을 동기적(synchronous)으로 로드하지만, 실제로 어떤 모듈이 로드될지 컴파일 시점에는 알 수 없는 동적(dynamic) 로딩이 진행됩니다.&lt;/li>
&lt;li>&lt;strong>ESM&lt;/strong>: 모듈을 비동기적(asynchronous)으로 로드하고, 어떤 모듈을 로드할지 정적(static)으로 결정되므로, 실제로 로드되는 항목만 번들링하는 tree-shaking이 가능합니다.&lt;/li>
&lt;/ul>
&lt;h2 id="tree-shaking">Tree-shaking&lt;/h2>
&lt;h3 id="tree-shaking-개념">Tree-shaking 개념&lt;/h3>
&lt;p>Tree-shaking은 배포를 위해 코드를 번들링할 때, 모듈 시스템에서 사용되지 않는 모듈을 제거하는 기능입니다. 즉, 사용하지 않는 모듈을 결과물에서 제거하여 번들 파일의 크기를 줄입니다. 번들 파일의 크기가 줄어들면 로드하는 데 걸리는 시간이 줄어들고 메모리 역시 절약할 수 있겠죠. 프로젝트가 작을 때는 티가 안나지만, 프로젝트의 크기가 커질수록 매우 큰 차이를 보입니다.&lt;/p>
&lt;h3 id="tree-shaking-실습-예제">Tree-shaking 실습 예제&lt;/h3>
&lt;p>에제로 확인해보죠! 다음과 같이 package.json을 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//tree-shaking/package.json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tree-shaking-demo&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Demonstrates tree-shaking differences between CommonJS and ESM&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;module&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;build&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;webpack&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;test-commonjs&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;node commonjs-consumer.js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;test-esm&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;node esm-consumer.mjs&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;test-esm-multiple&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;node esm-consumer-multiple.mjs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;devDependencies&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;webpack&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;^5.89.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;webpack-cli&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;^5.1.4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>npm install&lt;/code> 명령으로 패키지를 설치하고, 다음과 같이 webpack을 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//tree-shaking/webpack.config.cjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// tree-shaking을 포함한 최적화 활성화
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">entry&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;commonjs-bundle&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;./commonjs-consumer.cjs&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;esm-bundle&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;./esm-consumer.mjs&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">output&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">path&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">__dirname&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;dist&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">filename&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;[name].js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clean&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">optimization&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">usedExports&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// tree-shaking 활성화
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">sideEffects&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resolve&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">extensions&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;.js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;.mjs&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">module&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rules&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">test&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="sr">/\.mjs$/&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;javascript/esm&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 webpack을 통해 번들링할 &lt;code>commonjs-consumer.cjs&lt;/code>와 &lt;code>esm-consumer.mjs&lt;/code> 파일, 그리고 각 파일에서 임포트할 유틸 모듈을 작성합니다.&lt;/p>
&lt;p>우선 CommonJS 파일들 부터 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tree-shaking/large-utils.cjs (CommonJS)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;large-utils.js 로딩 중 - 모든 함수가 로드됩니다!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 수학 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;add 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">subtract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;subtract 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;multiply 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">divide&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;divide 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">power&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;power 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 문자열 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;capitalize 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">charAt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toUpperCase&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;reverse 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">truncate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;truncate 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 배열 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="nx">sortArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sortArray 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[...&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">sort&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">filterEven&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;filterEven 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">num&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">num&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sum 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reduce&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">total&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">num&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 복잡한 유틸리티 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="nx">complexCalculation&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;complexCalculation 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1000000&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">result&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sqrt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">heavyStringProcessing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;heavyStringProcessing 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 모든 함수 내보내기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">add&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">subtract&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">divide&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">power&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">truncate&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sortArray&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">filterEven&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sum&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">complexCalculation&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">heavyStringProcessing&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tree-shaking/commonjs-consumer.cjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== CommonJS Consumer ===&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;large-utils.js에서 임포트 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// CommonJS는 전체 모듈을 임포트합니다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">utils&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./large-utils.cjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\nadd 함수만 사용:&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">utils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n주의: &amp;#39;add&amp;#39;만 사용하지만, 모든 함수가 로드됩니다!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;이는 CommonJS가 전체 모듈 네임스페이스를 로드하기 때문입니다.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>다음으로 ESM 파일들을 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tree-shaking/large-utils.mjs (ESM)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;large-utils.mjs 로딩 중&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 수학 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;add 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">subtract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;subtract 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;multiply 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">divide&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;divide 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">power&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;power 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 문자열 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">capitalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;capitalize 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">charAt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toUpperCase&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;reverse 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">truncate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;truncate 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 배열 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">sortArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sortArray 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[...&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">sort&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">filterEven&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;filterEven 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">num&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">num&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">arr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sum 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">reduce&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">total&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">num&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 복잡한 유틸리티 함수들
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">complexCalculation&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;complexCalculation 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1000000&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">result&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sqrt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">heavyStringProcessing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;heavyStringProcessing 함수 호출됨&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tree-shaking/esm-consumer.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== ESM Consumer ===&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;large-utils.mjs에서 임포트 중...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ESM은 특정 함수만 임포트합니다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">add&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s2">&amp;#34;./large-utils.mjs&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\nadd 함수만 사용:&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;결과:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n주의: &amp;#39;add&amp;#39; 함수만 임포트됩니다!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;다른 함수들은 이 모듈의 범위에 포함되지 않습니다.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ESM의 경우 CommonJS와 다르게 &lt;code>add&lt;/code> 함수만 임포트하고 있습니다. 이 코드를 빌드하고, 빌드된 파일의 크기를 확인해보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npm run build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tree-shaking-demo@1.0.0 build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; webpack
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">asset commonjs-bundle.js 1.79 KiB &lt;span class="o">[&lt;/span>emitted&lt;span class="o">]&lt;/span> &lt;span class="o">[&lt;/span>minimized&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>name: commonjs-bundle&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">asset esm-bundle.js &lt;span class="m">404&lt;/span> bytes &lt;span class="o">[&lt;/span>emitted&lt;span class="o">]&lt;/span> &lt;span class="o">[&lt;/span>minimized&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>name: esm-bundle&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">orphan modules 1.73 KiB &lt;span class="o">[&lt;/span>orphan&lt;span class="o">]&lt;/span> &lt;span class="m">1&lt;/span> module
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">modules by path ./*.cjs 2.41 KiB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./commonjs-consumer.cjs &lt;span class="m">528&lt;/span> bytes &lt;span class="o">[&lt;/span>built&lt;span class="o">]&lt;/span> &lt;span class="o">[&lt;/span>code generated&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ./large-utils.cjs 1.89 KiB &lt;span class="o">[&lt;/span>built&lt;span class="o">]&lt;/span> &lt;span class="o">[&lt;/span>code generated&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./esm-consumer.mjs + &lt;span class="m">1&lt;/span> modules 2.14 KiB &lt;span class="o">[&lt;/span>built&lt;span class="o">]&lt;/span> &lt;span class="o">[&lt;/span>code generated&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">webpack 5.100.0 compiled successfully in &lt;span class="m">216&lt;/span> ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; ls -lh ./dist
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">total &lt;span class="m">16&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--@ &lt;span class="m">1&lt;/span> onlifecoding staff 1.8K Jul &lt;span class="m">12&lt;/span> 09:26 commonjs-bundle.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-rw-r--r--@ &lt;span class="m">1&lt;/span> onlifecoding staff 404B Jul &lt;span class="m">12&lt;/span> 09:26 esm-bundle.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="-번들-크기-비교">📊 번들 크기 비교&lt;/h3>
&lt;p>빌드된 파일 크기에 확인할 수 있듯이 esm 번들은 404B로 1.8KB에 비해 약 22% 수준입니다.&lt;/p>
&lt;pre class="mermaid">xychart-beta
title &amp;#34;번들 크기 비교 (Bytes)&amp;#34;
x-axis [CommonJS, ESM]
y-axis &amp;#34;크기 (Bytes)&amp;#34; 0 --&amp;gt; 2000
bar [1792, 404]&lt;/pre>&lt;p>&lt;strong>크기 절약 효과:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>CommonJS 번들: 1,792 bytes (100%)&lt;/li>
&lt;li>ESM 번들: 404 bytes (22.5%)&lt;/li>
&lt;li>&lt;strong>절약된 크기: 1,388 bytes (77.5% 감소!)&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>이렇게 간단한 예제에서도 tree-shaking을 통해 번들 파일의 크기를 꽤 줄일 수 있었는데 큰 프로젝트에서는 더 큰 차이가 나겠죠?&lt;/p>
&lt;h3 id="-tree-shaking-작동-원리">🌳 Tree-shaking 작동 원리&lt;/h3>
&lt;pre class="mermaid">graph TB
subgraph &amp;#34;CommonJS (Tree-shaking 불가)&amp;#34;
A1[large-utils.cjs] --&amp;gt; B1[모든 함수 포함]
B1 --&amp;gt; C1[add, subtract, multiply, divide, ...]
C1 --&amp;gt; D1[번들: 1.8KB]
D1 --&amp;gt; E1[사용하지 않는 함수도 포함]
end&lt;/pre>&lt;pre class="mermaid">graph TB
subgraph &amp;#34;ESM (Tree-shaking 가능)&amp;#34;
A2[large-utils.mjs] --&amp;gt; B2[정적 분석]
B2 --&amp;gt; C2[사용되는 함수만 선택]
C2 --&amp;gt; D2[add 함수만]
D2 --&amp;gt; E2[번들: 404B]
E2 --&amp;gt; F2[78% 크기 감소!]
end&lt;/pre>&lt;h2 id="nodejs-22의-새로운-기능">Node.js 22의 새로운 기능&lt;/h2>
&lt;h3 id="requireing-synchronous-esm-graphs">require()ing Synchronous ESM Graphs&lt;/h3>
&lt;p>ESM이 가야할 방향인 건 분명하지만 문제는 ESM이 표준으로 제시되기까지 너무 오랜 시간이 걸렸고, CommonJS를 사용하는 레거시 코드가 너무나도 많다는 겁니다.&lt;/p>
&lt;p>그러면 핵심 라이브러리가 ESM만을 지원하도록 변경된다면 어떻게 해야 될까요? 프로젝트를 ESM으로 마이그레이션 해야겠지만, 이건 그렇게 쉬운 일이 아닙니다. 그래서 Node.js 22에서는 &lt;code>require()ing Synchronous ESM Graphs&lt;/code>라는 기능을 제공합니다.&lt;/p>
&lt;p>즉, 동기적으로 해석가능한 ESM 그래프를 require를 통해 임포트할 수 있게 해주겠다는 겁니다! 이 기능을 사용하면 레거시 코드를 최소한으로 변경하면서 ESM으로 마이그레이션 할 수 있게 됩니다.&lt;/p>
&lt;h3 id="제한사항">제한사항&lt;/h3>
&lt;p>주의할 점이 있다면 &lt;code>동기적으로 해석가능한&lt;/code>에 주목해야 합니다. 만약 임포트해야 할 모듈에서 top-level await를 사용하고 있다면 이 기능을 사용할 수 없습니다.&lt;/p>
&lt;h3 id="-nodejs-22-esm-require-결정-트리">🔀 Node.js 22 ESM require() 결정 트리&lt;/h3>
&lt;pre class="mermaid">flowchart TD
A[ESM 모듈을 require로 임포트] --&amp;gt; B{Node.js 22+?}
B --&amp;gt;|No| C[❌ 불가능&amp;lt;br/&amp;gt;ESM을 require할 수 없음]
B --&amp;gt;|Yes| D{top-level await 사용?}
D --&amp;gt;|Yes| E[❌ 불가능&amp;lt;br/&amp;gt;ERR_REQUIRE_ASYNC_MODULE]
D --&amp;gt;|No| F[✅ 가능&amp;lt;br/&amp;gt;require로 ESM 임포트]
E --&amp;gt; G[대안: import 사용]
F --&amp;gt; H[레거시 코드 마이그레이션 용이]
style C fill:#ffcccc
style E fill:#ffcccc
style F fill:#ccffcc
style G fill:#ffffcc
style H fill:#ccffcc&lt;/pre>&lt;h3 id="nodejs-22-실습-예제">Node.js 22 실습 예제&lt;/h3>
&lt;p>그럼 예시를 볼까요?&lt;/p>
&lt;p>우선 top-level await를 사용하는 ESM 모듈과 사용하지 않는 ESM 모듈을 작성해 보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// require-esm/async-math.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;async-math.mjs 로딩 중 (최상위 await가 있는 ESM)&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 이 최상위 await는 require()가 작동하지 않게 합니다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nb">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">multiplier&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">calculate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">y&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">multiplier&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;1.0.0&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// require-esm/math.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;math.mjs 로딩 중 (ESM)&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">PI&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">3.14159&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">calculate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">PI&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 모듈을 로드해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// require-esm/test-node22-feature.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== Node.js 22 require(ESM) 기능 테스트 ===&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Node.js 버전:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 기능이 사용 가능한지 확인
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">features&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">features&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">require_module&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;✅ require(ESM) 기능이 사용 가능합니다&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;❌ require(ESM) 기능이 사용 불가능합니다&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;다음과 같은 이유일 수 있습니다:&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;1. Node.js 22+ 버전을 사용하지 않는 경우&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2. --experimental-require-module 플래그를 활성화해야 하는 경우&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;=&amp;#34;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 테스트 1: 일반 CommonJS require
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n테스트 1: 일반 CommonJS 모듈&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">regularModule&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./math.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;✅ ESM 모듈 require 성공&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;결과 타입:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">regularModule&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;사용 가능한 익스포트:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">regularModule&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;❌ ESM 모듈 require 실패&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;오류:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">code&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;=&amp;#34;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 테스트 2: 최상위 await가 있는 ESM
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n테스트 2: 최상위 await가 있는 ESM&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">asyncModule&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./async-math.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;✅ 이것은 작동하지 않아야 합니다!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;❌ 예상된 실패 - 최상위 await가 있는 ESM은 require할 수 없습니다&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;오류:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">code&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;=&amp;#34;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 테스트 3: ESM import
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;\n테스트 3: 최상위 await가 있는 ESM import&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">asyncModule&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./async-math.mjs&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;✅ ESM 모듈 import 성공&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;결과 타입:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">asyncModule&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;사용 가능한 익스포트:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">asyncModule&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;❌ 비동기 import로 ESM import 실패&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;오류:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">code&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 코드를 다음과 같이 진행됩니다.&lt;/p>
&lt;ul>
&lt;li>먼저 노드 버전을 통해 동기적으로 해석가능한 ESM 그래프가 require()를 통해 임포트 가능한지 확인합니다.&lt;/li>
&lt;li>top-level await를 사용하지 않는 ESM 모듈을 require()를 통해 임포트 합니다. 임포트가 성공해야 합니다.&lt;/li>
&lt;li>top-level await를 사용하는 ESM 모듈을 require()를 통해 임포트 합니다. 임포트가 실패해야 합니다.&lt;/li>
&lt;li>비동기 import를 통해 top-level await를 사용하는 ESM 모듈을 임포트 합니다. 임포트가 성공해야 합니다.&lt;/li>
&lt;/ul>
&lt;p>결과를 확인해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node require-esm/test-node22-feature.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> Node.js &lt;span class="m">22&lt;/span> require&lt;span class="o">(&lt;/span>ESM&lt;span class="o">)&lt;/span> 기능 &lt;span class="nv">테스트&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Node.js 버전: v22.14.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ require&lt;span class="o">(&lt;/span>ESM&lt;span class="o">)&lt;/span> 기능이 사용 &lt;span class="nv">가능합니다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">==================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">테스트 1: 일반 CommonJS 모듈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">math.mjs 로딩 중 &lt;span class="o">(&lt;/span>ESM&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ ESM 모듈 require 성공
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">결과 타입: object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">사용 가능한 익스포트: &lt;span class="o">[&lt;/span> &lt;span class="s1">&amp;#39;PI&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;__esModule&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;add&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;default&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;multiply&amp;#39;&lt;/span> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">==================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">테스트 2: 최상위 await가 있는 ESM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 예상된 실패 - 최상위 await가 있는 ESM은 require할 수 없습니다
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">오류: &lt;span class="nv">ERR_REQUIRE_ASYNC_MODULE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">==================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">테스트 3: 최상위 await가 있는 ESM import
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">async-math.mjs 로딩 중 &lt;span class="o">(&lt;/span>최상위 await가 있는 ESM&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ ESM 모듈 import 성공
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">결과 타입: object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">사용 가능한 익스포트: &lt;span class="o">[&lt;/span> &lt;span class="s1">&amp;#39;calculate&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;version&amp;#39;&lt;/span> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>앞서 설명 드렸던 대로 top-level await를 사용하지 않는 ESM 모듈은 require()를 통해 임포트가 가능합니다. 하지만 top-level await를 사용하는 ESM 모듈은 require()로 임포트할 수 없으므로 async import를 사용해야 합니다.&lt;/p>
&lt;h3 id="실제-프로젝트-적용-사례">실제 프로젝트 적용 사례&lt;/h3>
&lt;p>제가 개발 중 실제로 겪었던 사례를 소개해드리겠습니다.&lt;/p>
&lt;h4 id="-문제-상황-esm-전용-라이브러리-사용-시-require-불가능">🚨 문제 상황: ESM 전용 라이브러리 사용 시 require() 불가능&lt;/h4>
&lt;p>기존 기능을 k8s 기반으로 전환하기 위해 프로토타이핑을 진행하고 있었습니다. 그런데 코드를 실행하자 다음과 같은 에러가 발생했습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">⠋ TSC Initializing &lt;span class="nb">type&lt;/span> checker.../dist/some/services/kubernetes-job.service.js:315
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mountPath: &lt;span class="s1">&amp;#39;/workspace&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ^
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Error &lt;span class="o">[&lt;/span>ERR_REQUIRE_ESM&lt;span class="o">]&lt;/span>: require&lt;span class="o">()&lt;/span> of ES Module /node_modules/@kubernetes/client-node/dist/index.js from /some/services/kubernetes-job.service.js not supported.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Instead change the require of index.js in /dist/some/services/kubernetes-job.service.js to a dynamic import&lt;span class="o">()&lt;/span> which is available in all CommonJS modules.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at TracingChannel.traceSync &lt;span class="o">(&lt;/span>node:diagnostics_channel:315:14&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Object.&amp;lt;anonymous&amp;gt; &lt;span class="o">(&lt;/span>/dist/some/services/kubernetes-job.service.js:12:61&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> code: &lt;span class="s1">&amp;#39;ERR_REQUIRE_ESM&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>문제를 찾다보니, tsconfig.json 설정이 &lt;code>&amp;quot;module&amp;quot;: &amp;quot;commonjs&amp;quot;&lt;/code> 였고 &lt;a href="https://github.com/kubernetes-client/javascript/releases/tag/1.0.0">kubernetes-client 라이브러리가 1.0 버전 부터 ESM 전용으로 변경&lt;/a> 되었기 때문이었습니다! 게다가 node.js 버전 역시 22.11.0 이었습니다. require(esm)은 &lt;a href="https://github.com/nodejs/node/releases/tag/v22.12.0">node.js 22.12.0 부터 기본적으로 활성화&lt;/a> 되었으며, node.js 22.11.0 이하에서는 &lt;code>--experimental-require-module&lt;/code> 플래그를 추가해야 합니다.&lt;/p>
&lt;h4 id="-상황-분석">📋 상황 분석&lt;/h4>
&lt;pre class="mermaid">graph TB
subgraph &amp;#34;문제 발생 환경&amp;#34;
A[TypeScript 프로젝트] --&amp;gt; B[tsconfig.json&amp;lt;br/&amp;gt;module: commonjs]
B --&amp;gt; C[빌드된 JS&amp;lt;br/&amp;gt;CommonJS 형태]
C --&amp;gt; D[kubernetes/client-node&amp;lt;br/&amp;gt;1.0.0+ ESM 전용]
D --&amp;gt; E[❌ ERR_REQUIRE_ESM]
end&lt;/pre>&lt;pre class="mermaid">graph TB
subgraph &amp;#34;라이브러리 변경사항&amp;#34;
F[kubernetes/client-node 0.x] --&amp;gt; G[CommonJS 지원]
F --&amp;gt; H[kubernetes/client-node 1.0+] --&amp;gt; I[ESM 전용]
I --&amp;gt; J[require 불가능]
end&lt;/pre>&lt;p>&lt;strong>핵심 문제:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>TypeScript 설정&lt;/strong>: &lt;code>tsconfig.json&lt;/code>의 &lt;code>module&lt;/code> 설정이 &lt;code>&amp;quot;commonjs&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>라이브러리 변경&lt;/strong>: &lt;code>@kubernetes/client-node&lt;/code> 1.0.0부터 ESM 전용 (&lt;a href="https://github.com/kubernetes-client/javascript/releases/tag/1.0.0">릴리즈 노트&lt;/a>)&lt;/li>
&lt;li>&lt;strong>Node.js 버전&lt;/strong>: 개발 환경의 Node.js 22.11.0에서는 기본적으로 require(ESM) 미지원. &lt;code>--experimental-require-module&lt;/code> 플래그를 추가해야 합니다.&lt;/li>
&lt;/ul>
&lt;h4 id="-해결-방법">💡 해결 방법&lt;/h4>
&lt;p>일단 가장 간단한 해결책으로 다음과 같이 &lt;code>--experimental-require-module&lt;/code> 플래그를 추가했습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// package.json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;start:dev&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NODE_OPTIONS=&amp;#39;--experimental-require-module&amp;#39; nest start --watch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;start:prod&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NODE_OPTIONS=&amp;#39;--experimental-require-module&amp;#39; node dist/main.js&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 문제가 해결된 걸 확인하고, node.js 버전을 현재 최신 버전인 22.17.0 으로 업데이트 했습니다.&lt;/p>
&lt;hr>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nodesource.com/blog/enhancing-nodejs-esm-graphs">Enhancing Node.js Core: Introducing Support for Synchronous ESM Graphs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://nodejs.org/ko/blog/release/v22.12.0">Node v22.12.0 (LTS)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.appsignal.com/2024/12/11/a-deep-dive-into-commonjs-and-es-modules-in-nodejs.html">A Deep Dive Into CommonJS and ES Modules in Node.js&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://nodejs.org/api/modules.html">Node.js v24.4.0 documentation&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>patch-package로 패키지 커스터마이즈하기</title><link>https://netscout.github.io/posts/patch-package%EB%A1%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A6%88%ED%95%98%EA%B8%B0/</link><pubDate>Sat, 10 May 2025 08:45:21 +0900</pubDate><guid>https://netscout.github.io/posts/patch-package%EB%A1%9C-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A6%88%ED%95%98%EA%B8%B0/</guid><description>react와 같이 node.js 기반으로 개발하다보면, 여러 패키지를 설치해서 사용하게 됩니다. 그리고 그러다 보면, 기존 패키지의 코드를 수정해서 사용해야 하는 경우가 생깁니다! 이럴 때는 어떻게 해야 할까요?</description><content>&lt;p>react와 같이 node.js 기반으로 개발하다보면, 여러 패키지를 설치해서 사용하게 됩니다. 그리고 그러다 보면, 기존 패키지의 코드를 수정해서 사용해야 하는 경우가 생깁니다! 이럴 때는 어떻게 해야 할까요?&lt;/p>
&lt;h2 id="미련했던-옛-시절">미련했던 옛 시절&lt;/h2>
&lt;p>현재 작업 중인 프로젝트에서는 webrtc 기반으로 코드 에디터의 내용을 동기화 하는 기능을 구현했었습니다. 그러다 보니 오픈소스 패키지를 사용하다가 필요한 기능을 넣기 위해 패키지를 수정할 필요가 있었죠. 그래서 고민하다가&amp;hellip; node_modules 폴더에서 해당 패키지의 소스 코드를 찾아서 그걸 /src/lib 폴더에 복사해두고 직접 수정했습니다! 그리곤 설치된 패키지를 import 하는 대신에 /src/lib에 복사해서 수정한 파일을 import 하는 방식으로 구현했죠.&lt;/p>
&lt;p>물론 이 방법에도 장점은 있습니다. 간단하죠! 그리고 디버깅도 쉽습니다. 코드가 내 프로젝트 안에 있으니까요. 그런데 이 작업을 할 당시에도 &lt;code>&amp;quot;나중에 시간이 좀 지나서 패키지를 업데이트해야 되면, 일일이 변경점을 찾아서 수정해야 되나...?&amp;quot;&lt;/code> 하는 불안감은 있었지만 그런 걸 생각할 여유가 아예 없었기 때문에 그냥 그대로 뒀습니다.&lt;/p>
&lt;h2 id="기술적-부채는-결국-돌아온다">기술적 부채는 결국 돌아온다!&lt;/h2>
&lt;p>그렇게 대충 마무리를 하고 2년이 지났고, 그 프로젝트를 다시 들여다 보고 있었습니다. 어떻게든 회사가 죽지 않고 버티다 보면 신기하게도 이미 수명이 끝났다고 생각했던 솔루션이 다시 부활하게 되는 순간이 오곤 합니다. 네, 지금이 그런 순간이었던 거죠. 거의 포기했던 솔루션을 다시 봐야 하는 상황인거죠. 개발자라면 이때의 막막한 심정을 공감하실 겁니다. 본인이 막대한 노력을 들여서 일을 장황하게 벌여놨지만, 시간이 한참 지나서 그걸 다시 스스로 수습해야 하는 상황말이죠&amp;hellip;&lt;/p>
&lt;p>어쨌거나 그 당시에 넘쳐나던 요구사항을 쫓아가기 급급하면서 계속 새로운 기능을 공장처럼 찍어냈고, 여러가지 사정으로 인해 그 기능들은 제대로 테스트 과정을 거치지도 못한 채 방치되었습니다. 빚을 내면서 빨리 개발했지만, 그 빚을 갚지 못하면서 이자만 눈덩이 처럼 불어난 셈이죠. 패키지 파일을 /src/lib에 복사했던 건 수 많은 부채 중의 하나였습니다.&lt;/p>
&lt;h2 id="좀-더-우아한-방법이-없을까">좀 더 우아한 방법이 없을까?&lt;/h2>
&lt;p>이 문제에 대해 위대하신 &lt;code>ChatGPT&lt;/code>님과 대화하다 보니, 여러가지 방법이 있지만 가장 간단한 건 &lt;code>patch-package&lt;/code>를 활용하는 방법이라는 걸 알게되었습니다. 그러니까, &lt;code>node_modules/some-package/src/source.js&lt;/code> 파일을 직접 수정하고, &lt;code>npx patch-package some-package&lt;/code> 명령을 실행하면, 원래 패키지에서 변경된 점을 찾아서 &lt;code>patches&lt;/code> 폴더에 기록하고 패키지를 새롭게 빌드해줍니다. &lt;code>patches&lt;/code> 폴더에 기록된 내용을 들여다보면 대충 다음과 같은 내용입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gh">diff --git a/node_modules/some-package/src/source.js b/node_modules/some-package/src/source.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">index c0fc343..92a2941 100644
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>&lt;span class="gd">--- a/node_modules/some-package/src/source.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+++ b/node_modules/some-package/src/source.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>&lt;span class="gu">@@ -97,6 +97,7 @@ someArray[someIndex] = (
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // @todo - blah blah some original source code
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const someVariable = 30000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+const someOtherVariable = 4000;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>게다가! &lt;code>package.json&lt;/code>의 &lt;code>scripts&lt;/code>에 &lt;code>postinstall&lt;/code> 스크립트를 추가하면, 패키지를 설치할 때마다 자동으로 패치 파일을 적용해줍니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;postinstall&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;patch-package&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>패키지를 새로 설치할 때 마다 수작업으로 변경사항을 적용할 필요가 없는 거죠! 다만, patch 파일 이름(&lt;code>some-package+0.1.6.patch&lt;/code>) 에서도 알 수 있듯이 패키지의 버전이 바뀐다면 패치는 자동 적용되지 않습니다. 그럴 때는 다음과 같이 파일의 이름을 변경하고 다시 시도해볼 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 패키지 버전이 0.1.6에서 0.1.7로 바뀌었다고 가정해봅니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; cp patches/some-package+0.1.6.patch patches/some-package+0.1.7.patch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 패치를 적용해봅니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; npx patch-package
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>만약 패키지가 업데이트 되면서 패치와 관련된 코드가 변경되었다면 패치는 실패하며 그때는 패치를 새로 만들어야 합니다. 이 방법도 만능은 아닌 셈이죠. 그래도 현 시점에서 가장 간단하면서도 우아하게 적용할 수 있는 방법이었습니다.&lt;/p></content></item></channel></rss>