<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on 엔지니어 생존기록</title><link>https://netscout.github.io/posts/</link><description>Recent content in Posts on 엔지니어 생존기록</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sat, 03 Oct 2026 08:01:33 +0900</lastBuildDate><atom:link href="https://netscout.github.io/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>분산 시스템 설계(3) - 액터 모델(2): 상태는 누가 어떻게 관리하나?</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-3-%EC%95%A1%ED%84%B0-%EB%AA%A8%EB%8D%B8-2-%EC%83%81%ED%83%9C%EB%8A%94-%EB%88%84%EA%B0%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B4%80%EB%A6%AC%ED%95%98%EB%82%98/</link><pubDate>Sat, 03 Oct 2026 08:01:33 +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-3-%EC%95%A1%ED%84%B0-%EB%AA%A8%EB%8D%B8-2-%EC%83%81%ED%83%9C%EB%8A%94-%EB%88%84%EA%B0%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B4%80%EB%A6%AC%ED%95%98%EB%82%98/</guid><description>모든 어플리케이션은 종류를 막론하고 결국은 상태를 어떻게 관리하나?라는 하나의 질문을 해결합니다. 쇼핑 앱에 로그인해서 주문을 하기 위해서는 회원, 상품, 주문 상태를 관리해야 합니다. 실시간 온라인 게임에서는 각 플레이어의 캐릭터 HP/MP, 스킬 등의 상태와 …</description><content>&lt;p>모든 어플리케이션은 종류를 막론하고 결국은 &lt;code>상태를 어떻게 관리하나?&lt;/code>라는 하나의 질문을 해결합니다. 쇼핑 앱에 로그인해서 주문을 하기 위해서는 회원, 상품, 주문 상태를 관리해야 합니다. 실시간 온라인 게임에서는 각 플레이어의 캐릭터 HP/MP, 스킬 등의 상태와 NPC 들의 상태를 관리하면서 각 플레이어, NPC간의 상호작용을 상태에 실시간으로 반영해야 하죠. 그러니까 좀 단순화해서 말하자면, 개발자가 작성하는 코드는 대부분이 이 상태를 어떻게 안전하면서도 효율적으로 처리할거냐 하는 문제가 됩니다.&lt;/p>
&lt;p>그렇기 때문에 이 문제는 역사적으로도 매우 중요한 문제였고, 하드웨어와 네트워크의 발전으로 인해 상태를 관리하기 위한 방법도 많은 변화가 있었습니다.&lt;/p>
&lt;p>&lt;a href="https://www.ijcai.org/Proceedings/73/Papers/027B.pdf">액터 모델은 병렬성을 달성하기 위한 방법으로 1973년에 이미 제시&lt;/a>되었지만, 한동안 빛을 보지는 못했고, 2010년대 들어서야 다시 세상의 주목을 받게 되었죠. 이 글에서는 상태를 다루기 위해 분투했던 지난 날의 역사를 좀 돌아보고자 합니다.(제가 궁금해서 그렇습니다. 하하하&amp;hellip;)&lt;/p>
&lt;h2 id="객체의-탄생19601970-상태는-객체-안에-존재한다">객체의 탄생(1960~1970): 상태는 객체 안에 존재한다!&lt;/h2>
&lt;h3 id="최초의-oop-언어-simula-67">최초의 OOP 언어: Simula 67&lt;/h3>
&lt;p>객체라는 개념이 현실에 구현된 건 1967년으로 보는 게 적절해 보입니다. 1967년에 Ole-Johan Dahl과 Kristen Nygaard가 Simula 67이라는 언어를 발표하는데요, 우리에게 익숙한 객체, 클래스, 상속 등의 개념과 함께 가상(virtual) 프로시저, coroutine(코드 실행을 정지하고 다시 실행할 수 있는 개념) 등을 제시했습니다.&lt;/p>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Simula#Classes,_subclasses_and_virtual_procedures">wikipedia에서 발췌한 Simula 67의 예제 코드&lt;/a>를 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Begin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Class Glyph;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Virtual: Procedure print Is Procedure print;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Begin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> End;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Glyph Class Char (c);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Character c;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Begin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Procedure print;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> OutChar(c);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> End;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Glyph Class Line (elements);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ref (Glyph) Array elements;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Begin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Procedure print;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Begin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Integer i;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> For i:= 1 Step 1 Until UpperBound (elements, 1) Do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> elements (i).print;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> OutImage;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> End;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> End;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ref (Glyph) rg;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ref (Glyph) Array rgs (1 : 4);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ! Main program;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rgs (1):- New Char (&amp;#39;A&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rgs (2):- New Char (&amp;#39;b&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rgs (3):- New Char (&amp;#39;b&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rgs (4):- New Char (&amp;#39;a&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rg:- New Line (rgs);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rg.print;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">End;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>부모 클래스 &lt;code>Glyph&lt;/code>가 있고, &lt;code>Glyph&lt;/code>에는 가상 프로시저(가상 메서드) &lt;code>print&lt;/code>가 선언되어 있습니다. 그리고 자식 클래스 &lt;code>Char&lt;/code>, &lt;code>Line&lt;/code>이 선언되어 있고, 각각 &lt;code>print&lt;/code>함수를 구현하고 있습니다. 메인 프로그램 코드가 실행되면서 화면에 &lt;code>Abba&lt;/code>가 출력되겠죠.&lt;/p>
&lt;p>프로세스를 번갈아 실행하기 위해서 coroutine을 제공했지만, 두 프로세스가 동시에 한 객체를 접근하는 경우는 언어의 설계상 존재할 수 없었습니다.&lt;/p>
&lt;h3 id="액터-모델에-영감을-주다-smalltalk">액터 모델에 영감을 주다: Smalltalk&lt;/h3>
&lt;p>1970년대, Xerox PARC 연구소의 Alan Kay는 Simula에서 영감을 받아 순수 OOP 언어인 Smalltalk를 만들었고(&lt;a href="https://web.archive.org/web/20070930075844/http://gagne.homedns.org/%7etgagne/contrib/EarlyHistoryST.html#12">최초의 Smalltalk-71은 1971년에 고안되었습니다.&lt;/a>), &lt;code>Object Oriented&lt;/code>라는 말을 최초로 사용했습니다. Smalltalk를 순수 OOP 언어로 부르는 이유는, 원시(primitive) 타입이 존재하지 않기 때문입니다. 정수나 부울 등의 모든 값들이 객체이며, 정수간의 계산도 객체 간의 메세지를 전달하여 처리됩니다.&lt;/p>
&lt;p>그러니까 이렇게 간단해 보이는 덧셈이,&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">3 + 4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>수신자 객체 &lt;code>3&lt;/code>에게 메세지 &lt;code>+&lt;/code>와 매개변수 &lt;code>4&lt;/code>를 전달해서 결과를 얻는다고 하네요&amp;hellip; 지금와서 보면 이렇게 까지 순수할 필요가 있었을까 싶긴합니다만, 어쨌거나 모든 객체가 서로 메세지를 주고 받는다는 이 아이디어는 이후 &lt;a href="https://web.archive.org/web/20070930075844/http://gagne.homedns.org/%7etgagne/contrib/EarlyHistoryST.html#17">액터 모델에 영향을 주게 됩니다.&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>In November, I presented these ideas and a demonstration of the interpretation scheme to the MIT AI lab. &lt;code>This eventuall led to Carl Hewitt's more formal &amp;quot;Actor&amp;quot; approach&lt;/code> [Hewitt 73]. In the first Actor paper the resemblence to Smalltalk is at its closest. The paths later diverged, partly because we were much more interested in making things than theorizing, and partly because we had something no one else had: Chuck Thacker&amp;rsquo;s Interim Dynabook (later known as the &amp;ldquo;ALTO&amp;rdquo;).&lt;/p>&lt;/blockquote>
&lt;p>하지만 Alan Kay는 그 당시에 이론보다는 &lt;code>Dynabook&lt;/code> 이라고 하는 어린이 학습용 기기를 만드는 데 훨씬 관심이 있었기 때문에, 액터 모델을 계속 연구하지는 않았다고 합니다. 참고로 그 당시에 구상하던 Dynabook의 모습이라는데요, 참 시대를 앞서 나간 거 같습니다!&lt;/p>
&lt;p align="center">&lt;img src="./Dynabook.png" />&lt;br />By Kay, A.C. - "A personal computer for children of all ages" - paper presented at the ACM National Conference, Boston, Fair use, https://en.wikipedia.org/w/index.php?curid=19820380&lt;/p>
&lt;p>Smalltalk는 객체를 &lt;code>메세지로만 소통하는 독립 단위&lt;/code>로 보는 관점을 제시했지만, 이후 OOP는 정적 타입과 가상 함수 테이블을 제시했던 Simula의 방식이 주류로 자리잡게 됩니다.&lt;/p>
&lt;h2 id="액터-모델의-태동1973">액터 모델의 태동(1973)&lt;/h2>
&lt;p>앞서 살펴봤듯이 액터 모델의 이론은 1973년에 Carl Hewitt 등이 발표한 &lt;a href="https://www.ijcai.org/Proceedings/73/Papers/027B.pdf">A Universal Modular ACTOR Formalism for Artificial Intelligence&lt;/a>을 통해 제안되었습니다. 액터 모델을 제안한 이유는 당시 하드웨어가 발전함에 따라 물리적인 프로세서를 여러 개 병렬로 실행하는 아이디어를 생각하기 시작하던 때였는데요, 액터 모델이 거기에 적합하다는 거 였습니다.&lt;/p>
&lt;blockquote>
&lt;p>Current developments in hardware technology are making it economically attractive to run many physical processors in parallel. This leads to a &amp;ldquo;swarm of bees&amp;rdquo; style of programming. The actor formalism provides a coherent method for organizing and controlling all these processors. &lt;code>One way to build an ACTOR machine is to put each actor on a chip and build a decoding network so that each actor chip can address all the others.&lt;/code> In certain applications parallel processing can greatly speed up the processing. For example with sufficient parallelism, garbage collection can be done in a time which is proportional to the logarithm of the storage collected instead of a time proportional to the amount of storage collected which is the best that a serial processor can do. Also the architecture looks very promising for parallel processing in the lower levels of computer audio and visual processing.&lt;/p>&lt;/blockquote>
&lt;p>그러니까 칩 하나에 액터 하나를 심어서 그걸 네트워크로 연결하면 액터가 서로를 찾을 수 있다, 그러면 특정한 상황에서 순차 프로세싱 보다 병렬 프로세싱은 매우 빨라질거다 뭐 그런 이야기 입니다. 액터가 뭐길래 칩 하나에 액터 하나를 심는다는 걸까요? 액터 모델에서 이야기하는 액터는 3가지 작업을 수행할 수 있었습니다.&lt;/p>
&lt;ol>
&lt;li>다른 액터에게 메세지를 보낸다.&lt;/li>
&lt;li>새 액터를 만든다.&lt;/li>
&lt;li>다음 메세지를 받았을 때 어떻게 행동할지 결정한다.&lt;/li>
&lt;/ol>
&lt;p>3번 작업은 곧 액터 자신의 상태를 변경한다는 말인데요, 직접 다른 액터의 상태를 읽거나 변경할 방법은 없으며 메세지를 통해서 작업을 하도록 부탁해야 합니다. 즉, 독자적으로 행동하면서 서로 메세지를 주고 받는 액터를 칩하나에 심어서 병렬 처리를 하자 뭐 그런 이야기였던 셈이죠.&lt;/p>
&lt;p>왜 이런 구상을 했는지 당시 상황을 좀 살펴보면 인텔 4004(1971), 8008(1972)등 CPU가 칩 하나로 만들어 지고 있었고, &lt;code>반도체 칩에 집적되는 트랜지스터의 수가 약 2년마다 두 배로 증가한다&lt;/code>(1965년 당시 최초 발언에서는 1년마다 였지만, 1975년 2년마다로 수정)는 &lt;code>무어의 법칙&lt;/code>도 맞아가던 시절이었습니다. 그러니까 프로세서가 이제 찍어낼 수 있는 부품의 개념으로 바뀌고 있었고, 실제로 &lt;a href="https://en.wikipedia.org/wiki/ILLIAC_IV">ILLIAC IV(1972)&lt;/a>등 여러 병렬 컴퓨터가 만들어 지고 있었습니다.(이후 1975년에 이르러서 제대로 동작했다고 합니다.)&lt;/p>
&lt;p align="center">&lt;img src="./ILLIAC_4_parallel_computer.jpg" />&lt;br />ILLIAC IV의 모습. By Steve Jurvetson from Menlo Park, USA - Flickr, CC BY 2.0, https://commons.wikimedia.org/w/index.php?curid=755549&lt;/p>
&lt;p>게다가 현대 인터넷의 시초인 ARPANET이 1969년 구축되어 독립된 노드들이 메세지를 주고받는 통신 모델이 현실화 되었고, Smalltalk가 객체 간의 메세지 통신이 실제로 동작한다는 아이디어를 제공했습니다.&lt;/p>
&lt;p>그리고 이후 액터 모델을 실제로 만들려는 시도가 있었습니다.(MIT의 &lt;a href="https://web.archive.org/web/20170706134358/ftp://publications.ai.mit.edu/ai-publications/pdf/AIM-672.pdf">Act 1&lt;/a>, &lt;a href="https://web.archive.org/web/20170706144407/ftp://publications.ai.mit.edu/ai-publications/pdf/AITR-728.pdf">Act 2&lt;/a>나 일본에서 개발된 &lt;a href="https://en.wikipedia.org/wiki/Actor-Based_Concurrent_Language">ABCL&lt;/a> 등) 하지만 연구실 밖에서는 전혀 주목받지 못했고, 액터 모델이 제대로 구현될 수 있다는 걸 세상에 알린 건 Erlang 이었습니다.&lt;/p>
&lt;h3 id="erlang">Erlang&lt;/h3>
&lt;p>Erlang은 &lt;a href="https://en.wikipedia.org/wiki/Ericsson">에릭슨&lt;/a>의 전화 교환기의 소프트웨어를 개선하기 위해 Joe Armstrong 등이 설계했습니다. 수많은 동시성 처리와 여러 컴퓨터에 걸친 분산 처리, 수년간 무중단 운영이 가능해야 하며, 하드웨어 및 소프트웨어 오류에 대한 내결함성(fault tolerance)을 가져야 했습니다. 1986년에 개발을 시작한 뒤 Prolog 인터프리터에서 C기반의 VM 환경으로 개선(1992)하는 등의 많은 노력 끝에 실제 개발에 투입될 수 있었고, 1998년에 출시된 AXD301 스위치는 100만 라인이 넘는 Erlang으로 작성되었고 &lt;a href="https://en.wikipedia.org/wiki/Nines_%28notation%29#Nomenclature">9-nines&lt;/a>의 가용성을 달성했다고 알려져 있습니다.&lt;/p>
&lt;p>하지만 통신 업계를 제외하면 Erlang은 무명에 가까웠고, 주류는 Erlang과는 다른 길로 발전해갔습니다.&lt;/p>
&lt;blockquote>
&lt;p>사실 Erlang은 액터 모델에서 영향을 받았다기 보다는, 문제를 해결하려고 노력한 결과 액터 모델의 형태를 갖추게 되었습니다. Joe Armstrong은 자신이 액터 모델을 전혀 몰랐다고 말하기도 했는데요, 어쨌거나 결과적으로는 액터 모델이 잘 동작한다는 걸 증명했습니다.&lt;/p>&lt;/blockquote>
&lt;h2 id="oop-전성시대19801990년대">OOP 전성시대(1980~1990년대)&lt;/h2>
&lt;p>Bjarne Stroustrup은 1979년, Simula 67의 클래스 개념을 C에 얹은 &lt;code>C with Classes&lt;/code> 작업을 시작했습니다. C의 속도와 이식성을 잃지 않으면서도 클래스, 상속, 가상 함수, 함수 오버로딩 등을 추가하려고 했던 거죠. 그리고 1983년에 이름을 C++로 바꾸고 1985년에 첫 번째 상용 컴파일러 Cfront가 출시 됩니다.&lt;/p>
&lt;p>이후 90년대에 이르러 C++의 대중화, Delphi, Java 등의 OOP 언어 들이 잇따라 출시되고 전성시대를 이루게 됩니다.&lt;/p>
&lt;p>주류 OOP 언어의 객체는 &lt;code>메세지로만 소통하는 독립 단위&lt;/code>가 아닌 메모리위의 구조체 + 가상 함수 테이블로 구현되었고, 각각의 객체가 자신의 정보를 은닉하고 외부에 공개한 메서드로만 자신의 상태를 변경할 수 있도록 했습니다. 이런 방식은 처음에는 문제가 없어보였지만 멀티 스레드 상황에서는 스레드 간에 객체의 상태가 가변적으로 공유되는 문제가 있었습니다. 서로 다른 스레드가 동일한 객체의 메서드를 호출할 때, 메모리에서 읽고 쓰는 과정이 겹치면 객체의 상태가 오염되었던 거죠.&lt;/p>
&lt;p>물론 훨씬 더 오래 전에 이런 문제를 이미 고민한 사람들이 있었는데요, &lt;a href="https://en.wikipedia.org/wiki/Monitor_%28synchronization%29">모니터&lt;/a>를 통해 동시성 문제를 해결하고자 했습니다. 모니터는 mutex(mutual exclusion 또는 lock)와 조건 변수(~까지 기다려 같은 조건 설정)를 통해 접근권을 가지지 못한 스레드가 공유 객체의 상태 값에 접근하지 못하도록 막아줍니다.&lt;/p>
&lt;pre class="mermaid">sequenceDiagram
participant T1 as 스레드 1
participant T2 as 스레드 2
participant O as 객체
T1-&amp;gt;&amp;gt;O: lock 획득, deposit(100) 호출
T2-&amp;gt;&amp;gt;O: lock 대기
O--&amp;gt;&amp;gt;T1: 상태값 갱신, lock 해제
T2-&amp;gt;&amp;gt;O: lock 획득, withdraw(50) 호출
O--&amp;gt;&amp;gt;T2: 상태값 갱신, lock 해제&lt;/pre>&lt;p>위 예제에서 락이 두 스레드의 동시 접근을 순차적으로 정렬하고, 스레드2는 스레드1이 락을 해제할 때 까지 대기 하게 됩니다.&lt;/p>
&lt;p>단, 모니터가 데드락이나 라이브락을 막아주지는 못하므로 프로그래머는 신중하게 락의 순서와 범위를 결정해야 합니다.&lt;/p>
&lt;p>두 스레드가 계좌 A와 B 사이에서 서로 반대 방향으로 송금한다고 해볼까요? 스레드 1은 A → B, 스레드 2는 B → A로 보내려고 하고, 송금하려면 두 계좌의 락을 모두 잡아야 합니다.&lt;/p>
&lt;p>&lt;strong>데드락(deadlock)&lt;/strong> 은 서로가 가진 락을 기다리면서 둘 다 영원히 멈춰버리는 상황입니다.&lt;/p>
&lt;pre class="mermaid">sequenceDiagram
participant T1 as 스레드 1 (A → B)
participant A as 계좌 A 락
participant B as 계좌 B 락
participant T2 as 스레드 2 (B → A)
T1-&amp;gt;&amp;gt;A: lock 획득
T2-&amp;gt;&amp;gt;B: lock 획득
T1-xB: lock 요청 → 대기 (T2가 보유 중)
T2-xA: lock 요청 → 대기 (T1이 보유 중)
Note over T1,T2: 서로 상대의 락을 기다리며 둘 다 멈춤 (아무것도 안 함)&lt;/pre>&lt;p>&lt;strong>라이브락(livelock)&lt;/strong> 은 데드락을 피하려고 &amp;ldquo;상대 락을 못 잡으면 내 락을 풀고 다시 시도하자&amp;quot;고 했는데, 둘이 똑같은 타이밍에 양보를 반복하는 상황입니다. 스레드는 계속 바쁘게 움직이지만, 일은 하나도 진행되지 않습니다.&lt;/p>
&lt;pre class="mermaid">sequenceDiagram
participant T1 as 스레드 1 (A → B)
participant A as 계좌 A 락
participant B as 계좌 B 락
participant T2 as 스레드 2 (B → A)
loop 같은 타이밍에 끝없이 반복
T1-&amp;gt;&amp;gt;A: lock 획득
T2-&amp;gt;&amp;gt;B: lock 획득
T1-xB: lock 시도 실패
T2-xA: lock 시도 실패
T1-&amp;gt;&amp;gt;A: 양보하려고 lock 해제
T2-&amp;gt;&amp;gt;B: 양보하려고 lock 해제
end
Note over T1,T2: 둘 다 계속 움직이지만 송금은 영원히 진행되지 않음&lt;/pre>&lt;p>좁은 복도에서 마주친 두 사람으로 비유하면, 데드락은 둘 다 &amp;ldquo;당신이 먼저 비켜요&amp;rdquo; 하고 가만히 서 있는 거고, 라이브락은 둘 다 같은 쪽으로 계속 비켜주느라 좌우로 왔다 갔다만 하는 겁니다. 보통 데드락은 모든 스레드가 락을 &lt;strong>같은 순서&lt;/strong>로 잡게 해서(예: 계좌 번호가 작은 쪽 먼저) 막고, 라이브락은 재시도 전에 &lt;strong>무작위로 잠깐 기다리게&lt;/strong>(random backoff) 해서 타이밍을 어긋나게 만들어 막습니다.&lt;/p>
&lt;h2 id="분산-객체1990-2000년대-초">분산 객체(1990-2000년대 초)&lt;/h2>
&lt;p>80년대 후반부터 PC와 유닉스 워크스테이션의 성능이 향상되고 가격은 대중화 되기 시작했습니다. 그래서 기존에 쓰던 비싼 메인프레임 컴퓨터 대신에 여러대의 컴퓨터로 업무를 나눠 처리하는 클라이언트/서버 아키텍처가 유행했고, 클라이언트와 서버를 네트워크로 연결하기 위한 LAN도 대중화 되었습니다.&lt;/p>
&lt;p align="center">&lt;img src="./h13-ibm-390_0.png" />&lt;br />메인프레임과 PC가 한 공간에서, https://www.sleutelboek.eu/web/computerhardware30-13-de-krachtigste-computers&lt;/p>
&lt;p>그러다 보니 기업이 쓰던 기존의 메인프레임(주로 COBOL을 사용), 유닉스 서버, 윈도우 등 수많은 기종이 뒤섞이게 되었고, 기업 간에 쓰는 시스템도 매우 달라서 서로 호환이 되지 않는 경우가 종종 발생했습니다. 기업이 합병하는 일이 생기면 시스템 통합이 아주 큰 문제였던 거죠.&lt;/p>
&lt;p>1989년 HP, Sun등의 기업이 업계 표준을 만들 목표로 &lt;code>OMG(Object Management Group)&lt;/code>을 결성했고, 1991년 CORBA 1.0을 발표했습니다.&lt;/p>
&lt;p align="center">&lt;img src="./diagram-orb_to_orb.gif" />&lt;br />CORBA 원격 호출, https://userpages.umbc.edu/~dgorin1/451/middleware/objectoriented.htm&lt;/p>
&lt;p>위 다이어그램에 등장하는 요소는 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>Client: 원격 객체의 메서드를 호출하는 쪽 프로그램&lt;/li>
&lt;li>Object: 실제 처리 로직을 담고 있는 서버 쪽 객체&lt;/li>
&lt;li>Stub: 클라이언트 쪽 대리인. IDL 인터페이스 정의로 부터 생성되며, Stub에 대한 호출을 네트워크로 전송할 수 있는 바이트 형태로 변환(마샬링)&lt;/li>
&lt;li>Skel(Skeleton): 서버 쪽 대리인. IDL 인터페이스 정의로 부터 생성되며, 도착한 바이트를 메서드 이름과 인자로 복원하고(언마샬링), 실제 객체의 메서드를 호출하고 결과를 다시 마샬링해서 리턴.&lt;/li>
&lt;li>ORB(Object Request Broker): 요청을 받아 목적지 객체를 찾고 전달한다. 객체가 같은 프로세스 또는 다른 컴퓨터에 있는지 찾고 연결하여 통신을 처리한다.&lt;/li>
&lt;li>IIOP(Internet Inter-ORB Protocol): ORB가 TCP/IP를 통해 통신을 주고 받는데 사용하는 표준 프로토콜. CORBA 2.0에 추가됨.&lt;/li>
&lt;/ul>
&lt;p>도식의 화살표를 따라가보면 두 개의 호출을 확인할 수 있습니다. 첫 번째는 같은 ORB에서 관리되는 객체를 호출하는 경우, 그리고 두 번째는 원격의 ORB에서 관리되는 객체를 호출하는 경우입니다.&lt;/p>
&lt;p>MS에서도 자신들만의 독자적인 길을 가고 있었는데요, 1996년 윈도우 NT 4.0 기반의 DCOM을 발표했습니다.&lt;/p>
&lt;p align="center">&lt;img src="./diagram-dcom.png" />&lt;br />DCOM Technology. Şevket Duran Haşim Sak. https://www.slideserve.com/truong/dcom-technology&lt;/p>
&lt;p>앞서 살펴본 CORBA와 구조는 거의 동일합니다. COM 런타임이 CORBA의 ORB의 역할을 맡아 객체 생성 및 참조 관리와 호출을 관리합니다. 클라이언트 측에서는 Proxy, 서버에서는 Stub이 호출을 받아 마샬링/언마샬링을 담당합니다.&lt;/p>
&lt;p>두 기술 모두 분산 시스템 환경에서 객체가 어디에 존재하는지와 상관없이 로컬 호출 처럼 사용할 수 있도록 하는 위치 투명성을 확보하고자 했습니다. 하지만, 분산 객체는 로컬 객체와 본질적으로 다르다는 주장이 설득력을 얻었죠.&lt;/p>
&lt;p>Jim Waldo등 &lt;a href="https://waldo.scholars.harvard.edu/publications/note-distributed-computing">A Note on Distributed Computing&lt;/a>의 저자들은 초록(abstract)에서 이렇게 말하고 있습니다.&lt;/p>
&lt;blockquote>
&lt;p>We argue that objects that interact in a distributed system need to be dealt with in ways that are intrinsically different from objects that interact in a single address space. These differences are required because distributed systems require that the programmer be aware of latency, have a different model of memory access, and take into account issues of concurrency and partial failure.&lt;/p>&lt;/blockquote>
&lt;p>즉, 로컬 호출과 원격 호출은 다음과 같이 차이가 있다는 겁니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&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;/td>
&lt;td>밀리초, 4~5자릿수 차이&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>메모리 접근&lt;/td>
&lt;td>포인터 공유 가능&lt;/td>
&lt;td>주소 공간이 다름, 포인터 무의미&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>부분 실패&lt;/td>
&lt;td>호출자와 피호출자가 같이 죽거나 같이 산다&lt;/td>
&lt;td>원격 피호출자만 죽을 수 있고, 죽었는지 느린지 구분 불가&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>동시성&lt;/td>
&lt;td>호출 순서를 제어할 수 있다&lt;/td>
&lt;td>다른 클라이언트가 언제든 끼어든다&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>이 중에 가장 심각한 문제는 동시성과 부분 실패일 겁니다. 은행 시스템에서 입금/인출 하는데 부분적으로 실패한다? 입금이 진행 중인데 인출 돼버렸다? 이건 굉장히 심각하죠. 객체를 네트워크위에 올려놓았지만, 객체의 동시성을 제어하기 위한 락은 네트워크를 넘지 못했죠.&lt;/p>
&lt;h3 id="분산-락과-2단계-커밋">분산 락과 2단계 커밋&lt;/h3>
&lt;p>분산 시스템에서 원격 객체의 상태를 지키기 위해서는 두 가지 문제를 풀어야 합니다.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>상호 배제: 네트워크를 통해 참여하는 여러 노드가 &amp;ldquo;이 객체를 지금 누가 접근할 수 있나?&amp;ldquo;에 대해 합의해야 하는 문제입니다. 이 문제를 해결하려면 락이 로컬을 넘어서, 합의 프로토콜(paxos, raft 등)을 통해 복제되는 서비스(분산 락 매니저)여야 합니다. 최근에는 ZooKeeper나 etcd등이 이런 역할을 담당합니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>원자적 커밋: 여러 노드에 걸쳐있는 변경이 전부 성공하여 반영되거나, 모두 실패하여 취소하는 문제입니다. 이 문제를 해결하기 위해서 2단계 커밋이 제시되었습니다. 각 노드에서 오는 요청을 코디네이터가 처리하며, 코디네이터는 모든 참여자에게 PREPARE를 보냅니다. 모든 참여자가 VOTE_COMMIT으로 응답하면 COMMIT 요청을 보내고, 하나라도 ABORT로 응답하면 ABORT를 보냅니다.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;pre class="mermaid">sequenceDiagram
participant C as 코디네이터
participant A as 참여자 A
participant B as 참여자 B
C-&amp;gt;&amp;gt;A: PREPARE
C-&amp;gt;&amp;gt;B: PREPARE
A--&amp;gt;&amp;gt;C: VOTE_COMMIT (로그 기록 후)
B--&amp;gt;&amp;gt;C: VOTE_COMMIT (로그 기록 후)
C-&amp;gt;&amp;gt;A: COMMIT
C-&amp;gt;&amp;gt;B: COMMIT&lt;/pre>&lt;p>두 경우 모두 조정자가 필요하고, 조정자는 결론이 날 때 까지 요청을 블록해야 합니다. CORBA OTS(Object Transaction Service) 와 DCOM의 MTS(Microsoft Transaction Server)가 2단계 커밋을 분산 객체에 구현한 것인데, 네트워크에서 객체에 락을 걸기 위해 매우 복잡한 절차를 거쳐야 했던 거죠.&lt;/p>
&lt;h2 id="마이크로서비스2010">마이크로서비스(2010~)&lt;/h2>
&lt;p>이후 XML을 HTTP로 주고받으면서 이기종간의 상호운용성을 확보하자는 SOAP기반의 웹서비스가 제시되었고 MS등의 각 벤더가 여기 집중했지만 너무 복잡한 표준 명세와 IDE나 프레임워크의 과도한 의존, 그리고 XML 구조가 너무 복잡하고 무거운 이유 등의 문제가 있었습니다. 게다가 2000년대 중반 웹2.0과 ajax의 확산으로 웹의 중심이 브라우저로 옮겨간 것도 SOAP기반 웹서비스가 외면받는 이유가 되었습니다.&lt;/p>
&lt;p>그리고 SOA라는 개념도 2000년대 들어 유행했지만, 역시 각 벤더가 고가의 제품과 컨설팅을 중심으로 사업을 전개하면서 그 자체가 또 하나의 병목지점이자 블랙박스가 되었습니다.&lt;/p>
&lt;p>그리고 지금 우리는 자유로운 JSON기반의 REST API와 계약 기반의 gRPC등을 통해 마이크로서비스 시대에 살고 있는데요, 마이크로서비스에서 상태는 DB가 책임지고 있습니다.&lt;/p>
&lt;pre class="mermaid">flowchart LR
LB[로드 밸런서] --&amp;gt; S1[주문 서비스 #1]
LB --&amp;gt; S2[주문 서비스 #2]
LB --&amp;gt; S3[주문 서비스 #3]
S1 --&amp;gt; DB[(주문 DB)]
S2 --&amp;gt; DB
S3 --&amp;gt; DB&lt;/pre>&lt;p>이상적인 마이크로서비스 구조에서 각 서비스는 자기 도메인과 연관된 DB만 가지기 때문에 서비스 간의 트랜잭션은 존재하지 않으며, 여러 서비스에 걸친 변경을 처리할 때는 saga 패턴을 사용하게 됩니다.&lt;/p>
&lt;pre class="mermaid">flowchart LR
subgraph F[정상 흐름: 각 단계는 자기 DB에 로컬 커밋]
direction LR
O[주문 서비스&amp;lt;br/&amp;gt;주문 생성] --&amp;gt; P[결제 서비스&amp;lt;br/&amp;gt;결제 승인] --&amp;gt; I[재고 서비스&amp;lt;br/&amp;gt;재고 차감] --&amp;gt; D[배송 서비스&amp;lt;br/&amp;gt;배송 예약]
end
D -. 배송 예약 실패 .-&amp;gt; CI
subgraph B[보상 흐름: 역순으로 되돌림]
direction RL
CI[재고 서비스&amp;lt;br/&amp;gt;재고 복원] --&amp;gt; CP[결제 서비스&amp;lt;br/&amp;gt;결제 취소] --&amp;gt; CO[주문 서비스&amp;lt;br/&amp;gt;주문 취소]
end&lt;/pre>&lt;p>각 단계는 성공시 즉시 커밋되는데, 중간에 요청 하나가 실패할 경우 역순으로 각 커밋을 되돌리는 구조입니다. 각 서비스가 취소와 복원을 위한 로직을 따로 설계해야 하는 거죠. 트랜잭션의 롤백이 아닌 &lt;code>실패를 보상하기 위한 새 트랜잭션&lt;/code>을 수행하기 때문에 중간 상태가 다른 요청에 노출될 수 있습니다.&lt;/p>
&lt;p>이런 구조는 모든 상태가 DB에 존재하는 상태 없는 서비스에는 잘 맞지만, 실시간 게임이나 장바구니 처럼 처리 대상이 오래 유지되는 상태를 가지는 경우라면, 매번 요청을 받을 때 마다 상태를 DB에서 복원/저장하는 식으로 처리해야 합니다.&lt;/p>
&lt;p>이런 경우 &lt;code>상태를 메모리에 올려놓고, 딱 1 군데에서만 그걸 접근하면 어떨까?&lt;/code> 하는 질문이 제기되었고, 그래서 액터 모델이 다시 돌아오게 됩니다!&lt;/p>
&lt;h2 id="액터-모델의-귀환2010">액터 모델의 귀환(2010~)&lt;/h2>
&lt;p>앞서 상태를 공유함으로써, 또는 분산 시스템에서 상태를 공유하고자 하면서 발생한 문제들과 해결책들을 살펴봤습니다. 그 중에서도 마이크로서비스는 현재 가장 성공적으로 구현되어 운영되고 있습니다. DDD(Domain Driven Design), EDA(Event Driven Architecture)등의 설계를 통해서 서비스 간의 의존성을 명확하게 구분하고 결과적 일관성을 목표로 해서 조금 더 유연하게 상태를 관리하면서 시스템 실패에 대응하는 형태로 진화하고 있죠.&lt;/p>
&lt;p>하지만 액터 모델은 상태 관리 문제를 아예 다르게 접근합니다. 처음부터 아예 상태를 공유하지 않으면 된다는 거죠! 2009년에 JVM 기반의 &lt;a href="https://akka.io/">Akka&lt;/a>가 나왔고, 이후에 Akka를 .net으로 포팅한 &lt;a href="https://getakka.net/">Akka.net&lt;/a>이 나왔습니다. 그리고 2015년에 MS는 자사 서비스에서 활용하던 &lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/overview?pivots=orleans-10-0">Orleans&lt;/a>를 오픈소스로 공개했습니다.&lt;/p>
&lt;p>고전 액터 모델에서 액터의 생성 및 삭제는 개발자가 처리해야 했습니다. 특정 액터에게 메세지를 보내려면 반드시 먼저 액터가 생성되어 있어야 하는데, 그래야 액터의 주소를 알 수 있고 거기에 메세지를 보낼 수 있기 때문이죠. Orleans는 항상 액터가 존재한다고 가정하는 가상 액터(virtual actor)를 새롭게 제시했습니다. 개발자는 액터의 존재 유무와 상관없이 메세지를 그냥 보내기만 하면 되고, 액터의 생성 및 삭제 그리고 활성/비활성화는 런타임이 담당하는 구조인거죠.&lt;/p>
&lt;p>이렇게 보면 앞서 이야기 했던 분산 객체와도 좀 비슷한 느낌이 들죠? 하지만, 액터 모델의 경우 분산 객체 처럼 RPC(Remote Procedure Call) 호출을 한다기 보다는, 분산 시스템 어딘가에 존재할 액터에게 메세지를 보내는 개념으로 처리됩니다. 그러면 액터는 자신에게 도달한 메세지를 큐에 쌓아뒀다가 순서대로 처리하게 되는 거죠.&lt;/p>
&lt;p>그러니까 분산 객체가 &lt;code>원격의 객체가 로컬에 있는 거 처럼 호출&lt;/code>하는 게 목표였다면, 액터 모델은 &lt;code>로컬에 있더라도 원격 메세지 호출 처럼 다루자&lt;/code>의 느낌입니다. 액터가 어디에 있는지 신경쓰지 말고 메세지 전송/수신 방식을 사용하자는 거죠.&lt;/p>
&lt;blockquote>
&lt;p>그러니까 로컬에서 로컬로 전송되는 메세지의 경우 &lt;code>네트워크 실패가 거의 발생하지 않는 원격 메세지&lt;/code>정도로 생각하자는 거죠.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>.NET Orleans의 경우 액터에 대한 메세지 전송이(&lt;code>await order.AddItem(x)&lt;/code>) 마치 RPC처럼 보이기도 합니다.&lt;/p>&lt;/blockquote>
&lt;p>액터 모델을 아주 간단한 도식으로 살펴 보면 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">flowchart LR
C1[클라이언트 A] --&amp;gt;|메시지| R[런타임: ID → 노드 라우팅]
C2[클라이언트 B] --&amp;gt;|메시지| R
R --&amp;gt; A1[&amp;#34;액터 cart:42&amp;lt;br/&amp;gt;(노드 1, 상태 메모리 보관)&amp;#34;]
R --&amp;gt; A2[&amp;#34;액터 cart:77&amp;lt;br/&amp;gt;(노드 2)&amp;#34;]
A1 -.-&amp;gt;|주기적/이벤트 저장| DB[(영속 저장소)]&lt;/pre>&lt;p>액터에게 메세지를 보내는 클라이언트는 런타임이 이해할 수 있는 액터의 식별자 ID를 목표로 메세지를 전송합니다. 그러면 런타임은 ID를 통해 액터가 어떤 노드에 있는지 찾고, 액터에게 메세지를 전달 해줍니다. 각 액터는 자신의 상태를 메모리에 가지고 있으며(상태는 최종적으로 DB에 저장하지만, 액터가 생성/활성화될 때 메모리에 로드합니다. 시스템에서 액터는 결과적으로 유일하기 때문에 메모리에서 상태를 관리해도 문제가 없습니다!), 큐에 쌓인 메세지를 순서대로 처리하므로 락을 사용하지 않고도 상태의 일관성이 보장됩니다.&lt;/p>
&lt;blockquote>
&lt;p>시스템에서 액터가 결과적으로 유일한 이유는, 클러스터간의 네트워크 단절등의 불안정한 상황에서는 액터가 중복되어 활성화 될 수도 있기 때문입니다! Orleans의 경우 액터는 활성화 될 때, 저장소에서 ETag(버전 표시)를 확인합니다. 그래서 자신이 가진 ETag을 비교해서 값이 다르다면 저장소에 상태를 덮어쓸 수 없도록 합니다.&lt;/p>&lt;/blockquote>
&lt;p>물론 액터 모델도 여러 액터에 걸친 처리를 해야 하는 경우가 생길 수도 있습니다. 예를 들어서 하나의 주문에 여러 상품이 포함되는 경우를 생각해보면, 주문도 액터고 상품도 액터입니다. 그런데 주문 액터는 주문에 포함된 모든 상품 주문 상태(장바구니에 뒀다가 주문하는 그 순간에 재고가 없어졌을 수도 있죠!)를 확인하고 주문의 상태를 변경해야 합니다.&lt;/p>
&lt;p>Orleans는 이런 문제를 위해서 분산 ACID 트랜잭션을 지원하긴 합니다. 이미 앞서 살펴봤던 &lt;code>락+2단계 커밋&lt;/code>기반이기 때문에, 빈번한 데이터 쓰기가 발생하는 경우 성능이 저하될 수 있겠죠. 그리고 &lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/grains/transactions#transactional-state-storage">ITransactionalStateStorage를 구현하는 데이터 스토리지&lt;/a>만 지원되는데 Azure Table Storage, &lt;a href="https://www.nuget.org/packages/Microsoft.Orleans.Transactions.DynamoDB">AWS DynamoDB&lt;/a>, &lt;a href="https://github.com/Scythemen/Cellar.Orleans.Transactions.AdoNet">MSSQL/MySQL&lt;/a> 등이 지원되고 있습니다. 그런데 DynamoDB의 경우 최신 버전 다운로드가 398회(2026-09-28 기준)에 불과하고, MSSQL/MySQL 라이브러리의 경우 오래전에 개발이 중단되는 등 사실상 Azure Table Storage 정도가 안정적인 버전이라서 선택지가 좁은 상황입니다.&lt;/p>
&lt;p>아무래도 일반적인 해결책은 앞서 살펴봤던 saga 패턴을 사용하는 거겠죠. 그게 아니라면 이 문제가 액터 모델로 풀기에 적절하지 않은 문제 일 수도 있습니다.(DB에서 상태를 관리하는 경우, 트랜잭션 하나로 해결할 수도 있기 때문이죠.) 그래서 액터 모델과 DB에 상태를 저장하는 방식을 섞어 쓰는 경우도 있다고 합니다.&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>뭔가 액터 모델에 대해서 정리하려다 보니 동시성과 병렬성에 대한 정리도 하고, 객체를 기반으로 한 분산 시스템의 역사에 대해서 살펴보게 됐습니다. 그냥 단순히 제가 궁금해서 그랬던 거긴 한데, 꼼꼼하게 정리하자니 다뤄야할 내용이 너무 많아서 좀 핵심 내용위주로 살펴보면서 정리했습니다. 어쨌거나 다음에는 .NET Orleans를 통해서 액터 모델을 직접 구현해볼까 합니다!&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en">Alan Kay, &amp;ldquo;Dr. Alan Kay on the Meaning of Object-Oriented Programming&amp;rdquo; (2003년 이메일, Stefan Ram 정리)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.cppreference.com/w/cpp/language/history">cppreference, History of C++&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Monitor_%28synchronization%29">Wikipedia, Monitor (synchronization)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Actor_model">Wikipedia, Actor model&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/ILLIAC_IV">Wikipedia, ILLIAC IV&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Erlang_%28programming_language%29">Wikipedia, Erlang (programming language)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://erlang.org/pipermail/erlang-questions/2014-June/079891.html">erlang-questions 메일링 리스트, &amp;ldquo;Erlang is &lt;em>not&lt;/em> a implementation of the Actor model&amp;rdquo; (2014)&lt;/a>, &lt;a href="https://en.wikiquote.org/wiki/Joe_Armstrong_%28programmer%29">Wikiquote, Joe Armstrong&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture">Wikipedia, Common Object Request Broker Architecture&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://userpages.umbc.edu/~dgorin1/451/middleware/objectoriented.htm">UMBC, Object-Oriented Middleware&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doc.akka.io/libraries/misc/smli_tr-94-29.pdf">Jim Waldo, Geoff Wyant, Ann Wollrath, Sam Kendall, &amp;ldquo;A Note on Distributed Computing&amp;rdquo; (Sun Microsystems Laboratories, SMLI TR-94-29, 1994)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www4.cs.fau.de/~geier/corba-faq/transaction-service.html">CORBA FAQ, Object Transaction Service&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.oracle.com/cd/E11882_01/win.112/e26104/using.htm">Oracle, Using Microsoft Transaction Server with Oracle Database&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dl.acm.org/doi/10.1145/38713.38742">Hector Garcia-Molina, Kenneth Salem, &amp;ldquo;Sagas&amp;rdquo; (ACM SIGMOD 1987)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doc.akka.io/libraries/akka-core/current/general/remoting.html">Akka Documentation, Location Transparency&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-identity">Microsoft Learn, Orleans Grain identity&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/host/grain-directory">Microsoft Learn, Orleans Grain directory&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/grains/transactions">Microsoft Learn, Transactions in Orleans&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>분산 시스템 설계(3) - 액터 모델(1) feat. Go, Node.js, .NET, Java, Rust</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-3-%EC%95%A1%ED%84%B0-%EB%AA%A8%EB%8D%B8-1-feat-go-node-js-net-java-rust/</link><pubDate>Sat, 19 Sep 2026 08:11:19 +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-3-%EC%95%A1%ED%84%B0-%EB%AA%A8%EB%8D%B8-1-feat-go-node-js-net-java-rust/</guid><description>아주 간단하게 설명하면, 동시성은 여러 작업을 어떻게 처리할지 프로그램을 설계하는 문제고, 병렬성은 동시 실행을 처리하는 하드웨어의 문제입니다.</description><content>&lt;blockquote>
&lt;p>원래는 액터 모델에 대해서 다루려고 했으나&amp;hellip; 조사를 하다 보니, 동시성과 병렬성에 대해서 먼저 정리하고 가는 게 좋겠다 싶은 마음이 들었습니다.(제가 헷갈렸기 때문이기도 하죠!) 그리고 정리를 하다 보니&amp;hellip; 모르는 내용이 많아서 이거 저거 정리해서 넣다 보니 일단 각 주요 언어별로 어떻게 동시성과 병렬성을 처리하는지에 대해서 정리하는 문서가 되어버렸습니다!&lt;/p>&lt;/blockquote>
&lt;p>아주 간단하게 설명하면, 동시성은 여러 작업을 어떻게 처리할지 프로그램을 설계하는 문제고, 병렬성은 동시 실행을 처리하는 하드웨어의 문제입니다.&lt;/p>
&lt;p>아마도 흑백요리사 같은 요리 경연 프로그램을 누구나 한 번쯤은 봤을 겁니다. 1대1 또는 n대n으로 요리대결을 진행하죠.&lt;/p>
&lt;p>1대1로 요리대결을 할 때는 동시성의 문제가 절대적입니다. 제한된 시간 내에 국을 끓이면서 재료를 손질하다가 오븐에 재료를 넣고 다시 국을 젓고 등의 작업을 어떻게 번갈아가면서 동시에 진행할지 잘 설계해야 되죠. 반면에 n대n으로 요리대결을 할 때는 동시성과 병렬성을 함께 고려해야 합니다. 각자의 작업 배분과 진행 순서를 잘 설계한 다음에 실수 없이 동시에 각자 할 일을 잘 진행해야만 하죠.&lt;/p>
&lt;p>먼저 1대1 대결입니다. 조리대는 하나, 요리사도 한 명이죠. 국이 끓는 동안 손 놓고 기다리면 시간이 아까우니까, 그 사이에 재료를 손질하고 오븐을 돌립니다. 이렇게 &lt;code>기다리는 시간에 다른 일을 끼워 넣는 설계&lt;/code>가 동시성입니다.&lt;/p>
&lt;pre class="mermaid">flowchart TB
subgraph ONE[&amp;#34;1대1 대결 = 동시성 (조리대 1개, 요리사 1명이 여러 일을 번갈아 처리)&amp;#34;]
direction TB
A1[&amp;#34;① 국 불에 올리기&amp;#34;] --&amp;gt; A2[&amp;#34;② 재료 손질&amp;#34;] --&amp;gt; A3[&amp;#34;③ 국 젓기&amp;#34;] --&amp;gt; A4[&amp;#34;④ 오븐에 넣기&amp;#34;] --&amp;gt; A5[&amp;#34;⑤ 소스 만들기&amp;#34;] --&amp;gt; A6[&amp;#34;⑥ 국 간 맞추기&amp;#34;] --&amp;gt; A7[&amp;#34;⑦ 오븐에서 꺼내기&amp;#34;] --&amp;gt; A8[&amp;#34;완성&amp;#34;]
A1 -. &amp;#34;불에 올리고 손을 뗌&amp;#34; .-&amp;gt; W1[&amp;#34;국이 끓는 중&amp;lt;br/&amp;gt;(손 안 대고 기다리는 시간)&amp;#34;]
W1 -. &amp;#34;이 시간에 ② ③을 처리&amp;#34; .-&amp;gt; A3
A4 -. &amp;#34;오븐에 넣고 손을 뗌&amp;#34; .-&amp;gt; W2[&amp;#34;오븐이 굽는 중&amp;lt;br/&amp;gt;(기다리는 시간)&amp;#34;]
W2 -. &amp;#34;이 시간에 ⑤ ⑥을 처리&amp;#34; .-&amp;gt; A6
end&lt;/pre>&lt;p>다음은 n대n 대결입니다. 조리대도 여러 개, 요리사도 여러 명이죠. 누가 뭘 맡을지 나눈 다음에는 각자 자기 조리대에서 &lt;code>동시에 병렬로&lt;/code> 작업합니다. 물론 그 와중에도 요리사 한 명 한 명은 여전히 자기가 해야 할 일에 대해서 동시성을 고민해야 합니다.&lt;/p>
&lt;pre class="mermaid">flowchart LR
D[&amp;#34;작업 배분&amp;lt;br/&amp;gt;(누가 뭘 맡을지 설계)&amp;#34;]
subgraph MANY[&amp;#34;n대n 대결 = 병렬성 (조리대 3개, 요리사 3명이 각자 맡은 일을 동시에 처리)&amp;#34;]
direction LR
subgraph C1[&amp;#34;요리사 A (조리대 1)&amp;#34;]
direction LR
B1[&amp;#34;국 끓이기&amp;#34;] --&amp;gt; B2[&amp;#34;간 맞추기&amp;#34;]
end
subgraph C2[&amp;#34;요리사 B (조리대 2)&amp;#34;]
direction LR
B3[&amp;#34;재료 손질&amp;#34;] --&amp;gt; B4[&amp;#34;오븐에 굽기&amp;#34;]
end
subgraph C3[&amp;#34;요리사 C (조리대 3)&amp;#34;]
direction LR
B5[&amp;#34;소스 만들기&amp;#34;] --&amp;gt; B6[&amp;#34;플레이팅&amp;#34;]
end
end
E[&amp;#34;완성&amp;#34;]
D --&amp;gt; B1
D --&amp;gt; B3
D --&amp;gt; B5
B2 --&amp;gt; E
B4 --&amp;gt; E
B6 --&amp;gt; E&lt;/pre>&lt;blockquote>
&lt;p>뒤에서 살펴볼 런타임 얘기로 바꿔 말하면, 조리대는 CPU 코어, 요리사는 OS 스레드, 각 요리 작업은 goroutine이나 Task 같은 작업 단위에 해당합니다. 조리대가 하나뿐이어도 동시성 설계는 필요하고, 조리대가 여러 개라야 동시에 작업할 수 있는 병렬성이 생기는 거죠.&lt;/p>&lt;/blockquote>
&lt;p>각 언어의 런타임은 이런 동시성과 병렬성을 해결하는 모습이 조금씩 다릅니다. 흥미를 위해서 그 부분을 먼저 확인해보고 넘어갈까 합니다.&lt;/p>
&lt;h2 id="go">Go&lt;/h2>
&lt;p>Go는 &lt;code>동시성을 위해서 goroutine을 사용합니다.&lt;/code> goroutine은 OS 스레드와 달리 매우 작은 2KB의 스택에서 시작하기 때문에 수십만 개까지도 생성이 가능하고, OS 스레드에서 처리할 goroutine을 쉽게 전환할 수 있기 때문에 goroutine의 전환은 OS 스레드의 문맥 교환(context switching)이 필요하지 않습니다.&lt;/p>
&lt;blockquote>
&lt;p>참고로 OS 스레드의 문맥 교환은 대략 다음과 같은 과정을 거칩니다. *표시는 특히 많은 시간이 소요되는 과정을 나타냅니다.&lt;/p>
&lt;ul>
&lt;li>커널 모드 진입*: CPU는 필요한 권한을 얻기 위해 유저 모드에서 커널 모드로 진입&lt;/li>
&lt;li>스레드1의 상태 저장: CPU 레지스터, 프로그램 카운터, 스택 포인터 등 스레드1의 모든 상태를 스레드1의 메모리 블록에 기록(TCB, Thread Control Block)&lt;/li>
&lt;li>스케줄러 상태 갱신: OS가 스레드1을 대기 상태로 설정하고 스레드2를 대기 큐에서 선택&lt;/li>
&lt;li>TLB/cache 초기화*: 스레드1과 스레드2가 서로 다른 프로세스의 스레드라면, TLB(translation lookaside buffer)를 초기화&lt;/li>
&lt;li>스레드2의 상태 불러오기: 스레드2의 TCB에서 모든 상태를 불러오기&lt;/li>
&lt;li>유저 모드로 복귀*: CPU가 유저 모드로 돌아와서 스레드2의 이전 상태로부터 계속 실행&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;p>병렬성은 논리 코어 개수를 가리키는 &lt;code>GOMAXPROCS&lt;/code> 설정을 통해 해결합니다.(기본값이 논리 코어 개수이고, 자유롭게 설정할 수 있습니다.) 런타임 스케줄러가 goroutine을 여러 OS 스레드에 배분하고, 여러 개의 CPU 코어에서 동시에 실행하면서 자연스럽게 병렬 실행이 가능해지는 구조인 거죠. Go는 GMP(Goroutine-Machine-Processor) 스케줄러를 통해 병렬성을 관리합니다.&lt;/p>
&lt;ul>
&lt;li>G (goroutine): 실행할 작업 단위. OS 스레드와 다르게 스택이 &lt;a href="https://github.com/golang/go/blob/6e676ab2b809d46623acb5988248d95d1eb7939c/src/runtime/stack.go#L76">2KB로 시작해서&lt;/a> 수십만 개 만들 수 있어요.&lt;/li>
&lt;li>M (machine): 실제 OS 스레드.&lt;/li>
&lt;li>P (processor): 논리 프로세서. 각 P가 자기 고루틴 실행 큐를 갖고, M이 P를 하나 획득해야 고루틴을 실행할 수 있어요.&lt;/li>
&lt;/ul>
&lt;p>각 G, M, P를 통해 병렬성을 관리하는 과정을 도식화 해보면 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">flowchart TD
subgraph HW[&amp;#34;하드웨어 (4코어)&amp;#34;]
direction LR
C1[코어 1]
C2[코어 2]
C3[코어 3]
C4[코어 4]
end
OS[&amp;#34;OS 스케줄러 (커널)&amp;lt;br/&amp;gt;M을 어느 코어에서 돌릴지 결정 — Go는 관여하지 않음&amp;lt;br/&amp;gt;M은 코어에 고정되지 않고 자유롭게 옮겨짐&amp;#34;]
subgraph MS[&amp;#34;OS 스레드 M (런타임이 필요한 만큼 생성, 기본 상한은 최대 10000개)&amp;#34;]
direction LR
M1[M1]
M2[M2]
M3[M3]
M4[M4]
M5[&amp;#34;M5&amp;lt;br/&amp;gt;blocking syscall 중&amp;lt;br/&amp;gt;P 반납, Go 코드 실행 불가&amp;#34;]
M6[&amp;#34;M6&amp;lt;br/&amp;gt;유휴 (idle)&amp;lt;br/&amp;gt;P를 못 얻어 대기&amp;#34;]
end
subgraph GO[&amp;#34;Go 런타임 스케줄러 (유저 공간)&amp;#34;]
direction TB
subgraph PS[&amp;#34;P = 실행 허가증 + 로컬 큐 + mcache (GOMAXPROCS = 4 → 동시에 Go 코드를 돌리는 M도 최대 4개)&amp;#34;]
direction LR
P0[&amp;#34;P0&amp;lt;br/&amp;gt;실행 중: G1&amp;lt;br/&amp;gt;큐: G5 G9&amp;#34;]
P1[&amp;#34;P1&amp;lt;br/&amp;gt;실행 중: G2&amp;lt;br/&amp;gt;큐: G6&amp;#34;]
P2[&amp;#34;P2&amp;lt;br/&amp;gt;실행 중: G3&amp;lt;br/&amp;gt;큐: G7 G8&amp;#34;]
P3[&amp;#34;P3&amp;lt;br/&amp;gt;실행 중: G4&amp;lt;br/&amp;gt;큐: (비어 있음)&amp;#34;]
end
GQ[&amp;#34;글로벌 큐&amp;lt;br/&amp;gt;G10 G11 G12 … 100K+&amp;lt;br/&amp;gt;(스택 2 KB로 시작, 필요 시 확장)&amp;#34;]
NP[&amp;#34;netpoller&amp;lt;br/&amp;gt;네트워크 I/O 대기 중인 G&amp;lt;br/&amp;gt;M을 블록하지 않음&amp;#34;]
end
C1 --- OS
C2 --- OS
C3 --- OS
C4 --- OS
OS -. &amp;#34;코어에 배치 (시점마다 달라짐)&amp;#34; .-&amp;gt; M1
OS -.-&amp;gt; M2
OS -.-&amp;gt; M3
OS -.-&amp;gt; M4
OS -.-&amp;gt; M5
OS -.-&amp;gt; M6
M1 -- &amp;#34;P 획득&amp;#34; --&amp;gt; P0
M2 -- &amp;#34;P 획득&amp;#34; --&amp;gt; P1
M3 -- &amp;#34;P 획득&amp;#34; --&amp;gt; P2
M4 -- &amp;#34;P 획득&amp;#34; --&amp;gt; P3
GQ -. &amp;#34;① 로컬 큐가 비면 글로벌 큐에서 G를 가져옴&amp;lt;br/&amp;gt;(61틱마다 1번은 로컬 큐보다 먼저 확인 → 기아 방지)&amp;#34; .-&amp;gt; P3
NP -. &amp;#34;② 글로벌 큐도 비면 I/O 완료된 G 확인&amp;#34; .-&amp;gt; P3
P2 -. &amp;#34;③ 그래도 없으면 다른 P의 로컬 큐에서 절반 훔쳐옴&amp;lt;br/&amp;gt;(work stealing, 마지막 수단)&amp;#34; .-&amp;gt; P3&lt;/pre>&lt;ul>
&lt;li>위 예시에서는 CPU에서 사용 가능한 &lt;code>논리 코어가 4개&lt;/code>(&lt;code>GOMAXPROCS&lt;/code>가 4로 설정된 상태)인 상태를 예시로 보여주고 있습니다.&lt;/li>
&lt;li>OS 스케줄러는 각 OS 스레드(M)가 어떤 논리 코어에서 작동할지를 결정합니다.&lt;/li>
&lt;li>논리 코어가 4개이기 때문에 &lt;code>OS 스레드가 사용할 수 있는 프로세서(P)도 4개&lt;/code>입니다. &lt;code>동시에 처리할 수 있는 작업 수가 4개&lt;/code>인 셈이죠.&lt;/li>
&lt;li>각 OS 스레드(M)는 작업을 위해 프로세서(P)를 획득해야 합니다.&lt;/li>
&lt;li>3번 프로세서(P3)를 보면 현재 goroutine G4를 실행 중이고, 로컬 큐에 다음 작업이 없기 때문에 G4가 끝나면 처리할 작업이 없는 상태입니다.&lt;/li>
&lt;li>① G4가 끝나면 우선 글로벌 큐에서 goroutine을 가져옵니다.&lt;/li>
&lt;li>② 글로벌 큐에도 작업이 없다면 완료된 I/O 작업이 있는지 확인합니다.&lt;/li>
&lt;li>③ 글로벌 큐와 완료된 I/O 작업도 없다면, 다른 프로세서(P)에서 goroutine 절반을 가져옵니다.&lt;/li>
&lt;/ul>
&lt;p>매우 간결하면서도 효과적으로 동시성, 병렬성을 관리하고 있습니다.&lt;/p>
&lt;h3 id="정리">정리&lt;/h3>
&lt;p>정리하자면,&lt;/p>
&lt;ul>
&lt;li>&lt;code>P(프로세서)&lt;/code>는 Go 런타임이 만드는 자료구조로, Go 코드를 실행하기 위한 작업대입니다 (작업 더미인 로컬 큐, 공구함인 mcache 포함).&lt;/li>
&lt;li>&lt;code>P&lt;/code>는 &lt;code>GOMAXPROCS&lt;/code> 개수만큼 생성된다. 기본값은 논리 코어 수이며, Go 1.25부터는 cgroup CPU 제한도 반영합니다.&lt;/li>
&lt;li>&lt;code>M(OS 스레드)&lt;/code>은 고루틴을 실행하는 주체다. 런타임이 필요에 따라 생성하며(ex: goroutine이 특정 &lt;code>M&lt;/code>을 독점하면, 그 &lt;code>M&lt;/code>은 다른 goroutine을 실행하지 못하므로 런타임이 추가로 &lt;code>M&lt;/code>을 생성), 기본 상한은 10,000개입니다.&lt;/li>
&lt;li>&lt;code>M&lt;/code>이 어떤 코어에서 실행될지는 &lt;code>OS 스케줄러&lt;/code>가 결정한다. Go는 관여하지 않습니다.&lt;/li>
&lt;li>&lt;code>M&lt;/code>은 &lt;code>P&lt;/code>를 획득해야만 고루틴을 실행할 수 있다. 따라서 동시에 Go 코드를 실행하는 &lt;code>M&lt;/code>은 최대 &lt;code>GOMAXPROCS&lt;/code>개입니다.&lt;/li>
&lt;/ul>
&lt;h2 id="nodejs">Node.js&lt;/h2>
&lt;p>Node.js는 싱글 스레드 기반으로 실행되며 파일, 네트워크 I/O 등의 이벤트가 발생할 때마다 작업 큐에 쌓아뒀다가 해당 이벤트들을 처리할 순서가 되면 큐에서 작업을 꺼내 순차적으로 처리합니다. 즉, I/O 중심의 동시성 작업 처리를 가장 잘할 수 있는 구조를 선택하고 있습니다. 우선 Node.js의 동시성 처리 구조에 대해서 좀 더 자세히 알아볼까 합니다.&lt;/p>
&lt;p>Node.js는 다음과 같이 JS 영역과 C/C++기반의 네이티브 런타임 영역으로 구성되어 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./01.png" width="800px"/>&lt;/p>
&lt;p>Node.js 는 JS 코드를 실행해야 할 때마다 V8에게 실행을 요청합니다. 스크립트 코드 실행, 이벤트 루프의 콜백(파일, 네트워크 I/O 등) 등이 발생할 때마다 V8에게 실행을 요청하는 거죠. 이때, 네이티브 영역에서 JS 영역으로 넘어가게 되며 JS의 호출이 모두 끝났을 때(JS 호출 스택이 완전히 비었을 때) 다시 네이티브 영역으로 돌아오게 됩니다.&lt;/p>
&lt;p>스크립트 실행 후 발생하는 각 이벤트의 콜백을 처리하기 위해서 네이티브 영역의 libuv는 여러 단계로 구성된 루프를 순환하면서 각 단계의 콜백들을 순차적으로 처리합니다.&lt;/p>
&lt;pre class="mermaid">flowchart TD
Main[&amp;#34;메인 스크립트 실행&amp;lt;br/&amp;gt;(이것도 &amp;#39;콜백 하나&amp;#39;로 취급)&amp;#34;]
Main --&amp;gt; MT
subgraph MT[&amp;#34;마이크로태스크 체크포인트 — JS 콜백 하나가 리턴해 호출 스택이 빌 때마다 실행&amp;#34;]
direction LR
NT[&amp;#34;① process.nextTick 큐&amp;lt;br/&amp;gt;빌 때까지 전부 실행&amp;lt;br/&amp;gt;(도중에 추가된 것도 계속)&amp;#34;]
PR[&amp;#34;② Promise 큐&amp;lt;br/&amp;gt;.then / await 이후 / queueMicrotask&amp;lt;br/&amp;gt;빌 때까지 전부 실행&amp;lt;br/&amp;gt;(도중에 추가된 것도 계속)&amp;#34;]
NT --&amp;gt; PR
PR --&amp;gt;|&amp;#34;②에서 nextTick이 새로 잡혔으면 ①로&amp;#34;| NT
end
MT --&amp;gt;|&amp;#34;두 큐가 모두 빈 뒤에야 다음으로&amp;#34;| Timers
subgraph Loop[&amp;#34;이벤트 루프 — 한 바퀴 (각 단계는 자기 큐의 콜백을 순서대로 실행)&amp;#34;]
Timers[&amp;#34;timers&amp;lt;br/&amp;gt;만료된 setTimeout / setInterval 콜백&amp;#34;]
Pending[&amp;#34;pending callbacks&amp;lt;br/&amp;gt;이전 바퀴에서 미뤄진 I/O 콜백&amp;#34;]
Poll[&amp;#34;poll&amp;lt;br/&amp;gt;준비된 I/O 이벤트 수집 → 콜백 실행&amp;#34;]
Wait[&amp;#34;OS에 블로킹 대기 (epoll / kqueue / IOCP)&amp;lt;br/&amp;gt;스레드가 멈추므로 CPU 사용 없음&amp;lt;br/&amp;gt;대기 시간 = 가장 가까운 타이머까지 남은 시간&amp;#34;]
Check[&amp;#34;check&amp;lt;br/&amp;gt;setImmediate 콜백&amp;#34;]
Close[&amp;#34;close callbacks&amp;lt;br/&amp;gt;socket.on(&amp;#39;close&amp;#39;) 등&amp;#34;]
Exit[&amp;#34;루프 종료 → 프로세스 종료&amp;#34;]
Timers --&amp;gt; Pending --&amp;gt; Poll
Poll --&amp;gt;|&amp;#34;실행할 콜백 없음 + setImmediate 예약 없음&amp;#34;| Wait
Wait --&amp;gt;|&amp;#34;I/O 도착 → 깨어나서 poll 안에서 즉시 콜백 실행&amp;#34;| Poll
Wait --&amp;gt;|&amp;#34;타이머 시각 도래 → 이벤트 없어도 깨어남&amp;#34;| Check
Poll --&amp;gt;|&amp;#34;콜백 다 돌림 또는 setImmediate 예약됨&amp;#34;| Check
Check --&amp;gt; Close
Close --&amp;gt;|&amp;#34;살아있는 타이머·핸들 있음 → 다음 바퀴&amp;#34;| Timers
Close --&amp;gt;|&amp;#34;타이머·핸들·예약 아무것도 없음&amp;#34;| Exit
end
Loop -.-&amp;gt;|&amp;#34;어느 단계든 콜백 하나 끝날 때마다&amp;#34;| MT&lt;/pre>&lt;p>여기서 주목해야 할 부분은 두 가지입니다.&lt;/p>
&lt;ul>
&lt;li>메인 스크립트 실행(&lt;code>CommonJS 기준&lt;/code>, 이벤트 루프 진입 전 1회)&lt;/li>
&lt;li>이벤트 루프에서 콜백 하나가 종료될 때마다 실행되는 마이크로태스크&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>위 두 가지 경우 모두 JS 코드 실행을 위해서 네이티브-&amp;gt;JS 영역으로 진입했다가 JS 실행이 모두 끝나서 다시 네이티브 영역으로 돌아오는 상황입니다. 즉, JS 실행 후 네이티브 영역으로 돌아올 때마다 마이크로태스크 큐(nextTick 큐 -&amp;gt; Promise 큐 순서) 정리 작업을 진행하는 셈이죠!&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>참고로 만료된 타이머 여러 개가 있는 경우 각 타이머 콜백을 JS의 반복문 안에서 순차적으로 처리하기 때문에 네이티브 영역으로 돌아가지 않지만, JS 코드에서 runNextTicks()를 명시적으로 호출해서 네이티브로 돌아간 것과 같은 효과를 냅니다. 그리고 setImmediate도 같은 방식으로 처리됩니다.&lt;/p>&lt;/blockquote>
&lt;h3 id="메인-스크립트-실행">메인 스크립트 실행&lt;/h3>
&lt;p>어떻게 메인 스크립트를 실행하고 이벤트 루프에 진입하는지는 모듈의 형식에 따라 달라집니다. 그 이유는 &lt;a href="https://netscout.github.io/posts/node-js-experimental-vm-modules/">CommonJS, ESM 모듈을 처리하는 방식이 다르기 때문&lt;/a>인데요. 아주 간단한 express 예제를 통해 차이점을 알아보겠습니다.&lt;/p>
&lt;blockquote>
&lt;p>각 모듈의 차이점에 대해서 더 자세한 내용은 &lt;a href="https://netscout.github.io/posts/node-js-experimental-vm-modules/">Node.js: &amp;ndash;experimental-vm-modules&lt;/a>를 참조하세요!&lt;/p>&lt;/blockquote>
&lt;h4 id="commonjs">CommonJS&lt;/h4>
&lt;p>아주 간단한 express를 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">// server.js (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">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&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 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>&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">get&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="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="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="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hello&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="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="mi">3000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&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;listening&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 내부적으로 콜백을 process.nextTick으로 등록
&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">nextTick&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&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;tick&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="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">then&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&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;promise&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;end of script&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>아래쪽의 4줄을 보면, 총 3개의 콜백과 콘솔 출력이 있죠? 일단 결과를 볼까요?&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 server.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end of script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">listening
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tick
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">promise
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>end of script&lt;/code> -&amp;gt; &lt;code>listening&lt;/code> -&amp;gt; &lt;code>tick&lt;/code> -&amp;gt; &lt;code>promise&lt;/code>의 순서로 출력되네요. 그러니까,&lt;/p>
&lt;ul>
&lt;li>console.log 실행: &lt;code>end of script&lt;/code>&lt;/li>
&lt;li>process.nextTick: &lt;code>listening&lt;/code>&lt;/li>
&lt;li>process.nextTick: &lt;code>tick&lt;/code>&lt;/li>
&lt;li>promise: &lt;code>promise&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>이런 순서로 실행된 건데요, 앞서 살펴봤던 도식의 &lt;code>메인 스크립트 실행&lt;/code> 후 리턴하면서 콜스택이 비었기 때문에 &lt;code>process.nextTick 큐&lt;/code> -&amp;gt; &lt;code>promise 큐&lt;/code>의 순서로 큐를 비우면서 콜백을 실행한 결과입니다.&lt;/p>
&lt;p>CommonJS는 스크립트를 실행하기 전에, &lt;a href="https://github.com/nodejs/node/blob/577b9c2bd86e878d1b3c06fa922afb2080fc785e/lib/internal/modules/cjs/loader.js#L412">스크립트의 내용을 다음과 같은 wrapper 함수로 감싸서 함수 형태로 만듭니다.&lt;/a>&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="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">__filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">__dirname&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">// server.js 내용 전체
&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;/code>&lt;/pre>&lt;/div>&lt;p>그리고 V8에게 이 함수의 실행을 요청합니다.&lt;/p>
&lt;blockquote>
&lt;p>더 자세하게 설명하면 &lt;a href="https://github.com/nodejs/node/blob/577b9c2bd86e878d1b3c06fa922afb2080fc785e/src/node.cc#L403">V8이 run_main_module.js를 실행하는 과정&lt;/a>에서 함수 wrapping 및 실행이 진행됩니다.&lt;/p>&lt;/blockquote>
&lt;p>그리고 메인 스크립트의 콜 스택이 모두 비어서 실행이 종료되면 V8은 JS 실행을 종료하고 리턴합니다. 그리고는 마이크로태스크 큐에 있는 작업들을 nextTick -&amp;gt; promise의 순서로 모두 실행하게 됩니다. 그래서 다음과 같은 순서로 출력이 진행되죠:&lt;/p>
&lt;ol>
&lt;li>메인 스크립트의 console.log(&amp;rsquo;end of script&amp;rsquo;);&lt;/li>
&lt;li>nextTick에 등록된 순서대로, console.log(&amp;rsquo;listening&amp;rsquo;), console.log(&amp;rsquo;tick')&lt;/li>
&lt;li>다음으로 promise에 등록된 console.log(&amp;lsquo;promise&amp;rsquo;)&lt;/li>
&lt;/ol>
&lt;h4 id="esm">ESM&lt;/h4>
&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">// server.mjs
&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="nx">express&lt;/span> &lt;span class="nx">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 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>&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">get&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="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="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="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hello&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="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="mi">3000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&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;listening&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 내부적으로 콜백을 process.nextTick으로 등록
&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">nextTick&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&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;tick&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="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">then&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&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;promise&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;end of script&amp;#39;&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node server.mjs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">end of script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">promise
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">listening
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tick
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이번에는 &lt;code>end of script&lt;/code> -&amp;gt; &lt;code>promise&lt;/code> -&amp;gt; &lt;code>listening&lt;/code> -&amp;gt; &lt;code>tick&lt;/code> 의 순서로 출력되네요. 그러니까,&lt;/p>
&lt;ul>
&lt;li>console.log 실행: &lt;code>end of script&lt;/code>&lt;/li>
&lt;li>promise: &lt;code>promise&lt;/code>&lt;/li>
&lt;li>process.nextTick: &lt;code>listening&lt;/code>&lt;/li>
&lt;li>process.nextTick: &lt;code>tick&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>이런 순서로 출력된 셈이죠. 마이크로태스크 작업 중에 process.nextTick 대신에 promise 큐의 작업이 먼저 실행된 건데요. 그 과정을 간략하게 살펴보겠습니다.&lt;/p>
&lt;p>ESM의 경우 CommonJS처럼 함수로 wrapping하지 않고, 모듈이 비동기(async)방식으로 로드됩니다. CommonJS와 동일하게 실행되다가 &lt;a href="https://github.com/nodejs/node/blob/577b9c2bd86e878d1b3c06fa922afb2080fc785e/lib/internal/modules/run_main.js#L147">ESM 로더를 사용해야 하는지&lt;/a> 판단 과정을 거친 뒤에 &lt;a href="https://github.com/nodejs/node/blob/577b9c2bd86e878d1b3c06fa922afb2080fc785e/lib/internal/modules/run_main.js#L122">비동기로 모듈을 로드&lt;/a>하게 됩니다.&lt;/p>
&lt;p>그러니까 Node.js는 모듈 로드 작업을 promise 체인으로 걸고 메인 스크립트 실행을 종료합니다. 파일 자체는 동기로 읽지만, 모듈 로더는 async 함수이기 때문에 이후의 작업은 모두 promise 큐에서 진행됩니다. 그래서 메인 스크립트가 끝나고 마이크로태스크를 비우는 시점에 본문이 실행됩니다. 아직은 이벤트 루프가 실행되지 않은 상태이고, 대략 다음과 같이 진행됩니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">메인 스크립트(run_main_module) 종료 → 마이크로태스크 비우기
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ nextTick 큐 (비어 있음)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └ Promise 큐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ 로더의 await 이어짐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├ … link, instantiate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └ module.evaluate() → server.mjs 본문 실행 ← 여기
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └ 본문에서 건 .then → 같은 큐에 붙어 바로 이어서 실행
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └ 본문에서 건 nextTick → Promise 큐 다 빈 뒤 실행
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그렇기 때문에 nextTick보다 promise 콜백이 먼저 화면에 출력된 거죠!&lt;/p>
&lt;h4 id="io는-누가-기다리나">I/O는 누가 기다리나&lt;/h4>
&lt;p>I/O의 성격에 따라 메인 스레드(정확히는 OS 커널)와 별도의 스레드가 I/O 이벤트를 대기 및 처리합니다.&lt;/p>
&lt;pre class="mermaid">flowchart LR
JS[&amp;#34;메인 스레드 (V8 + 이벤트 루프)&amp;#34;]
OS[&amp;#34;OS 이벤트 알림&amp;lt;br/&amp;gt;epoll / kqueue / IOCP&amp;#34;]
TP[&amp;#34;libuv 스레드 풀&amp;lt;br/&amp;gt;기본 4개, UV_THREADPOOL_SIZE로 최대 1024&amp;#34;]
JS -- &amp;#34;소켓, 타이머&amp;#34; --&amp;gt; OS
JS -- &amp;#34;fs, dns.lookup, crypto, zlib&amp;#34; --&amp;gt; TP
OS -- &amp;#34;완료 이벤트&amp;#34; --&amp;gt; JS
TP -- &amp;#34;완료 이벤트&amp;#34; --&amp;gt; JS&lt;/pre>&lt;p>libuv는 이벤트 루프를 관리하면서 필요한 작업을 스레드 풀에 대기 중인 스레드에 할당합니다. 스레드 풀에는 기본적으로 4개의 스레드가 생성되는데요, 만약 4개의 스레드가 무언가 스레드 블록(block)이 필요한 I/O 작업을 진행 중이라면, dns.lookup을 진행해야 되는 HTTP 요청이 막힐 수도 있습니다!&lt;/p>
&lt;h3 id="nodejs의-병렬성">Node.js의 병렬성&lt;/h3>
&lt;p>Node.js에서 병렬성을 확보하기 위해서는 별도의 worker_threads나 프로세스를 클러스터링해야 하는데요, 다음은 worker_threads를 사용하는 예시입니다.&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">// worker_thread.mjs
&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">Worker&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isMainThread&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">parentPort&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">workerData&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;node:worker_threads&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">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">isMainThread&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="kr">const&lt;/span> &lt;span class="nx">w&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Worker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">URL&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 class="p">{&lt;/span> &lt;span class="nx">workerData&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">10000000000&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">w&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;message&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">sum&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&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="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="p">}&lt;/span> &lt;span class="k">else&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="kd">let&lt;/span> &lt;span class="nx">sum&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="nx">workerData&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">sum&lt;/span> &lt;span class="o">+=&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="nx">parentPort&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">postMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sum&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;/code>&lt;/pre>&lt;/div>&lt;p>그리고 웹서버로 Node.js를 사용하는 경우, 일반적으로 PM2 같은 오케스트레이터를 사용하여 프로세스를 클러스터링하거나 k8s 등의 환경에 배포합니다.&lt;/p>
&lt;h3 id="정리-1">정리&lt;/h3>
&lt;p>지금까지 확인한 내용을 정리해보면,&lt;/p>
&lt;ul>
&lt;li>CPU를 많이 사용하는 작업은 이벤트 루프의 콜백에서 처리될 경우, 이벤트 루프를 블록하게 됩니다. 그래서 &lt;code>worker_threads&lt;/code>를 사용해서 별도의 스레드로 처리하거나, 작업을 아주 작게 쪼개서 이벤트 루프가 막히지 않도록 해야 합니다.&lt;/li>
&lt;li>&lt;code>nextTick&lt;/code>, &lt;code>promise&lt;/code>의 콜백 안에서 &lt;code>nextTick&lt;/code>을 계속 걸면 이벤트 루프는 다음으로 진행하지 못합니다. 그래서 타이머나 I/O의 처리가 매우 지연될 수 있습니다.&lt;/li>
&lt;li>마찬가지로 &lt;code>promise&lt;/code> 콜백 안에서 계속 &lt;code>promise&lt;/code>를 걸어도 이벤트 루프는 다음으로 진행하지 못합니다.&lt;/li>
&lt;li>&lt;code>nextTick&lt;/code>은 &lt;code>지금 콜백이 끝난 직후에 처리해야 하는 작업&lt;/code>에만 예외적으로 사용하며, 다음 루프에 처리하는 게 목적이라면 &lt;code>setImmediate&lt;/code>를 사용합니다.&lt;/li>
&lt;li>&lt;code>setTimeout(0)&lt;/code>보다 &lt;code>setImmediate&lt;/code>를 사용합니다. &lt;code>setTimeout&lt;/code>은 아무리 짧게 잡아도 최소 1ms의 지연이 필요합니다. 그리고 &lt;code>setImmediate&lt;/code>는 I/O 콜백을 처리하는 &lt;code>Poll&lt;/code> 단계 다음으로 오는 &lt;code>Check&lt;/code> 단계에서 처리되므로, I/O 콜백에서 &lt;code>setImmediate&lt;/code>를 걸어두면 항상 &lt;code>setTimeout(0)&lt;/code> 보다 먼저 처리됩니다.&lt;/li>
&lt;li>&lt;code>setInterval&lt;/code> 타이머는 정확하지 않습니다. 타이머가 지정된 시간에 도달하더라도 콜백을 처리하는 &lt;code>Poll&lt;/code> 단계가 오래 걸리면 처리가 밀리기 때문입니다. 다음 처리 시간을 &lt;code>setTimeout&lt;/code>으로 설정하는 게 좀 더 정확한 처리가 가능합니다.&lt;/li>
&lt;/ul>
&lt;h2 id="net">.NET&lt;/h2>
&lt;p>.NET은 런타임이 관리하는 ThreadPool을 통해 비동기 작업을 나타내는 Task 실행을 조율합니다. async/await를 통해 I/O 등의 비동기 작업을 실행할 때는 관련 코드를 상태 머신으로 전환하여 스레드를 점유하지 않고 완료를 기다리도록 합니다.&lt;/p>
&lt;blockquote>
&lt;p>.NET 10 부터 Runtime Async를 실험적인 기능(Preview)으로 제공하고 있습니다. 원래 async/await는 컴파일러가 상태 머신으로 변환해야 했습니다. 컴파일러가 코드를 컴파일하다가 async가 붙어있는 메서드를 보면, &lt;code>IAsyncStateMachine&lt;/code>을 구현하는 상태 머신 구조체로 변환해서 비동기 작업이 완료될 때 까지 정지할 수 있도록 했었습니다. 그런데 Runtime Async의 경우 컴파일러는 &lt;code>[MethodImpl(MethodImplOptions.Async)]&lt;/code> 어트리뷰트가 붙은 IL 코드를 생성하기만 하고, 이후의 비동기 작업 처리는 런타임이 처리하도록 한 거죠. 이렇게 하면 상태 머신의 오버헤드를 줄일 수 있고, 스택 트레이스도 깔끔해지는 등 여러가지 장점이 있습니다. 다만, 아직은 Preview 단계라서 어떤 부분에서 더 성능이 향상되는지 면밀한 검토가 필요해보입니다. 더 자세한 내용은 이 글 끝에 있는 참고 자료를 확인해주세요!&lt;/p>&lt;/blockquote>
&lt;p>그리고 ThreadPool이 CPU의 여러 코어를 동시에 사용하기 때문에 병렬성은 자연스럽게 따라오는 구조인데요, 앞서 살펴봤던 Go의 GMP와 유사한 구조라고 볼 수 있죠.(물론 아직 goroutine 같은 도구는 지원하지 않고, 각 스레드는 OS 스레드와 1:1로 대응되는 구조입니다!)&lt;/p>
&lt;blockquote>
&lt;p>.NET은 병렬 프로그래밍을 위해 별도의 런타임 엔진과 라이브러리를 제공합니다! 이건 좀 이따가 더 살펴보죠.&lt;/p>&lt;/blockquote>
&lt;h3 id="threadpool의-구조">ThreadPool의 구조&lt;/h3>
&lt;p>ThreadPool의 구조를 간략하게 표현하면 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">flowchart TD
subgraph TP[&amp;#34;ThreadPool (.NET 6+ 관리 코드 구현)&amp;#34;]
HQ[&amp;#34;고우선순위 글로벌 큐&amp;lt;br/&amp;gt;(.NET 10: 블로킹 직전 이관된 로컬 작업)&amp;#34;]
GQ[&amp;#34;글로벌 큐&amp;lt;br/&amp;gt;QueueUserWorkItem, Task.Run 등 외부에서 넣은 작업&amp;#34;]
subgraph W1[&amp;#34;워커 스레드 1&amp;#34;]
L1[&amp;#34;로컬 큐 (LIFO)&amp;#34;]
end
subgraph W2[&amp;#34;워커 스레드 2&amp;#34;]
L2[&amp;#34;로컬 큐 (LIFO)&amp;#34;]
end
subgraph W3[&amp;#34;워커 스레드 3&amp;#34;]
L3[&amp;#34;로컬 큐 (LIFO)&amp;#34;]
end
end
IOT[&amp;#34;I/O 완료 처리&amp;lt;br/&amp;gt;Windows: IOCP 스레드&amp;lt;br/&amp;gt;Linux/macOS: epoll/kqueue 전용 스레드 (SocketAsyncEngine)&amp;#34;]
HQ -- &amp;#34;① 최우선&amp;#34; --&amp;gt; L1
GQ -- &amp;#34;② 로컬 큐 비면&amp;#34; --&amp;gt; L1
GQ --&amp;gt; L2
GQ --&amp;gt; L3
L2 -. &amp;#34;③ work stealing (FIFO 쪽에서)&amp;#34; .-&amp;gt; L1
IOT -- &amp;#34;완료 콜백을 큐에 넣음&amp;#34; --&amp;gt; GQ
W1 -. &amp;#34;블로킹 직전 로컬 큐 전체 이관&amp;#34; .-&amp;gt; HQ&lt;/pre>&lt;ul>
&lt;li>&lt;strong>글로벌 큐&lt;/strong>: 외부에서 넣은 작업. FIFO.&lt;/li>
&lt;li>&lt;strong>로컬 큐&lt;/strong>: 워커 스레드가 실행 중 만든 자식 Task는 자기 로컬 큐에 (LIFO, 캐시 지역성).&lt;/li>
&lt;li>&lt;strong>워크 스틸링&lt;/strong>: 자기 큐가 비고 글로벌 큐도 비면 다른 워커의 로컬 큐 반대쪽에서 훔친다.&lt;/li>
&lt;li>&lt;strong>스레드 수 조절&lt;/strong>: 최소(기본 논리 코어 수)까지 즉시 생성. 그 이상은 hill climbing 알고리즘이 처리량을 측정하며 천천히 늘린다. 블로킹 감지 시 더 빨리 추가하는 로직은 .NET 6부터.&lt;/li>
&lt;li>&lt;strong>Windows 스레드 풀 옵션&lt;/strong>: &lt;code>UseWindowsThreadPool&lt;/code> 설정으로 OS 스레드 풀을 쓸 수 있다. Windows Native AOT에서는 기본값. 이 경우 &lt;code>SetMinThreads&lt;/code>/&lt;code>SetMaxThreads&lt;/code>가 무효.&lt;/li>
&lt;/ul>
&lt;p>각 스레드가 자신의 로컬 큐를 가지고 있고, 로컬 큐가 비면 글로벌 큐나 다른 스레드의 로컬 큐에서 가져오는 등의 작업은 Go의 구조와 유사하죠?&lt;/p>
&lt;blockquote>
&lt;p>.NET의 Thread는 OS 스레드와 1:1 관계이기 때문에 비동기 작업에 대해서 async/await를 사용하지 않으면, 해당 작업을 처리하는 OS 스레드는 작업이 완료될 때까지 블록(block) 됩니다. 만약 윈도우 앱이라면 UI 스레드가 블록돼서 UI가 먹통이 되는 증상이 발생하게 됩니다!&lt;/p>&lt;/blockquote>
&lt;h3 id="net의-병렬성">.NET의 병렬성&lt;/h3>
&lt;p>.NET은 쉽게 병렬성을 얻을 수 있도록 PLINQ(Parallel LINQ)와 TPL(Task Parallel Library)를 제공합니다. 각 기술들은 기존에 제공하는 SDK와 유사하게 사용할 수 있으면서도 사용 가능한 모든 CPU의 자원을 활용하여 작업을 병렬화해서 실행합니다.&lt;/p>
&lt;p align="center">&lt;img src="./tpl-architecture.png" />&lt;br />출처: https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/&lt;/p>
&lt;p>이런 라이브러리를 활용하면, 순차 버전의 반복문을 아주 간단하게 병렬화 시킬 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&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="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">sourceCollection&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="n">Process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&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="n">Parallel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ForEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sourceCollection&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="그외의-언어들">그외의 언어들&lt;/h2>
&lt;h3 id="java">Java&lt;/h3>
&lt;p>Java는 .NET과 유사하게 OS 스레드와 1:1 대응되는 Thread를 제공하면서도 goroutine과 유사한 경량 스레드인 가상 스레드(Virtual Thread, JDK 21부터)를 제공합니다. Go와의 차이점이라면 각 goroutine은 전용 스택을 가지는데, 가상 스레드는 전용 스택이 없고 캐리어 스레드(OS 스레드)의 스택 위에서 실행됩니다.&lt;/p>
&lt;p align="center">&lt;img src="./02.png" />&lt;/p>
&lt;p>Go는 실행할 goroutine을 스위칭할 때, 각 goroutine의 스택은 고정된 상태로 레지스터 몇 개만 교체하면 되기 때문에 작업의 전환이 매우 빠릅니다. 반면에 가상 스레드는 전환이 일어날 때, 캐리어 스레드의 스택에서 가상 스레드의 프레임을 빼내고, 다른 가상 스레드의 프레임으로 교체하죠. 메모리 사용을 더 효율적으로 하는 측면이 있지만, 가상 스레드 전환은 goroutine에 비해 덩치가 큰 작업이 됩니다.&lt;/p>
&lt;p>Go는 처음부터 런타임을 직접 설계해서 goroutine에 최적화된 방식으로 만들 수 있었지만, Java는 매우 역사가 오래된 언어입니다. 그래서 기존에 이미 많은 JVM의 동작 방식에 대한 역사가 쌓여있죠. 기존에 동작하는 부분에 거의 영향을 주지 않으면서도 가상 스레드를 도입하기 위해서 이런 구조를 선택했다고 하는데, 꽤나 영리하면서도 현실적인 선택으로 보입니다.&lt;/p>
&lt;h3 id="rust">Rust&lt;/h3>
&lt;p>요즘 Rust가 참 많이 뜨는 거 같아서 겸사겸사 그냥 한 번 조사해봤습니다. 최근엔 &lt;a href="https://rustfoundation.org/media/guest-post-rust-is-tier-1-language-at-microsoft/">MS에서 Rust가 기존의 C++, C#, TypeScript와 함께 1티어 언어로 선정&lt;/a>하기도 했죠!&lt;/p>
&lt;p>Rust는 앞서 알아본 4가지 언어와는 근본적으로 다른데요, Rust는 표준 라이브러리 수준의 기본 런타임만 제공하기 때문에 다른 언어처럼 async를 사용하려면 tokio 같은 외부 런타임을 사용해야 합니다. C/C++ 같은 네이티브 계열의 언어라서 그런 듯합니다.&lt;/p>
&lt;p>그리고 데이터 레이스(race)의 발생이 컴파일러 레벨에서 차단된다고 하는데요, &lt;code>데이터 레이스는 동기화되지 않은 상태에서 두 스레드가 같은 메모리에 접근하여 읽기와 쓰기를 동시에 하는 경우에 발생&lt;/code>합니다. 누가 빨리 하느냐에 따라서 예측하기 힘든 결과가 발생하는 거죠. 이걸 원천 차단하기 위해서 Rust는 다음과 같은 빌림(Borrow) 규칙을 정하고 있습니다.&lt;/p>
&lt;ul>
&lt;li>가변 참조(&amp;amp;mut)는 딱 하나만 존재&lt;/li>
&lt;li>불변 참조(&amp;amp;)는 여러 개 존재 가능&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-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="fm">vec!&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">r1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &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="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">r2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &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="fm">println!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="si">{}&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">r1&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이런 규칙이 자연스럽게 멀티 스레드를 사용하는 경우에도 전파됩니다. 스레드 10개가 counter 하나를 같이 올리는 코드를 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">std&lt;/span>::&lt;span class="n">thread&lt;/span>::&lt;span class="n">scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">spawn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">});&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 클로저마다 counter의 가변 참조(&amp;amp;mut)를 잡음
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">});&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;code>std::thread::scope&lt;/code>는 안에서 만든 스레드가 모두 끝날 때까지 기다려주는 스레드 묶음입니다. 그래서 스레드가 바깥의 변수를 빌려 쓸 수 있죠.&lt;/p>&lt;/blockquote>
&lt;p>각 클로저는 counter를 고쳐야 하니까 counter의 가변 참조를 하나씩 잡습니다. 10개의 스레드가 동시에 살아있으니 가변 참조도 10개가 되죠. 그래서 &lt;code>가변 참조(&amp;amp;mut)는 딱 하나만 존재&lt;/code> 규칙에 위배되므로 컴파일 에러입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-mysql" data-lang="mysql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">error&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">E0499&lt;/span>&lt;span class="p">]:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cannot&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">borrow&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mutable&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">more&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">than&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">once&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">at&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">time&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>참고로 &lt;code>scope&lt;/code> 없이 &lt;code>std::thread::spawn&lt;/code>을 바로 쓰면 다른 에러가 먼저 나옵니다. spawn으로 만든 스레드는 main 함수보다 오래 살 수도 있어서, 곧 사라질지도 모르는 지역 변수를 빌려주는 것 자체가 금지되기 때문이죠.(E0373) 이 경우에는 &lt;code>move&lt;/code>를 붙여서 소유권을 넘겨야 하는데, 그러면 숫자가 스레드마다 복사되어서 각자 자기 복사본만 올리게 됩니다. 공유가 안 되는 거죠!&lt;/p>&lt;/blockquote>
&lt;p>그래서 데이터에 대한 동기화를 강제하는 타입을 사용합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Mutex&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// lock()을 통해서만 접근 가능한 타입
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">std&lt;/span>::&lt;span class="n">thread&lt;/span>::&lt;span class="n">scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">spawn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">lock&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">unwrap&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">});&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">});&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mutex는 안에 든 값을 lock()을 통해서만 만질 수 있게 해주는 상자입니다. lock()을 잡은 스레드는 한 순간에 하나뿐이니, 가변 참조도 그 순간 하나뿐이죠. 그리고 각 클로저는 counter 자체를 불변 참조(&amp;amp;)로만 빌리기 때문에 빌림 규칙에도 어긋나지 않습니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>scope&lt;/code> 없이 &lt;code>spawn&lt;/code>을 쓰는 경우에는 Arc로 감싸서 소유권을 나눠 갖습니다. Arc는 여러 스레드가 같은 값을 함께 소유하게 해주는 참조 카운터입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Arc&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Mutex&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Arc&lt;/span>::&lt;span class="n">clone&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">std&lt;/span>::&lt;span class="n">thread&lt;/span>::&lt;span class="n">spawn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">move&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">lock&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">unwrap&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">});&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&lt;p>그렇기 때문에 타입 수준에서 동기화 실수가 방지되는 측면이 있죠!&lt;/p>
&lt;p>그리고 마지막으로 타입마다 스레드의 경계를 넘어서 보낼 수 있는지 여부를 컴파일러가 판단합니다. 스레드 안전한 타입인지(thread-safe)에 따라서 컴파일러가 판단하여(Send/Sync) 그렇지 않은 경우 컴파일 에러를 발생시킵니다.&lt;/p>
&lt;p>정리하자면, 빌림(Borrow) 규칙과 Mutex 같이 동기화를 강제하는 타입, 스레드 안전 타입 여부 등을 통해서 데이터 레이스를 원천적으로 막습니다.&lt;/p>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Concurrent_computing">Concurrent computing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/">Parallel programming in .NET: A guide to the documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick">The Node.js Event Loop&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://go.dev/blog/waza-talk">Concurrency is not parallelism (Rob Pike)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://golang.org/s/go11sched">Scalable Go Scheduler Design Doc (Dmitry Vyukov)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://go.dev/doc/go1.25#container-aware-gomaxprocs">Go 1.25 Release Notes - GOMAXPROCS와 cgroup CPU 제한&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop">Don&amp;rsquo;t Block the Event Loop (or the Worker Pool)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.libuv.org/en/v1.x/threadpool.html">libuv: Thread pool work scheduling - 기본 4개, UV_THREADPOOL_SIZE, 최대 1024&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://nodejs.org/api/worker_threads.html">Node.js: Worker threads&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool">.NET: The managed thread pool&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://laurentkempe.com/2026/02/14/exploring-net-11-preview-1-runtime-async-a-dive-into-the-future-of-async-in-net/">Exploring .NET 11 Preview 1 Runtime Async: A dive into the Future of Async in .NET&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://medium.com/@schmidt.jeanbaptiste/why-the-future-of-c-is-faster-0949e30fe8bd">Why the Future of C# Is Faster&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://openjdk.org/jeps/444">JEP 444: Virtual Threads&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html">The Rust Book: References and Borrowing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doc.rust-lang.org/book/ch16-01-threads.html">The Rust Book: Using Threads to Run Code Simultaneously - thread::spawn과 move 클로저(E0373)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doc.rust-lang.org/book/ch16-03-shared-state.html">The Rust Book: Shared-State Concurrency - Mutex, Arc&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html">The Rust Book: Extensible Concurrency with Send and Sync&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>분산 시스템 설계(2) - 일관된 해싱</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-2-%EC%9D%BC%EA%B4%80%EB%90%9C-%ED%95%B4%EC%8B%B1/</link><pubDate>Sat, 12 Sep 2026 07:56:56 +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-2-%EC%9D%BC%EA%B4%80%EB%90%9C-%ED%95%B4%EC%8B%B1/</guid><description>해시 함수는 주어진 입력에 대해서 고정된 범위의 값으로 매핑합니다. 어떤 값이 주어져도 고정된 범위로 매핑해주기 때문에 분산 시스템에서는 이런 해시 알고리즘이 중요하게 사용됩니다. 분산 시스템에서는 들어오는 사용자의 입력을 분산된 시스템 중 어느 인스턴스에 전달해서 …</description><content>&lt;p>해시 함수는 &lt;code>주어진 입력에 대해서 고정된 범위의 값으로 매핑&lt;/code>합니다. 어떤 값이 주어져도 고정된 범위로 매핑해주기 때문에 분산 시스템에서는 이런 해시 알고리즘이 중요하게 사용됩니다. 분산 시스템에서는 들어오는 사용자의 입력을 분산된 시스템 중 어느 인스턴스에 전달해서 처리하게 할지, 그리고 인스턴스가 추가되거나 삭제될 때는 어떻게 할지 등의 문제를 해결해야 하기 때문이죠.&lt;/p>
&lt;h2 id="나머지-연산-해시modulo-hash">나머지 연산 해시(modulo hash)&lt;/h2>
&lt;p>가장 간단한 방법은 시스템으로 들어오는 요청을 해시로 변환하고, 그 해시를 인스턴스 개수로 나눈 나머지를 이용하는 방법입니다.&lt;/p>
&lt;p>사용자 10명을 서버 3대에 나누는 모습입니다. 각 사용자 ID를 해시한 뒤 3으로 나눈 나머지가 곧 서버 번호가 됩니다.&lt;/p>
&lt;pre class="mermaid">flowchart LR
subgraph U[&amp;#34;사용자 10명: ID를 해시한 뒤 3으로 나눈 나머지&amp;#34;]
direction TB
u1[&amp;#34;user-1&amp;lt;br/&amp;gt;hash = 0xc6c289…&amp;lt;br/&amp;gt;% 3 = 0&amp;#34;]
u2[&amp;#34;user-2&amp;lt;br/&amp;gt;hash = 0xd92b69…&amp;lt;br/&amp;gt;% 3 = 1&amp;#34;]
u3[&amp;#34;user-3&amp;lt;br/&amp;gt;hash = 0x92303a…&amp;lt;br/&amp;gt;% 3 = 1&amp;#34;]
u4[&amp;#34;user-4&amp;lt;br/&amp;gt;hash = 0xbeb218…&amp;lt;br/&amp;gt;% 3 = 2&amp;#34;]
u5[&amp;#34;user-5&amp;lt;br/&amp;gt;hash = 0x5d652c…&amp;lt;br/&amp;gt;% 3 = 1&amp;#34;]
u6[&amp;#34;user-6&amp;lt;br/&amp;gt;hash = 0x724cae…&amp;lt;br/&amp;gt;% 3 = 0&amp;#34;]
u7[&amp;#34;user-7&amp;lt;br/&amp;gt;hash = 0x920811…&amp;lt;br/&amp;gt;% 3 = 2&amp;#34;]
u8[&amp;#34;user-8&amp;lt;br/&amp;gt;hash = 0x4c485b…&amp;lt;br/&amp;gt;% 3 = 0&amp;#34;]
u9[&amp;#34;user-9&amp;lt;br/&amp;gt;hash = 0x14817f…&amp;lt;br/&amp;gt;% 3 = 2&amp;#34;]
u10[&amp;#34;user-10&amp;lt;br/&amp;gt;hash = 0xe4abd1…&amp;lt;br/&amp;gt;% 3 = 0&amp;#34;]
end
subgraph S[&amp;#34;서버 3대&amp;#34;]
direction TB
a[&amp;#34;server-a&amp;lt;br/&amp;gt;index 0 (4명)&amp;#34;]
b[&amp;#34;server-b&amp;lt;br/&amp;gt;index 1 (3명)&amp;#34;]
c[&amp;#34;server-c&amp;lt;br/&amp;gt;index 2 (3명)&amp;#34;]
end
u1 --&amp;gt; a
u6 --&amp;gt; a
u8 --&amp;gt; a
u10 --&amp;gt; a
u2 --&amp;gt; b
u3 --&amp;gt; b
u5 --&amp;gt; b
u4 --&amp;gt; c
u7 --&amp;gt; c
u9 --&amp;gt; c
classDef sa fill:#ffcccc,stroke:#333
classDef sb fill:#ccffcc,stroke:#333
classDef sc fill:#ccccff,stroke:#333
class a,u1,u6,u8,u10 sa
class b,u2,u3,u5 sb
class c,u4,u7,u9 sc&lt;/pre>&lt;p>각 사용자 ID의 해시 값을 서버 개수로 나눈 나머지에 따라 서버와 매핑되고 있습니다. 같은 ID를 해시하면 항상 같은 값이 나오기 때문에 user-1은 몇 번을 물어봐도 server-a입니다.&lt;/p>
&lt;h3 id="나머지-연산-해시-구현">나머지 연산 해시 구현&lt;/h3>
&lt;p>그럼 예시 코드를 확인해볼까요? 우선 나머지 연산 해시 관련 코드입니다.&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="c1">// src/hash.ts
&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="nx">crypto&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;node:crypto&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 나머지 연산(mod) 분배 알고리즘
&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 key 유저ID 등의 키
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param servers 서버 목록
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 분배 대상이 되는 서버 이름
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">modHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>: &lt;span class="kt">string&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">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Number&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="nx">BigInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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="k">return&lt;/span> &lt;span class="nx">servers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">index&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"> * 노드의 기본 crypto 모듈을 사용하여 입력 값을 해시한다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param input 입력 값
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param algorithm 해시 알고리즘
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 해시 결과
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">algorithm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sha256&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">bigint&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 256비트 해시 생성 -&amp;gt; 64개의 16진수 문자열(64 * 4 = 256)
&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">digest&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">crypto&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">algorithm&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">update&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">digest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hex&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">// 결과로 나온 16진수 문자열을 256비트 숫자로 변환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// number는 double 형식이므로, 정수의 범위는 -(2⁵³ - 1) ~ (2⁵³ - 1) 이므로 256bit 정수를 정확하게 표현불가.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 그래서 BigInt를 사용함. BigInt의 경우 자바스크립트 엔진에서 단일 값에 허용하는 최대 비트 수까지 할당가능.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 정수 비교를 위해 BigInt를 썼지만, sha256 해시의 결과는 항상 같은 길이의 문자열이므로 문자열을 그대로 비교해도 같은 결과를 얻을 수 있다!
&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="nx">BigInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`0x&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">digest&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;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>hash&lt;/code> 함수는 node.js에서 기본으로 제공하는 crypto 모듈을 통해 입력값의 해시를 생성합니다. 해시 알고리즘의 기본 값은 sha256으로 결과값이 항상 256비트로 구성됩니다. 그리고 나머지 연산 해시는 &lt;code>hash&lt;/code>함수를 이용해서 특정 사용자의 식별자를 해시로 변환하고, 서버 개수로 나눈 나머지를 인덱스로 사용해서 사용자와 서버를 매핑합니다.&lt;/p>
&lt;p>그리고 이어서 사용자 목록과 서버 목록을 받아서 해시 알고리즘을 적용하는 헬퍼 함수와 매핑 결과를 출력하는데 사용될 헬퍼 함수를 정의합니다.&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="c1">// src/hash.ts
&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"> * @param keys 유저ID 등의 키 목록
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param servers 서버 목록
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param hashFn 해시 알고리즘(링, 랑데뷰)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">assign&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">keys&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="nx">servers&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="nx">hashFn&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&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="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&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="kr">const&lt;/span> &lt;span class="nx">result&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="p">&amp;gt;&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">key&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">keys&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="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">hashFn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&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="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="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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">printDistribution&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">result&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&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="kr">const&lt;/span> &lt;span class="nx">distribution&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">number&lt;/span>&lt;span class="p">&amp;gt;&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">server&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">values&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">result&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">distribution&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">distribution&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="mi">0&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 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">table&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">distribution&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-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/distribution.ts
&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">assign&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">modHash&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">printDistribution&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./hash.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="kr">const&lt;/span> &lt;span class="nx">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&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="kt">10&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">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="sb">`user-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">i&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&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="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">// 해시 알고리즘과 각 해시 알고리즘 실행을 위한 hashFn 구성
&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">algorithms&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="err">(&lt;/span>&lt;span class="na">servers&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="err">[])&lt;/span> &lt;span class="err">=&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&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="kt">string&lt;/span>&lt;span class="o">&amp;gt;&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;mod&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">modHash&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">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">entries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">algorithms&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">name&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="s2">&amp;#34;--- 서버 3대 ---&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">const&lt;/span> &lt;span class="nx">servers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;server-a&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;server-b&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;server-c&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">const&lt;/span> &lt;span class="nx">before&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">printDistribution&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">before&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>사용자 10명과 서버 3대를 선언하고, 나머지 해시 알고리즘을 통해 매핑을 진행하고 결과를 확인하는 순서로 진행됩니다. 결과를 볼까요?&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; npx tsx ./src/distribution.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 class="o">=====&lt;/span> &lt;span class="nv">mod&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">4&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">3&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">3&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────┴────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>4:3:3 이라면 얼추 균등하게 요청이 분배되었네요. 유저를 1000명까지 늘려볼까요?&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; npx tsx ./src/distribution.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 class="o">=====&lt;/span> &lt;span class="nv">mod&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">339&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">347&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">314&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────┴────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>역시나 비슷한 결과가 나옵니다.&lt;/p>
&lt;h3 id="그런데-말입니다">그런데 말입니다&amp;hellip;&lt;/h3>
&lt;p>그러면 여기서 한 가지 의문이 생깁니다. &lt;code>얼추 비슷한데 그냥 나머지 연산 해시 이거면 다 되는 거 아닌가?&lt;/code> 하고 말이죠. 그러면 서버가 추가되거나 삭제될 때도 잘 동작하는지 추가로 확인해 보겠습니다.&lt;/p>
&lt;p>우선 src/hash.ts에 다음 함수를 추가합니다.&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="c1">// src/hash.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&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 previous 이전 분배 결과
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param current 재분배 결과
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 재분배 결과 발생한 이동
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">previous&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="p">&amp;gt;,&lt;/span> &lt;span class="nx">current&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&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="kd">let&lt;/span> &lt;span class="nx">moved&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">key&lt;/span> &lt;span class="k">of&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">previous&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">previous&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">current&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&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">moved&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">return&lt;/span> &lt;span class="nx">moved&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>그리고 다음과 같이 src/distribution.ts를 수정합니다.&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="c1">//src/distribution.ts
&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="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">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">entries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">algorithms&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">name&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="s2">&amp;#34;--- 서버 3대 ---&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">const&lt;/span> &lt;span class="nx">servers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;server-a&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;server-b&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;server-c&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">const&lt;/span> &lt;span class="nx">before&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">printDistribution&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">before&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;--- server-d 추가 ---&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">servers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;server-d&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">const&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">printDistribution&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">after&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">compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">before&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">after&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">users&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="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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx tsx ./src/distribution.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 class="o">=====&lt;/span> &lt;span class="nv">mod&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">339&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">347&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">314&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">--- server-d 추가 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-d │ &lt;span class="m">244&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">242&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">259&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">255&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="m">756&lt;/span> / &lt;span class="m">1000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>서버가 한 대 추가되어도 사용자의 분배는 거의 1/4에 가깝게 진행됩니다. 하지만, 이동된 사용자 수는 1/4이 아니라 3/4죠!&lt;/p>
&lt;p>서버가 상태를 가지지 않는(stateless) 웹 서버라면 굳이 문제될 건 없어 보입니다. nginx도 기본값으로 순차적으로 돌아가며 요청을 분배하는 라운드 로빈(round-robin) 방식을 사용하고, node.js 프로세스를 관리할 때 많이 사용하는 PM2 역시 라운드 로빈을 사용합니다.&lt;/p>
&lt;p>그런데 로컬 캐시와 같이 상태를 가지는 연결이라면, 75%의 사용자가 이동되는 문제는 매우 심각한 문제가 아닐까요?&lt;/p>
&lt;blockquote>
&lt;p>물론 Redis같은 전역 캐시를 사용해도 됩니다. 다만, 대규모 서비스로 갈수록 단순히 Redis만 사용하기 보다는 메모리 기반 로컬 캐시와 Redis를 조합해서 사용하는 경향이 있습니다. 로컬 캐시에서 응답할 수 있다면 Redis와 네트워크 통신에 걸리는 지연시간을 줄일 수 있기 때문이죠. DB가 업데이트 되면, pub/sub등으로 로컬 캐시를 무효화하고 다시 로드하도록 하는 등의 방식을 사용합니다.&lt;/p>&lt;/blockquote>
&lt;h2 id="링-해시ring-hash">링 해시(ring hash)&lt;/h2>
&lt;p>그래서 등장한 게 일관성 있게 요청을 분배할 수 있도록 해주는 링 해시 알고리즘입니다.&lt;/p>
&lt;p>사용자가 매핑될 수 있는 모든 해시 지점을 하나의 링(원)으로 구성한 뒤에, 그 위에 서버 인스턴스를 해시하여 배치합니다.&lt;/p>
&lt;p>SHA256을 사용하는 경우 해시 값은 0부터 2²⁵⁶ - 1까지의 아주 큰 숫자인데요, 이후 표시된 도식에서는 해시 값을 0에서 100 사이 눈금으로 줄여서 표시합니다(해시 값 ÷ 2²⁵⁶ × 100). 매핑 가능한 지점들을 하나의 링으로 구성하기 때문에 눈금 100은 다시 0으로 이어집니다.&lt;/p>
&lt;p>그러면 링 위에 서버 3대를 우선 배치해 볼까요?&lt;/p>
&lt;pre class="mermaid">graph LR
zero((&amp;#34;0&amp;lt;br/&amp;gt;(= 100)&amp;#34;))
b[&amp;#34;server-b&amp;lt;br/&amp;gt;위치 40.8&amp;#34;]
c[&amp;#34;server-c&amp;lt;br/&amp;gt;위치 76.5&amp;#34;]
a[&amp;#34;server-a&amp;lt;br/&amp;gt;위치 83.3&amp;#34;]
zero --&amp;gt;|&amp;#34;시계 방향&amp;#34;| b --&amp;gt; c --&amp;gt; a --&amp;gt;|&amp;#34;100을 지나면 다시 0&amp;#34;| zero
style zero fill:#f0f0f0,stroke:#333
style a fill:#ffcccc,stroke:#333
style b fill:#ccffcc,stroke:#333
style c fill:#ccccff,stroke:#333&lt;/pre>&lt;p>서버 이름을 해시하면 각 서버는 링 위의 한 점이 됩니다. server-b는 40.8, server-c는 76.5, server-a는 83.3에 놓였습니다.&lt;/p>
&lt;blockquote>
&lt;p>눈치채신 분도 있겠지만, 각 서버는 1/3 지점에 정확하게 매핑되지 않았습니다. 그렇기 때문에 server-b가 가장 많은 요청을, server-a가 가장 적은 요청을 받게 됩니다. 이걸 어떻게 해결할지 나중에 다시 이야기 하겠습니다!&lt;/p>&lt;/blockquote>
&lt;p>사용자 A를 서버에 매핑해야 할 때, A를 해시로 변환해서 원 위의 한 점에 놓고 시계 방향으로 가장 가까운 서버를 찾아서 사용자와 서버를 매핑합니다.&lt;/p>
&lt;blockquote>
&lt;p>링 해시 도식에서는 시작점(0)이 아래쪽에 위치하고 시계 방향으로 이동해서 다시 시작점(100)으로 이동합니다!&lt;/p>&lt;/blockquote>
&lt;pre class="mermaid">graph BT
zero((&amp;#34;0&amp;lt;br/&amp;gt;(= 100)&amp;#34;))
u7([&amp;#34;user-7&amp;lt;br/&amp;gt;3.5&amp;#34;])
u9([&amp;#34;user-9&amp;lt;br/&amp;gt;8.0&amp;#34;])
u8([&amp;#34;user-8&amp;lt;br/&amp;gt;29.7&amp;#34;])
u5([&amp;#34;user-5&amp;lt;br/&amp;gt;36.4&amp;#34;])
b[&amp;#34;server-b&amp;lt;br/&amp;gt;40.8&amp;#34;]
u6([&amp;#34;user-6&amp;lt;br/&amp;gt;44.6&amp;#34;])
u3([&amp;#34;user-3&amp;lt;br/&amp;gt;57.1&amp;#34;])
u4([&amp;#34;user-4&amp;lt;br/&amp;gt;74.4&amp;#34;])
c[&amp;#34;server-c&amp;lt;br/&amp;gt;76.5&amp;#34;]
u1([&amp;#34;user-1&amp;lt;br/&amp;gt;77.6&amp;#34;])
a[&amp;#34;server-a&amp;lt;br/&amp;gt;83.3&amp;#34;]
u2([&amp;#34;user-2&amp;lt;br/&amp;gt;84.8&amp;#34;])
u10([&amp;#34;user-10&amp;lt;br/&amp;gt;89.3&amp;#34;])
zero --&amp;gt; u7 --&amp;gt; u9 --&amp;gt; u8 --&amp;gt; u5 --&amp;gt; b --&amp;gt; u6 --&amp;gt; u3 --&amp;gt; u4 --&amp;gt; c --&amp;gt; u1 --&amp;gt; a --&amp;gt; u2 --&amp;gt; u10 --&amp;gt;|&amp;#34;다시 0으로&amp;#34;| zero
u1 -.-&amp;gt;|&amp;#34;시계 방향으로 처음 만나는 서버&amp;#34;| a
subgraph L[&amp;#34;범례: 사용자 색 = 매핑된 서버&amp;#34;]
direction LR
la[&amp;#34;server-a 담당 1명&amp;#34;]
lb[&amp;#34;server-b 담당 6명&amp;#34;]
lc[&amp;#34;server-c 담당 3명&amp;#34;]
end
classDef sa fill:#ffcccc,stroke:#333
classDef sb fill:#ccffcc,stroke:#333
classDef sc fill:#ccccff,stroke:#333
class a,la,u1 sa
class b,lb,u2,u5,u7,u8,u9,u10 sb
class c,lc,u3,u4,u6 sc
style zero fill:#f0f0f0,stroke:#333
style u1 stroke-width:3px&lt;/pre>&lt;p>user-1을 해시한 값이 77.6이기 때문에 시계 방향으로 처음 만나는 서버는 83.3의 server-a입니다. 같은 규칙으로 10명을 전부 배치하면 server-b 6명, server-c 3명, server-a 1명이 됩니다. 83.3을 지나 0으로 돌아와서 40.8까지 이어지는 긴 구간이 전부 server-b 몫이기 때문이죠.&lt;/p>
&lt;p>그럴듯해 보이지만 몇 가지 단점이 있습니다.&lt;/p>
&lt;ul>
&lt;li>링 위에 각 서버가 균등하게 배치되지 않아서 특정 서버에게 더 많은 사용자가 매핑된다.&lt;/li>
&lt;li>서버 하나가 종료되어 사라지는 경우, 그 다음 시계 방향에 위치한 서버가 모든 부담을 떠안는다.&lt;/li>
&lt;/ul>
&lt;p>그래서 이런 단점을 해소하기 위해서 각 서버 인스턴스의 가상 노드를 N개 더 만들어서 링 위에 배치합니다.&lt;/p>
&lt;pre class="mermaid">graph LR
subgraph V1[&amp;#34;가상 노드 1개: 담당 구간 a 6.8% / b 57.5% / c 35.7%&amp;#34;]
direction BT
z1((&amp;#34;0&amp;#34;))
b0[&amp;#34;server-b&amp;lt;br/&amp;gt;40.8&amp;#34;]
c0[&amp;#34;server-c&amp;lt;br/&amp;gt;76.5&amp;#34;]
a0[&amp;#34;server-a&amp;lt;br/&amp;gt;83.3&amp;#34;]
z1 --&amp;gt; b0 --&amp;gt; c0 --&amp;gt; a0 --&amp;gt; z1
end
subgraph V2[&amp;#34;가상 노드 2개: 담당 구간 a 20.1% / b 41.1% / c 38.8%&amp;#34;]
direction BT
z2((&amp;#34;0&amp;#34;))
a1[&amp;#34;server-a#1&amp;lt;br/&amp;gt;3.4&amp;#34;]
c1[&amp;#34;server-c#1&amp;lt;br/&amp;gt;6.5&amp;#34;]
b2[&amp;#34;server-b#0&amp;lt;br/&amp;gt;40.8&amp;#34;]
c2[&amp;#34;server-c#0&amp;lt;br/&amp;gt;76.5&amp;#34;]
a2[&amp;#34;server-a#0&amp;lt;br/&amp;gt;83.3&amp;#34;]
b1[&amp;#34;server-b#1&amp;lt;br/&amp;gt;90.1&amp;#34;]
z2 --&amp;gt; a1 --&amp;gt; c1 --&amp;gt; b2 --&amp;gt; c2 --&amp;gt; a2 --&amp;gt; b1 --&amp;gt; z2
end
V1 ~~~ V2
classDef sa fill:#ffcccc,stroke:#333
classDef sb fill:#ccffcc,stroke:#333
classDef sc fill:#ccccff,stroke:#333
class a0,a1,a2 sa
class b0,b1,b2 sb
class c0,c1,c2 sc
style z1 fill:#f0f0f0,stroke:#333
style z2 fill:#f0f0f0,stroke:#333&lt;/pre>&lt;p>도식의 왼쪽은 가상 노드가 1개인 경우, 그러니까 가상노드를 사용하지 않는 경우와 똑같습니다. 오른쪽은 각 서버 별로 가상 노드를 2개씩 배치하는 예시입니다. 각 점들의 배치가 조금 더 촘촘해졌는데요. 가상 노드가 없을 때 가장 큰 문제였던 server-a와 server-b의 구간에 다른 가상 노드가 몇 개 더 배치되면서 분배가 비교적 더 균등해지게 된 거죠.&lt;/p>
&lt;p>이렇게 하면 초기 서버 배치의 불평등을 완화할 수 있고, 서버의 추가/삭제에도 비교적 유연하게 대응할 수 있습니다. 그러면 링 알고리즘을 구현해봅시다!&lt;/p>
&lt;h3 id="링-해시-구현">링 해시 구현&lt;/h3>
&lt;p>다음과 같이 코드를 작성합니다.&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="c1">// src/ring.ts
&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">hash&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./hash.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="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">export&lt;/span> &lt;span class="kr">type&lt;/span> &lt;span class="nx">RingPoint&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">point&lt;/span>: &lt;span class="kt">bigint&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="nx">server&lt;/span>: &lt;span class="kt">string&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"> * 해시 링(consistent hashing)을 만든다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 서버 이름에 번호를 붙여 해시한 값이 링 위의 점이 되고, 서버 하나가 점을 vnodes개씩 가지도록 한다.
&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 servers 서버 목록
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param vnodes 서버 하나가 가지는 지점(virtual node) 수. 기본값 100.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns point 오름차순으로 정렬된 링. ringHash에 넘겨 재사용한다.
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">makeRing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">[],&lt;/span> &lt;span class="nx">vnodes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">RingPoint&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">ring&lt;/span>: &lt;span class="kt">RingPoint&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>&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">server&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">servers&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">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="nx">vnodes&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="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">ring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">point&lt;/span>: &lt;span class="kt">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">#&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">server&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="k">return&lt;/span> &lt;span class="nx">ring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sort&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="o">=&amp;gt;&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">point&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">point&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="nx">1&lt;/span> : &lt;span class="kt">a.point&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">point&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">1&lt;/span> : &lt;span class="kt">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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 해시 링(consistent hashing) 분배 알고리즘
&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"> * @param key 유저ID 등의 키
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param ring makeRing으로 만든 링
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 분배 대상이 되는 서버 이름
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">ringHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ring&lt;/span>: &lt;span class="kt">RingPoint&lt;/span>&lt;span class="p">[])&lt;/span>&lt;span class="o">:&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="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">keyPoint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&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">// 이진 탐색을 위한 양쪽 끝 경계. keyPoint보다 크거나 같은 첫 지점을 찾는다.
&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">low&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="kd">let&lt;/span> &lt;span class="nx">high&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">ring&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="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">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">low&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">high&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="kr">const&lt;/span> &lt;span class="nx">mid&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">low&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">high&lt;/span>&lt;span class="p">)&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="c1">// 중간 지점의 값이 keyPoint보다 작으면, mid이전에는 찾는 값이 없으므로 탐색 범위를 mid+1~high로 재설정
&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">ring&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">mid&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">point&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">keyPoint&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">low&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">mid&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 class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// 중간 지점의 값이 keyPoint보다 더 크면, mid이전에 찾는 값이 있을 수도 있으므로, 탐색 범위를 low~mid로 재설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">high&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">mid&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">// 마지막 지점보다 큰 keyPoint의 경우 low가 최대 ring.length까지 갈 수 있다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 그런데 ring[ring.length]는 존재하지 않으므로, % 연산으로 처음부터 순환하도록 한다.(wrap-around)
&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="nx">ring&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">low&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="nx">ring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">server&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>makeRing 함수를 통해 가상 노드 개수만큼 각 서버별로 가상노드를 생성하고, 각 가상노드의 해시 값을 링 위에 배치합니다.&lt;/p>
&lt;p>그리고 그렇게 만들어진 해시 링을 사용해서 각 사용자의 요청을 시계 방향의 가장 가까운(사용자 요청의 해시 값보다 크거나 같은) 노드에 매핑하는 거죠.&lt;/p>
&lt;h3 id="링에-배치된-서버와-매핑하는-방법-이진-탐색">링에 배치된 서버와 매핑하는 방법: 이진 탐색&lt;/h3>
&lt;p>각 사용자의 요청을 매핑하기 위해서 이진 탐색을 사용하고 있는데요, 이 과정을 좀 더 자세히 알아보겠습니다.&lt;/p>
&lt;p>가상 노드 2개짜리 링(지점 6개)에서 user-1(77.6)의 서버를 찾는 과정입니다. 정렬된 배열에서 77.6보다 크거나 같은 첫 지점을 찾습니다. low는 파란 테두리, high는 보라색 점선 테두리, mid는 노란색 칸입니다. high는 마지막 칸 다음, 즉 배열 끝(6)을 가리킬 수 있어서 #6 칸을 하나 더 그렸습니다. 회색은 탐색에서 빠진 칸입니다.&lt;/p>
&lt;pre class="mermaid">graph TD
subgraph LG[&amp;#34;범례&amp;#34;]
direction LR
lg1[&amp;#34;low&amp;lt;br/&amp;gt;탐색 범위의 시작 (포함)&amp;#34;] ~~~ lg2[&amp;#34;mid&amp;lt;br/&amp;gt;지금 비교하는 칸&amp;#34;] ~~~ lg3[&amp;#34;high&amp;lt;br/&amp;gt;탐색 범위의 끝 (미포함)&amp;#34;] ~~~ lg0[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;탐색 범위 안의 칸&amp;#34;] ~~~ lg4[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;탐색에서 빠진 칸&amp;#34;]
end
subgraph S1[&amp;#34;1단계: mid=3의 76.5 &amp;lt; 77.6 이므로 low = mid + 1 = 4&amp;#34;]
direction LR
s1_0[&amp;#34;low&amp;lt;br/&amp;gt;#0 server-a&amp;lt;br/&amp;gt;3.4&amp;#34;] ~~~ s1_1[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#1 server-c&amp;lt;br/&amp;gt;6.5&amp;#34;] ~~~ s1_2[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#2 server-b&amp;lt;br/&amp;gt;40.8&amp;#34;] ~~~ s1_3[&amp;#34;mid&amp;lt;br/&amp;gt;#3 server-c&amp;lt;br/&amp;gt;76.5&amp;#34;] ~~~ s1_4[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#4 server-a&amp;lt;br/&amp;gt;83.3&amp;#34;] ~~~ s1_5[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#5 server-b&amp;lt;br/&amp;gt;90.1&amp;#34;] ~~~ s1_6[&amp;#34;high&amp;lt;br/&amp;gt;#6&amp;lt;br/&amp;gt;(배열 끝)&amp;#34;]
end
subgraph S2[&amp;#34;2단계: mid=5의 90.1 ≥ 77.6 이므로 high = mid = 5&amp;#34;]
direction LR
s2_0[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#0 server-a&amp;lt;br/&amp;gt;3.4&amp;#34;] ~~~ s2_1[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#1 server-c&amp;lt;br/&amp;gt;6.5&amp;#34;] ~~~ s2_2[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#2 server-b&amp;lt;br/&amp;gt;40.8&amp;#34;] ~~~ s2_3[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#3 server-c&amp;lt;br/&amp;gt;76.5&amp;#34;] ~~~ s2_4[&amp;#34;low&amp;lt;br/&amp;gt;#4 server-a&amp;lt;br/&amp;gt;83.3&amp;#34;] ~~~ s2_5[&amp;#34;mid&amp;lt;br/&amp;gt;#5 server-b&amp;lt;br/&amp;gt;90.1&amp;#34;] ~~~ s2_6[&amp;#34;high&amp;lt;br/&amp;gt;#6&amp;lt;br/&amp;gt;(배열 끝)&amp;#34;]
end
subgraph S3[&amp;#34;3단계: mid=4의 83.3 ≥ 77.6 이므로 high = mid = 4&amp;#34;]
direction LR
s3_0[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#0 server-a&amp;lt;br/&amp;gt;3.4&amp;#34;] ~~~ s3_1[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#1 server-c&amp;lt;br/&amp;gt;6.5&amp;#34;] ~~~ s3_2[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#2 server-b&amp;lt;br/&amp;gt;40.8&amp;#34;] ~~~ s3_3[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#3 server-c&amp;lt;br/&amp;gt;76.5&amp;#34;] ~~~ s3_4[&amp;#34;low, mid&amp;lt;br/&amp;gt;#4 server-a&amp;lt;br/&amp;gt;83.3&amp;#34;] ~~~ s3_5[&amp;#34;high&amp;lt;br/&amp;gt;#5 server-b&amp;lt;br/&amp;gt;90.1&amp;#34;] ~~~ s3_6[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#6&amp;lt;br/&amp;gt;(배열 끝)&amp;#34;]
end
subgraph S4[&amp;#34;종료: low = high = 4 이므로 ring[4]의 server-a 선택&amp;#34;]
direction LR
s4_0[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#0 server-a&amp;lt;br/&amp;gt;3.4&amp;#34;] ~~~ s4_1[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#1 server-c&amp;lt;br/&amp;gt;6.5&amp;#34;] ~~~ s4_2[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#2 server-b&amp;lt;br/&amp;gt;40.8&amp;#34;] ~~~ s4_3[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#3 server-c&amp;lt;br/&amp;gt;76.5&amp;#34;] ~~~ s4_4[&amp;#34;low, high&amp;lt;br/&amp;gt;#4 server-a&amp;lt;br/&amp;gt;83.3&amp;#34;] ~~~ s4_5[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#5 server-b&amp;lt;br/&amp;gt;90.1&amp;#34;] ~~~ s4_6[&amp;#34;&amp;amp;nbsp;&amp;lt;br/&amp;gt;#6&amp;lt;br/&amp;gt;(배열 끝)&amp;#34;]
end
LG ~~~ S1
S1 --&amp;gt; S2 --&amp;gt; S3 --&amp;gt; S4
classDef out fill:#e0e0e0,stroke:#999,color:#888
classDef low fill:#fff,stroke:#1f6fd6,stroke-width:3px
classDef high fill:#fff,stroke:#8a2be2,stroke-width:3px,stroke-dasharray:6 3
classDef mid fill:#ffffcc,stroke:#333,stroke-width:3px
classDef found fill:#ffcccc,stroke:#333,stroke-width:3px
classDef past fill:#fafafa,stroke:#bbb,stroke-dasharray:4 3,color:#888
class lg1 low
class lg2 mid
class lg3 high
class lg4 out
class s1_0 low
class s1_3 mid
class s1_6 high
class s2_0,s2_1,s2_2,s2_3 out
class s2_4 low
class s2_5 mid
class s2_6 high
class s3_0,s3_1,s3_2,s3_3 out
class s3_4 mid
class s3_5 high
class s3_6 past
class s4_0,s4_1,s4_2,s4_3,s4_5 out
class s4_4 found
class s4_6 past&lt;/pre>&lt;p>세 번 비교하고 index 4의 server-a에 도착했습니다. 지점이 600개(서버 6대 × 가상 노드 100개)라도 10번이면 끝납니다. 만약 키가 마지막 지점 90.1보다 크면 low가 6까지 가는데, 6 % 6 = 0이므로 처음 지점인 server-a#1로 돌아갑니다. 이게 코드 마지막 줄의 wrap-around입니다.&lt;/p>
&lt;h3 id="링-해시-분배-코드-작성">링 해시 분배 코드 작성&lt;/h3>
&lt;p>이제 링 해시를 사용해서 분배를 진행하는 코드를 작성할 차례입니다.&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="c1">// src/distribution.ts
&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">makeRing&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ringHash&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./ring.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="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">// 해시 알고리즘과 각 해시 알고리즘 실행을 위한 hashFn 구성
&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">algorithms&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="err">(&lt;/span>&lt;span class="na">servers&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="err">[])&lt;/span> &lt;span class="err">=&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&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="kt">string&lt;/span>&lt;span class="o">&amp;gt;&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;mod&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">modHash&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;ring hash&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">ring&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">makeRing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">ringHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ring&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">entries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">algorithms&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">name&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="s2">&amp;#34;--- 서버 3대 ---&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">const&lt;/span> &lt;span class="nx">servers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;server-a&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;server-b&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;server-c&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">const&lt;/span> &lt;span class="nx">before&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">printDistribution&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">before&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;--- server-d 추가 ---&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">servers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;server-d&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">const&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">makeHashFn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">printDistribution&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">after&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">compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">before&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">after&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">users&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="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>링 해시 알고리즘은 분배를 진행하려면 미리 링을 구성해야 합니다. 그래서 링 해시를 진행하기 전에 makeRing 함수를 통해서 링을 먼저 만들고, 그 링을 사용하는 해시 함수를 리턴합니다.&lt;/p>
&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; npx tsx ./src/distribution.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 class="o">=====&lt;/span> &lt;span class="nv">mod&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">339&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">347&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">314&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">--- server-d 추가 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-d │ &lt;span class="m">244&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">242&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">259&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">255&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="m">756&lt;/span> / &lt;span class="nv">1000&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> ring &lt;span class="nb">hash&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">317&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">316&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">367&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">--- server-d 추가 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">215&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">268&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">280&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-d │ &lt;span class="m">237&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="m">237&lt;/span> / &lt;span class="m">1000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>링 해시는 나머지 연산 해시 보다 노드 변경에 훨씬 유연하게 대응하는 걸 볼 수 있습니다. 링 위에 새로운 점이 추가되는 거니까, 예전 노드가 더 가까운 요청들은 계속 기존의 노드와 매핑이 되기 때문에 그렇습니다.&lt;/p>
&lt;p>앞의 가상 노드 1개짜리 링에 server-d를 넣어 보겠습니다. server-d는 8.4에 놓입니다.&lt;/p>
&lt;pre class="mermaid">graph BT
zero((&amp;#34;0&amp;lt;br/&amp;gt;(= 100)&amp;#34;))
u7([&amp;#34;user-7&amp;lt;br/&amp;gt;3.5&amp;lt;br/&amp;gt;b → d 이동&amp;#34;])
u9([&amp;#34;user-9&amp;lt;br/&amp;gt;8.0&amp;lt;br/&amp;gt;b → d 이동&amp;#34;])
d[&amp;#34;server-d&amp;lt;br/&amp;gt;8.4 (새 서버)&amp;#34;]
u8([&amp;#34;user-8&amp;lt;br/&amp;gt;29.7&amp;#34;])
u5([&amp;#34;user-5&amp;lt;br/&amp;gt;36.4&amp;#34;])
b[&amp;#34;server-b&amp;lt;br/&amp;gt;40.8&amp;#34;]
u6([&amp;#34;user-6&amp;lt;br/&amp;gt;44.6&amp;#34;])
u3([&amp;#34;user-3&amp;lt;br/&amp;gt;57.1&amp;#34;])
u4([&amp;#34;user-4&amp;lt;br/&amp;gt;74.4&amp;#34;])
c[&amp;#34;server-c&amp;lt;br/&amp;gt;76.5&amp;#34;]
u1([&amp;#34;user-1&amp;lt;br/&amp;gt;77.6&amp;#34;])
a[&amp;#34;server-a&amp;lt;br/&amp;gt;83.3&amp;#34;]
u2([&amp;#34;user-2&amp;lt;br/&amp;gt;84.8&amp;lt;br/&amp;gt;b → d 이동&amp;#34;])
u10([&amp;#34;user-10&amp;lt;br/&amp;gt;89.3&amp;lt;br/&amp;gt;b → d 이동&amp;#34;])
zero --&amp;gt; u7 --&amp;gt; u9 --&amp;gt; d --&amp;gt; u8 --&amp;gt; u5 --&amp;gt; b --&amp;gt; u6 --&amp;gt; u3 --&amp;gt; u4 --&amp;gt; c --&amp;gt; u1 --&amp;gt; a --&amp;gt; u2 --&amp;gt; u10 --&amp;gt;|&amp;#34;다시 0으로&amp;#34;| zero
subgraph L[&amp;#34;범례: 사용자 색 = 매핑된 서버&amp;#34;]
direction LR
la[&amp;#34;server-a 1명&amp;#34;]
lb[&amp;#34;server-b 2명&amp;#34;]
lc[&amp;#34;server-c 3명&amp;#34;]
ld[&amp;#34;server-d 4명 (전부 b에서 이동)&amp;#34;]
end
classDef sa fill:#ffcccc,stroke:#333
classDef sb fill:#ccffcc,stroke:#333
classDef sc fill:#ccccff,stroke:#333
classDef sd fill:#ffdca8,stroke:#e07000,stroke-width:3px
class a,la,u1 sa
class b,lb,u5,u8 sb
class c,lc,u3,u4,u6 sc
class d,ld,u2,u7,u9,u10 sd
style zero fill:#f0f0f0,stroke:#333&lt;/pre>&lt;p>server-d가 8.4에 들어오면 83.3에서 8.4까지의 구간이 server-d 몫이 됩니다. 그 구간에 있던 user-2, user-10, user-7, user-9 네 명이 server-b에서 server-d로 옮겨 가고, 나머지 여섯 명은 그대로인 거죠. 위 실행 결과에서 이동된 사용자 수 237과 server-d에 배정된 수 237이 정확히 같은 것도 같은 이유인데요, 새 서버로 가야 할 사용자만 움직이고 나머지는 한 명도 안 움직이는 거죠.&lt;/p>
&lt;h2 id="랑데뷰-해시hrw-highest-random-weight">랑데뷰 해시(HRW, Highest Random Weight)&lt;/h2>
&lt;p>랑데뷰 해시는 링 해시보다 훨씬 간결하면서도 링 해시와 같은 수준으로 요청을 분배합니다. 사용자의 요청이 들어올 때마다 요청에 대해 가장 해시 점수가 높은 서버를 골라서 매핑시킵니다. 링을 미리 만들 필요도 없고, 서버가 추가되거나 삭제되어도 링을 재구성할 필요 없이 각 요청에 대한 각 서버의 해시 점수만 비교하면 됩니다.&lt;/p>
&lt;p>user-1을 서버에 매핑하는 과정입니다. 점수는 링 도식과 같은 방식으로 0에서 100 사이 눈금으로 줄여서 표시했습니다(xxHash 값 ÷ 2⁶⁴ × 100).&lt;/p>
&lt;pre class="mermaid">flowchart LR
k[&amp;#34;user-1&amp;#34;]
subgraph S3[&amp;#34;서버 3대: 사용자와 서버를 짝지어 점수 계산&amp;#34;]
direction TB
sa[&amp;#34;xxHash(user-1:server-a)&amp;lt;br/&amp;gt;= 37.1&amp;#34;]
sb[&amp;#34;xxHash(user-1:server-b)&amp;lt;br/&amp;gt;= 85.0&amp;#34;]
sc[&amp;#34;xxHash(user-1:server-c)&amp;lt;br/&amp;gt;= 46.8&amp;#34;]
end
subgraph S4[&amp;#34;server-d 추가&amp;#34;]
sd[&amp;#34;xxHash(user-1:server-d)&amp;lt;br/&amp;gt;= 47.7&amp;#34;]
end
win[&amp;#34;server-b&amp;#34;]
k --&amp;gt; sa
k --&amp;gt; sb
k --&amp;gt; sc
k --&amp;gt; sd
sb ==&amp;gt;|&amp;#34;최고 점수&amp;#34;| win
sd -.-&amp;gt;|&amp;#34;85.0보다 낮으니 그대로 server-b&amp;#34;| win
style k fill:#ffffcc,stroke:#333
style sa fill:#ffcccc,stroke:#333
style sb fill:#ccffcc,stroke:#333,stroke-width:3px
style sc fill:#ccccff,stroke:#333
style sd fill:#ffdca8,stroke:#333
style win fill:#ccffcc,stroke:#333,stroke-width:3px&lt;/pre>&lt;p>user-1은 server-b와 짝지었을 때 점수가 85.0으로 가장 높으니 server-b에 매핑됩니다. server-d가 추가되어도 새 점수 47.7은 85.0보다 낮기 때문에 user-1은 그대로 server-b에 남습니다. 새 서버에 대해 최고 점수를 받는 사용자만 옮겨 가기 때문에, 링 해시처럼 꼭 필요한 만큼만 이동합니다.&lt;/p>
&lt;h3 id="랑데뷰-해시-구현">랑데뷰 해시 구현&lt;/h3>
&lt;p>다음과 같이 코드를 작성해볼까요?&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="c1">// src/hrw.ts
&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="nx">xxhash_addon&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;xxhash-addon&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">XXHash64&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">xxhash_addon&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"> * 고성능 non-cryptographic xxHash를 통한 해시를 진행한다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param input 입력값
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 해시결과
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">xxHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">bigint&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">digest&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">XXHash64&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&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">// 해시 결과(8바이트 Buffer)를 BigInt로 변환. number는 double 형식이고 표현가능한 정수의 범위는 -(2⁵³ - 1) ~ (2⁵³ - 1) 이므로 64비트를 표현 불가.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 16진수 문자열을 거치지 않고 Buffer에서 바로 읽는다. 문자열 변환과 파싱 비용을 줄이기 위해서다.
&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="nx">digest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">readBigUInt64BE&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 랑데뷰(HRW, Highest Random Weight) 분배 알고리즘
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param key 유저ID 등의 키
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param servers 서버 목록
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 분배 대상이 되는 서버 이름
&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">rendezvousHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">[])&lt;/span>&lt;span class="o">:&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="kd">let&lt;/span> &lt;span class="nx">selected&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">highestScore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="nx">n&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">server&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">servers&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="kr">const&lt;/span> &lt;span class="nx">score&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">xxHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">:&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">server&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">score&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">highestScore&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">highestScore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">score&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">selected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">server&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">return&lt;/span> &lt;span class="nx">selected&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;h3 id="빠르고-균등한-비-암호학적-해시">빠르고 균등한 비 암호학적 해시&lt;/h3>
&lt;p>대신에 위 코드에서 주목할 점은 기존의 SHA-256 알고리즘 대신에 xxHash를 사용한다는 점입니다.&lt;/p>
&lt;p>들어오는 요청을 분배하기 위한 해시는 암호학적 요소가 필요 없고, 빠르고 균등하게 분배 되는지가 가장 중요합니다.&lt;/p>
&lt;blockquote>
&lt;p>이 점은 랑데뷰 해시 뿐만 아니라 다른 모든 해시(나머지, 링 해시)에도 해당되는 내용입니다!&lt;/p>&lt;/blockquote>
&lt;p>예를 들어서 SHA-256 알고리즘은 암호학적 해시입니다. 공격자가 원래의 값을 찾기 어렵도록 하기 위해서 다음과 같은 방어 기법이 들어가 있죠.&lt;/p>
&lt;ul>
&lt;li>Preimage resistance: 해시에서 원래 입력값을 찾기 어렵게 만든다&lt;/li>
&lt;li>Collision resistance: 서로 다른 두 입력이 같은 해시값을 갖기 어렵게 해야 한다&lt;/li>
&lt;/ul>
&lt;p>그래서 SHA-256은 대략 다음과 같은 과정으로 해시를 만듭니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Input
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Padding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">512-bit block으로 분할
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Block 1 ─┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Block 2 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Block 3 ├─→ Compression function
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">... │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Block N ─┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">256-bit hash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>반면에 xxHash는 암호학적 고려 없이 &lt;code>데이터를 최대한 빠르게 잘 섞어서 좋은 분포를 만들자&lt;/code>가 목표입니다. 그래서 CPU 친화적인 연산을 많이 사용해서 빠르게 계산할 수 있도록 대략 다음과 같은 과정을 거칩니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&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">여러 accumulator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ADD / MUL / ROTATE / XOR
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">avalanche
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">64-bit output
&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-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/perf-check.ts
&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">hash&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./hash.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="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">xxHash&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./hrw.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="kr">const&lt;/span> &lt;span class="nx">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&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="kt">100000&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">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="sb">`user-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">i&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&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="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">ROUNDS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&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"> * 해시 함수를 사용자 10만 명에 대해 실행하고 걸린 시간을 잰다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 첫 번째 측정은 JIT 예열(warmup)이므로 측정에서 빼고, ROUNDS만큼 돌린 평균을 출력한다.
&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">bench&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fn&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">input&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="kt">bigint&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">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">users&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">fn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&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">const&lt;/span> &lt;span class="nx">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">performance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">now&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="kd">let&lt;/span> &lt;span class="nx">round&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">round&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">ROUNDS&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">round&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">users&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">fn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&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="kr">const&lt;/span> &lt;span class="nx">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">performance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">now&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">start&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">ROUNDS&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">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">elapsed&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toFixed&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&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 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">bench&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;SHA256&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;sha256&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">bench&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;XXHash64&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">xxHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx tsx ./src/perf-check.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SHA256: 40.2ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">XXHash64: 23.1ms
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>xxHash가 약 1.7배 빠른 결과를 보여주고 있네요!&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>user-1&lt;/code> 같은 짧은 키는 해시 계산 자체보다 함수 호출과 Buffer 생성 같은 부차적인 시간이 더 많이 소요됩니다. 그래서 두 해시의 차이가 실제보다 작게 보이지만, 입력이 길어질수록 xxHash의 속도 차이는 더 벌어집니다!&lt;/p>&lt;/blockquote>
&lt;h3 id="랑데뷰-해시-분배-코드-작성">랑데뷰 해시 분배 코드 작성&lt;/h3>
&lt;p>다시 랑데뷰 해시로 돌아와서, 이제 분배를 진행해볼 수 있도록 코드를 작성할 차례입니다.&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="c1">// src/distribution.ts
&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">rendezvousHash&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./hrw.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="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">// 해시 알고리즘과 각 해시 알고리즘 실행을 위한 hashFn 구성
&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">algorithms&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="err">(&lt;/span>&lt;span class="na">servers&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="err">[])&lt;/span> &lt;span class="err">=&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">servers&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="kt">string&lt;/span>&lt;span class="o">&amp;gt;&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;mod&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">modHash&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;ring hash&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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">ring&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">makeRing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">servers&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="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">ringHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ring&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="s2">&amp;#34;랑데뷰(HRW)&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">rendezvousHash&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx tsx ./src/distribution.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 class="o">=====&lt;/span> &lt;span class="nv">mod&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">339&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">347&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">314&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">--- server-d 추가 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-d │ &lt;span class="m">244&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">242&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">259&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">255&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="m">756&lt;/span> / &lt;span class="nv">1000&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> ring &lt;span class="nb">hash&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">317&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">316&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">367&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">--- server-d 추가 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">215&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">268&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">280&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-d │ &lt;span class="m">237&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="m">237&lt;/span> / &lt;span class="nv">1000&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 class="o">(&lt;/span>HRW&lt;span class="o">)&lt;/span> &lt;span class="o">=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- 서버 3대 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">368&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">289&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">343&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">--- server-d 추가 ---
&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>index&lt;span class="o">)&lt;/span> │ Values │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-b │ &lt;span class="m">261&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-c │ &lt;span class="m">222&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-a │ &lt;span class="m">263&lt;/span> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ server-d │ &lt;span class="m">254&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="m">254&lt;/span> / &lt;span class="m">1000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>링 해시보다 훨씬 간단하면서도 유사한 결과를 내고 있습니다. 다만, HRW는 노드 개수가 n일 때, O(n)의 복잡도를 가지기 때문에 노드가 수백 개를 넘어가거나 초당 요청 수가 수백만 정도로 매우 많다면 효율이 많이 떨어지는 문제가 있습니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>구분&lt;/th>
&lt;th>나머지 연산(mod)&lt;/th>
&lt;th>링 해시&lt;/th>
&lt;th>랑데뷰(HRW)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>서버 3대 → 4대일 때 이동한 사용자(1000명 중)&lt;/td>
&lt;td>756명&lt;/td>
&lt;td>237명&lt;/td>
&lt;td>254명&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>사용자 한 명을 배정하는 비용&lt;/td>
&lt;td>해시 1번, O(1)&lt;/td>
&lt;td>해시 1번 + 이진 탐색, O(log(n·v))&lt;/td>
&lt;td>해시 n번, O(n)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>미리 만들어 둘 것&lt;/td>
&lt;td>없음&lt;/td>
&lt;td>정렬된 링(서버가 바뀔 때마다 다시)&lt;/td>
&lt;td>없음&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>메모리&lt;/td>
&lt;td>없음&lt;/td>
&lt;td>지점 n·v개&lt;/td>
&lt;td>없음&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>부하 균등&lt;/td>
&lt;td>좋음&lt;/td>
&lt;td>가상 노드 수(v)에 따라 달라짐&lt;/td>
&lt;td>좋음&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>어울리는 상황&lt;/td>
&lt;td>상태 없는 서버&lt;/td>
&lt;td>서버가 많을 때(수백 대 이상)&lt;/td>
&lt;td>서버가 적을 때, 코드가 단순해야 할 때&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>n은 서버 수, v는 서버당 가상 노드 수입니다.&lt;/p>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>요즘 개발자를 위한 시스템 설계 수업, 길벗&lt;/li>
&lt;li>&lt;a href="https://github.com/ktrongnhan/xxhash-addon">xxhash-addon&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Node.js: --experimental-vm-modules</title><link>https://netscout.github.io/posts/node-js-experimental-vm-modules/</link><pubDate>Sat, 29 Aug 2026 08:00:25 +0900</pubDate><guid>https://netscout.github.io/posts/node-js-experimental-vm-modules/</guid><description>최근에 클로드 코드의 작업을 지켜보던 중, 눈에 들어오는 커맨드가 있었습니다. 테스트를 실행하는 코드 앞에 NODEOPTIONS=&amp;ndash;experimental-vm-modules 이런 환경변수가 붙어있더군요. 그래서 vm? 노드에 무슨 vm? 하는 궁금증과 함께 찾아보기 …</description><content>&lt;p>최근에 클로드 코드의 작업을 지켜보던 중, 눈에 들어오는 커맨드가 있었습니다. 테스트를 실행하는 코드 앞에 &lt;code>NODE_OPTIONS=--experimental-vm-modules&lt;/code> 이런 환경변수가 붙어있더군요. 그래서 vm? 노드에 무슨 vm? 하는 궁금증과 함께 찾아보기 시작했습니다. 확인하다 보니&amp;hellip; 이게 그렇게 간단한 이야기는 아니었고, 그동안 노드를 계속 쓰면서도 알려고 하지 않았던 부끄러운 과거를 반성하게 되더군요&amp;hellip;&lt;/p>
&lt;p>&lt;code>--experimental-vm-modules&lt;/code> 를 좀 많이 단순화해서 한 마디로 정리하면, &amp;ldquo;테스트를 위해 CommonJS 시절에 mocking 하던 것처럼 ESM에서도 할 수 있게 해줄게&amp;rdquo; 정도일까요? 그러니까 이건 모듈에 관련된 내용이고, 모듈을 알아보려면 일단 Javascript의 서버 런타임의 역사를 좀 살펴볼 필요가 있습니다.&lt;/p>
&lt;h2 id="javascript-초창기의-서버-런타임">Javascript 초창기의 서버 런타임&lt;/h2>
&lt;p>1994년에 전설적인 Netscape 브라우저가 출시되었습니다. 그 이후에 전세계가 인터넷의 미래를 선점하기 위해서 움직였고, 마이크로소프트 역시 지금은 사라진 인터넷 익스플로러를 1995년 8월에 공개했습니다. 이후 각 회사는 브라우저와 함께 웹 개발 생태계의 주도권을 쥐기 위해 웹 서버 개발 환경을 출시합니다. 넷스케이프에서는 Netscape Enterprise Server와 LiveWire를 마이크로소프트에서는 IIS와 ASP를 1996년에 출시합니다.&lt;/p>
&lt;p>&lt;a href="https://docs.oracle.com/cd/E19957-01/816-6411-10/contents.htm">LiveWire의 당시 개발자 문서&lt;/a>는 현재도 확인할 수 있는데요, 아래 구조에서 볼 수 있듯이 서버에서 html 페이지를 렌더링해서 프론트로 보내주는 형태입니다. 지금 우리가 쓰는 서버 사이드 렌더링(SSR)의 개념인 셈이죠.&lt;/p>
&lt;p align="center">&lt;img src="./proc.gif" />&lt;br />출처: https://docs.oracle.com/cd/E19957-01/816-6411-10/jsserv.htm#1028696&lt;/p>
&lt;p>그리고 이후에도 Helma(1998), Jaxer(2008) 등 Javascript를 서버에서 구동할 수 있는 런타임이 등장했지만 한 가지 큰 문제가 있었는데요, 초창기 Javascript에는 모듈의 개념이 아예 없었습니다. 어느 정도 규모가 있는 코드를 작성하다보면 필연적으로 코드를 자르고 묶어서 서로 참조하게 하고, 누군가가 만든 코드를 가져와서 쓰기도 하고 해야 합니다. 그런데 이런 개념이 아예 없었기 때문에 각 서버 런타임별로 자기 나름대로의 방법을 추구하고 있었던 셈이죠.&lt;/p>
&lt;blockquote>
&lt;p>Netscape LiveWire의 좀 더 자세한 내용이 궁금하신 분은 &lt;a href="https://dev.to/macargnelutti/server-side-javascript-a-decade-before-node-js-with-netscape-livewire-l72">Server-side JavaScript a decade before Node.js with Netscape LiveWire&lt;/a> 를 참고하세요!&lt;/p>&lt;/blockquote>
&lt;h2 id="나는-꿈이-있어요">나는 꿈이 있어요!&lt;/h2>
&lt;p>이런 상황 속에서 2009년 1월 Kevin Dangoor는 &lt;a href="https://www.blueskyonmars.com/2009/01/29/what-server-side-javascript-needs/">What Server Side JavaScript needs&lt;/a> 라는 글에서 다음과 같이 이야기합니다.&lt;/p>
&lt;blockquote>
&lt;p>JavaScript needs a standard way to include other modules and for those modules to live in discreet namespaces. &amp;hellip; There needs to be a way to package up code for deployment and distribution and further to install packages. &amp;hellip; To that end, I’ve set up a new ServerJS group in hopes of getting the interested parties talking and maybe even to get us together face-to-face to produce some code and settle on some interfaces. &amp;hellip;&lt;/p>&lt;/blockquote>
&lt;p>Javascript에게 패키지 배포 및 모듈을 다루는 표준방식이 필요하며, 이를 위해서 ServerJS라는 논의 그룹을 시작한다는 내용입니다. 이후 ServerJS는 CommonJS로 이름을 바꿨고 이후 십 수년간 Node.js의 사실상 표준으로 사용됩니다. 지금와서 보면 그 당시에 대단한 꿈이었고, 저 같은 개발자는 그 덕을 많이 봤던 거죠.&lt;/p>
&lt;h2 id="하지만-그렇게-간단하지-않았다">하지만 그렇게 간단하지 않았다&lt;/h2>
&lt;p>하지만 지금, 우리는 결론을 알고 있습니다. ESM이 결국 표준이 되었고, 그동안 CommonJS로 배포된 패키지는 셀 수도 없이 많았죠. 아주 오랜 시간동안 패키지들은 CommonJS와 ESM을 동시에 지원하려고 애썼고, 결국은 ESM만을 지원하는 패키지를 CommonJS에서 사용할 수 있도록 &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> 기능이 추가되기도 했습니다. 오랜 기간 동안 CommonJS가 사용된 탓에(특히 Node.js가 폭발적으로 성장하던 시절에!) CommonJS를 무시할 수 없게 된 거죠.&lt;/p>
&lt;blockquote>
&lt;p>CommonJS와 ESM에 대해 알고 싶으신 분들은 &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와 ESM의 차이점&lt;/a> 을 참고하세요!&lt;/p>&lt;/blockquote>
&lt;h2 id="그-시절-우리가-좋아했던-mocking">그 시절 우리가 좋아했던 mocking&lt;/h2>
&lt;p>&lt;code>흉내지빠귀&lt;/code>는 곤충이나 다른 새의 울음소리를 흉내낸다고 해서 영어로는 &lt;code>Mockingbird&lt;/code>라고 불립니다.&lt;/p>
&lt;p align="center">&lt;img src="./Mimus_polyglottos1.jpg" />&lt;br />출처: https://simple.wikipedia.org/wiki/Mockingbird&lt;/p>
&lt;p>우리가 테스트를 작성하다보면 Mockingbird 같이, 코드의 각 부분이 실제와 동일한 인터페이스를 갖지만 내부 동작은 완전히 다른 무언가를 만들어야 합니다. 예를 들어서 DB에서 뭔가 데이터를 가져오는 코드를 테스트 한다고 생각해보죠.&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">// user-service.js
&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="p">{&lt;/span> &lt;span class="nx">db&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./database.js&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">exports&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SELECT * FROM users WHERE id = ?&amp;#39;&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;/code>&lt;/pre>&lt;/div>&lt;p>실제 DB에서 데이터를 가져오는 코드를 그대로 테스트에 활용한다면 다음과 같은 문제가 발생합니다.&lt;/p>
&lt;ul>
&lt;li>테스트하는 환경에 실제 DB가 구동되어야 함&lt;/li>
&lt;li>DB에 지연/장애 발생시 테스트 시간이 길어지거나 타임아웃 등으로 실패 발생&lt;/li>
&lt;li>DB에 데이터를 쓰는 경우, 테스트용 데이터가 섞여들어가는 문제 발생 가능&lt;/li>
&lt;/ul>
&lt;p>그래서 다음과 같이 DB 응답을 대체하는 mock 코드를 통해 테스트를 진행합니다.&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">// user-service.test.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">jest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./database.js&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">query&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">jest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fn&lt;/span>&lt;span class="p">()&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>&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">db&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./database.js&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">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">getUser&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./user-service.js&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="nx">beforeEach&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">jest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">resetAllMocks&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="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">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;id로 유저 한 명을 조회한다&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="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="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mockResolvedValue&lt;/span>&lt;span class="p">([{&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&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">email&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;a@b.com&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">rows&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">42&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">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rows&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toEqual&lt;/span>&lt;span class="p">([{&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&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">email&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;a@b.com&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">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toHaveBeenCalledWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SELECT * FROM users WHERE id = ?&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">42&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>만약 DB에 조회해오는 쿼리나 인덱스 등의 체크도 필요하다면, 메모리에서 동작하는 &lt;a href="https://github.com/WiseLibs/better-sqlite3">better-sqlite3&lt;/a>나 &lt;a href="https://github.com/typegoose/mongodb-memory-server">mongodb-memory-server&lt;/a> 등을 사용하기도 합니다.&lt;/p>
&lt;h2 id="그런데-뭐가-문제인가요">그런데 뭐가 문제인가요?&lt;/h2>
&lt;p>앞선 예시 코드를 보시면 알겠지만, require를 사용하고 있습니다. CommonJS죠. CommonJS에서는 mocking이 매우 쉽습니다. 왜 그런지 결론부터 말씀드리면, &lt;strong>CommonJS는 여러분의 코드를 함수로 감싸서 실행하는데, 이때 모듈이 로드되는 과정을 임의로 조정할 수 있습니다.&lt;/strong>&lt;/p>
&lt;p>즉, CommonJS에서는 모든 게 다 함수입니다. 우선 이 부분 부터 확인해보죠.&lt;/p>
&lt;h3 id="모든-게-다-함수다">모든 게 다 함수다.&lt;/h3>
&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 -v
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">v26.0.0
&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; node -e &lt;span class="s2">&amp;#34;console.log(require(&amp;#39;node:module&amp;#39;).wrapper)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Proxy&lt;span class="o">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;(function (exports, require, module, __filename, __dirname) { &amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;\n});&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>굉장히 간단한 명령과 출력이지만, 저는 이게 좀 이해하기 어려웠습니다. 하나씩 확인해보면,&lt;/p>
&lt;h4 id="1-nodemodule">1. node:module&lt;/h4>
&lt;p>앞에 &lt;code>node:&lt;/code>가 붙으면, &lt;code>이 모듈은 Node.js에서 기본으로 제공하는(built-in) 모듈이니까 node_modules 뒤지지 마라&lt;/code> 뭐 그런 의미입니다.
그리고 &lt;code>module&lt;/code>은 말 그대로 Node.js의 모듈 시스템을 가리킵니다. 다음과 같이 실행해보면,&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 -e &lt;span class="s2">&amp;#34;console.log(require(&amp;#39;node:module&amp;#39;))&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;ref *1&amp;gt; &lt;span class="o">[&lt;/span>Function: Module&lt;span class="o">]&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;/code>&lt;/pre>&lt;/div>&lt;p>Module의 생성자 함수가 리턴되는 걸 확인할 수 있죠?&lt;/p>
&lt;h4 id="2-wrapper">2. .wrapper&lt;/h4>
&lt;p>말 그대로 코드의 앞과 뒤를 감싸는 wrapper를 의미합니다. 앞선 .wrapper의 출력 결과를 보면,&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">Proxy&lt;span class="o">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;(function (exports, require, module, __filename, __dirname) { &amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;\n});&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>배열에 2개의 요소가 있습니다.&lt;/p>
&lt;ul>
&lt;li>&amp;lsquo;(function (exports, require, module, __filename, __dirname) { '&lt;/li>
&lt;li>&amp;lsquo;\n});&amp;rsquo;&lt;/li>
&lt;/ul>
&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">// greeting.js
&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;world&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">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="s1">&amp;#39;hello, &amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이런 코드가 있다면, Node.js 는 이 코드를 다음과 같이 컴파일합니다.&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="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">__filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">__dirname&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;world&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">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="s1">&amp;#39;hello, &amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">name&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="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-gdscript3" data-lang="gdscript3">&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="err">│&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exports&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">require&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">module&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">__filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">__dirname&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">│&lt;/span> &lt;span class="err">←&lt;/span> &lt;span class="n">Node가&lt;/span> &lt;span class="err">추가&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="err">│&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;world&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="n">module&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;hello, &amp;#39;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">←&lt;/span> &lt;span class="err">내가&lt;/span> &lt;span class="err">작성한&lt;/span> &lt;span class="err">코드&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="err">│&lt;/span> &lt;span class="p">});&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">←&lt;/span> &lt;span class="n">Node가&lt;/span> &lt;span class="err">추가&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="그래서-이게-다-뭐라는-건가요">그래서 이게 다 뭐라는 건가요?&lt;/h3>
&lt;p>Node.js가 붙여주는 wrapper의 파라미터를 잘 보시면, exports, require, module, __filename, __dirname 등 우리에게 매우 친숙한 이름들입니다. 그러니까 이게 무슨 전역 변수 같은 게 아니라 wrapper를 통해서 파라미터로 주입된 애들이라는 거죠!(저는 처음 알았습니다&amp;hellip;)&lt;/p>
&lt;p>그래서 require를 제 맘대로 정의하는 것도 가능합니다.&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">// my-require.js
&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">fs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fs&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="kd">function&lt;/span> &lt;span class="nx">myRequire&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filename&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. 대상이 되는 파일을 텍스트로 읽어오기
&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">yourCode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">readFileSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;utf8&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">// 2. 앞, 뒤로 wrapper 코드 붙이기
&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">wrapped&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;(function (exports, require, module, __filename, __dirname) {&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">yourCode&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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="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="kr">const&lt;/span> &lt;span class="nx">fn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">eval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">wrapped&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">// 4. 매개변수를 정의하고 호출하기
&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">module&lt;/span> &lt;span class="o">=&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 class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// require 파라미터에 myRequire를 전달함!!!!!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">fn&lt;/span>&lt;span class="p">(&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="p">,&lt;/span> &lt;span class="nx">myRequire&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">__dirname&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">// 5. module.exports 값을 리턴
&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="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&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="nx">myRequire&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./greeting.js&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// hello, world
&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node my-require.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">hello, world
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그러니까, Node.js가 wrapper로 코드를 감싸게 되면, 테스트를 위해서 만든 mock을 대신 로드하는 require를 주입해서 호출 경로를 바꿀 수 있다는 거죠! 그래서 CommonJS 코드는 mock 테스트가 아주 쉽습니다.&lt;/p>
&lt;blockquote>
&lt;p>여담이지만, 그래서 최상위 레벨의 return이 코드를 종료시키게 됩니다.&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="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;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="k">return&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;앞 줄의 return에서 wrapper 함수를 빠져나가므로 출력되지 않습니다!!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&lt;h2 id="그런데-esm은-달라요">그런데 ESM은&amp;hellip; 달라요!&lt;/h2>
&lt;p>근데 ESM은&amp;hellip; 다릅니다. 아까 봤던 코드를 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="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">db&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;./database.js&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">export&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&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">id&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>import는 함수가 아니라 선언입니다.(require는 함수였죠!) 게다가 import는 코드가 실행되기 전에 어떤 모듈을 로드할지 결정(resolve)됩니다. 위 코드가 실행되는 시점에서는 이미 db가 선언된 모듈로 로드된 상태라는 거죠. 조금 더 자세히 살펴보면 ESM에서 모듈은 다음과 같이 3단계로 로드됩니다.&lt;/p>
&lt;ol>
&lt;li>분석(parse): 코드 실행 전에 import, export 선언을 추출함&lt;/li>
&lt;li>연결(link): 모듈간의 참조 관계를 그래프로 구성함&lt;/li>
&lt;li>실행(evaluate): 코드가 실행됨&lt;/li>
&lt;/ol>
&lt;p>CommonJS처럼 모듈을 중간에 바꿔치기 하려면 2번 연결 단계가 유일한 희망이지만, ESM은 CommonJS처럼 쉬운 방법은 없습니다.&lt;/p>
&lt;blockquote>
&lt;p>하지만 Jest가 아니라 node:test 를 사용한다면 이런 방법을 고민할 필요가 없습니다. node:test는 vm 모듈을 사용하지 않습니다!&lt;/p>&lt;/blockquote>
&lt;h2 id="다시-experimental-vm-modules-이야기">다시 &amp;ndash;experimental-vm-modules 이야기&lt;/h2>
&lt;p>이제 드디어 &lt;code>--experimental-vm-modules&lt;/code> 플래그를 이야기할 차례입니다. 참 먼 길을 돌아왔네요&amp;hellip; 제가 모르는 게 이렇게 많았다니 저도 슬픕니다.&lt;/p>
&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 -e &lt;span class="s2">&amp;#34;const vm=require(&amp;#39;node:vm&amp;#39;); console.log(typeof vm.SourceTextModule)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">undefined
&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; node --experimental-vm-modules -e &lt;span class="s2">&amp;#34;const vm=require(&amp;#39;node:vm&amp;#39;); console.log(typeof vm.SourceTextModule)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>vm.SourceTextModule&lt;/code>이 원래는 없었는데, 플래그를 설정하자 나타났죠? 이 플래그를 설정하면 &lt;code>vm.Module&lt;/code>, &lt;code>vm.SourceTextModule&lt;/code>, &lt;code>vm.SyntheticModule&lt;/code>을 사용할 수 있게 해줍니다.&lt;/p>
&lt;p>이 클래스들을 활용하면 앞서 살펴봤던 ESM 모듈의 3단계를 직접 제어할 수 있습니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://nodejs.org/api/vm.html">노드 공식 문서&lt;/a>에 따르면 vm.Module, vm.SourceTextModule, vm.SyntheticModule 모두 아직까지 &lt;code>Stability: 1 - Experimental&lt;/code> 상태입니다. 각 모듈이 추가된 시점을 보면 9.6.0, 12.16.0 등으로 아주 오래전인데 말이죠. 어쩌면 이 기능은 앞으로 사용하지 않는게 더 좋을 거 같은 느낌도 드네요.&lt;/p>&lt;/blockquote>
&lt;h2 id="esm의-모듈-임포트를-직접-제어해보자">ESM의 모듈 임포트를 직접 제어해보자!&lt;/h2>
&lt;p>앞서 살펴본 &lt;code>vm.SourceTextModule&lt;/code>를 이용해서 다음과 같이 코드를 작성해보겠습니다.&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">// esm-3steps.js
&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="nx">vm&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;node:vm&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">// vm.createContext는 독립적인 코드 실행공간(샌드박스)을 생성합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 새로 생성된 context에는 console 조차도 없기 때문에 주입해줘야 합니다.
&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">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createContext&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">console&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단계 - 분석. 코드를 컴파일하면서 import와 export를 추출합니다.
&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">module&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SourceTextModule&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`export const greeting = &amp;#39;hello world&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="nx">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">identifier&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;hello.mjs&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="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="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// &amp;#39;unlinked&amp;#39;
&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">// 2단계 - 연결. 모든 import, export에서 로드할 모듈을 결정합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// hello.mjs는 다른 모듈을 import하지 않기 때문에, linker는 아무 일도 하지 않습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">await&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">link&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;no imports&amp;#39;&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="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// &amp;#39;linked&amp;#39;
&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">// 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">await&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">evaluate&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="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// &amp;#39;evaluated&amp;#39;
&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">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="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">namespace&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">greeting&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// &amp;#39;hello world&amp;#39;
&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node --experimental-vm-modules esm-3steps.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">unlinked
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">linked
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">evaluated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">hello world
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>node:68009&lt;span class="o">)&lt;/span> ExperimentalWarning: VM Modules is an experimental feature and might change at any &lt;span class="nb">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>Use &lt;span class="sb">`&lt;/span>node --trace-warnings ...&lt;span class="sb">`&lt;/span> to show where the warning was created&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>분석 -&amp;gt; 연결 -&amp;gt; 실행&lt;/code>의 3단계가 명확하게 드러나죠? 이제 CommonJS 때 처럼 직접 로드하는 방법을 시도해보려고&amp;hellip;. 했습니다만. 코드를 보다 보니, &lt;code>아 이건 그냥 가능하다 정도만 확인하고 넘어가야겠다&lt;/code> 싶었습니다. 좀 많이 복잡하기 때문에 직접 이런 코드를 작성하는 일은 없어야겠다는 생각이 들더군요! 혹시 관심있으신 분들은 &lt;code>esm-loader&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; node --experimental-vm-modules esm-loadMock.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span> id: 42, name: &lt;span class="s1">&amp;#39;홍길동&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="o">(&lt;/span>node:81468&lt;span class="o">)&lt;/span> ExperimentalWarning: VM Modules is an experimental feature and might change at any &lt;span class="nb">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>Use &lt;span class="sb">`&lt;/span>node --trace-warnings ...&lt;span class="sb">`&lt;/span> to show where the warning was created&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="mock을-사용하지-않는-디자인">mock을 사용하지 않는 디자인&lt;/h2>
&lt;p>ESM 예제를 보고 나니 mocking을 꼭 이렇게까지 해야 하나 싶은 생각이 들었습니다. 꼭 필요한 경우도 있겠죠. 예를 들면,&lt;/p>
&lt;ul>
&lt;li>내가 작성하지 않은 라이브러리가 껴있는 경우&lt;/li>
&lt;li>특정 레이어에서 몇 개의 레이어를 그냥 전달만 하는 경우(ex: handler.js -&amp;gt; service.js -&amp;gt; repository.js -&amp;gt; db.js)&lt;/li>
&lt;li>코드 실행 전에 import로 인해 실행되는 경우&lt;/li>
&lt;/ul>
&lt;p>뭐 이런 경우는 mocking을 하는 게 더 나을 수도 있습니다. 그런데 실제 코드와 테스트 코드에서 서로 다른 뭔가를 주입할 수 있도록 구성한다면 애초에 mocking을 피할 수 있습니다.&lt;/p>
&lt;p>우선 아주 간단하게 실제 DB에 접근할 때 사용될 database.js를 작성합니다.&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">// database.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 코드가 실행되기 전에 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="s1">&amp;#39; [database.js] connecting to postgres://prod...&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">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">db&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">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;REAL DATABASE HIT&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;/code>&lt;/pre>&lt;/div>&lt;p>그리고 db를 넘겨받아서 데이터를 조회할 user-service.js 를 작성하고요,&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">// user-service.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 팩토리 패턴을 사용해서 import를 하나도 선언하지 않습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// db 파라미터에는 실제 DB나 fake DB가 사용될 수 있죠.
&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">createUserService&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">db&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&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">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;SELECT * FROM users WHERE id = ?&amp;#39;&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 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>다음으로 실제 db를 통해 사용자 정보를 조회하는 main.js 입니다.&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">// 실제 코드를 실행하는 진입점입니다.
&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">db&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;./database.js&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">createUserService&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;./user-service.js&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">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">userService&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createUserService&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">db&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 마지막으로 fake DB를 사용해서 user-service.js를 테스트할 user-service.test.mjs 를 작성합니다.&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">// user-service.test.mjs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 실행 방법: node --test
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// node:test는 vm 모듈을 사용하지 않기 때문에 --experimental-vm-modules 플래그가 필요 없습니다!
&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">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">test&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;node:test&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="nx">assert&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;node:assert&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">// fake DB를 만드는 함수
&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">createFakeDb&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rows&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="kr">const&lt;/span> &lt;span class="nx">calls&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">calls&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&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">calls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">sql&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="k">return&lt;/span> &lt;span class="nx">rows&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">]&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 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">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fake DB로 서비스를 생성하기&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="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="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">createUserService&lt;/span> &lt;span class="p">}&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="s1">&amp;#39;./user-service.js&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">const&lt;/span> &lt;span class="nx">fakeDb&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createFakeDb&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&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="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">service&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createUserService&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">fakeDb&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">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">42&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">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">deepStrictEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&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 class="nx">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">deepStrictEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fakeDb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">calls&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="p">{&lt;/span> &lt;span class="nx">sql&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;SELECT * FROM users WHERE id = ?&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">42&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">test&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="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="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">createUserService&lt;/span> &lt;span class="p">}&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="s1">&amp;#39;./user-service.js&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">const&lt;/span> &lt;span class="nx">service&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createUserService&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">createFakeDb&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">strictEqual&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">999&lt;/span>&lt;span class="p">),&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="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;db 오류가 전파되는지 확인하는 테스트&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="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="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">createUserService&lt;/span> &lt;span class="p">}&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="s1">&amp;#39;./user-service.js&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">const&lt;/span> &lt;span class="nx">service&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createUserService&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">db&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">query&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;connection reset&amp;#39;&lt;/span>&lt;span class="p">);&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="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">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">throws&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="sr">/connection reset/&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>fake DB를 만들고 그걸 팩토리 createUserService 함수에 넘겨줘서, userService가 fake DB를 통해 테스트를 진행하도록 구성하고 있습니다. 테스트 실행 결과는 다음과 같습니다.&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 --test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✔ fake DB로 서비스를 생성하기 &lt;span class="o">(&lt;/span>1.105083ms&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✔ 찾을 수 없는 데이터 테스트 &lt;span class="o">(&lt;/span>0.085625ms&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✔ db 오류가 전파되는지 확인하는 테스트 &lt;span class="o">(&lt;/span>0.432792ms&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ tests &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ suites &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ pass &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ fail &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ cancelled &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ skipped &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ todo &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ℹ duration_ms 42.566208
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>mocking 없이도 깔끔하게 테스트를 진행할 수 있죠? 의존성이 강하게 결합되는 부분을 제거하도록 디자인하면 mocking의 필요성 자체를 없앨 수도 있습니다!&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>이 글의 시작은 한 플래그에 포함된 &amp;ldquo;vm&amp;quot;이라는 글자 때문이었지만, 조사를 하는 과정에서 예전에 어떤 일이 있었고, 내가 아무 생각없이 오랫동안 쓰던 게 이런 고민을 통해 나왔던 거구나 하고 공부하는 기회가 되었습니다. 역시&amp;hellip; 모르는 게 너무 많군요!&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nodejs.org/api/vm.html">Node.js 공식 문서: vm - SourceTextModule과 SyntheticModule&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://nodejs.org/api/modules.html#the-module-wrapper">Node.js 공식 문서: The module wrapper&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://jestjs.io/docs/ecmascript-modules">Jest 공식 문서: ECMAScript Modules&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.oracle.com/cd/E19957-01/816-6411-10/contents.htm">LiveWire 개발자 문서(Oracle 아카이브)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dev.to/macargnelutti/server-side-javascript-a-decade-before-node-js-with-netscape-livewire-l72">Server-side JavaScript a decade before Node.js with Netscape LiveWire&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.blueskyonmars.com/2009/01/29/what-server-side-javascript-needs/">What Server Side JavaScript needs - Kevin Dangoor&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와 ESM의 차이점&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>SIMD를 알아보자(feat. Go)</title><link>https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/</link><pubDate>Wed, 12 Aug 2026 21:30:50 +0900</pubDate><guid>https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/</guid><description>최근에 Youtube에서 재밌는 영상 하나를 봤습니다. People Are Mad They&amp;rsquo;re Told to Learn 이라는 영상인데요, 영상의 발단은 Mitchell Hashimoto의 Everyone Should Know SIMD 라는 글이었습니다. …</description><content>&lt;p>최근에 Youtube에서 재밌는 영상 하나를 봤습니다. &lt;a href="https://youtu.be/4nJ2tEPD4-k?si=vpg1EWAFg4DQ4DkX">People Are Mad They&amp;rsquo;re Told to Learn&lt;/a> 이라는 영상인데요, 영상의 발단은 Mitchell Hashimoto의 &lt;a href="https://mitchellh.com/writing/everyone-should-know-simd">Everyone Should Know SIMD&lt;/a> 라는 글이었습니다. 인터넷에서는 그 글에 대한 반발로 &amp;ldquo;아니 왜 모든 개발자가 이런 걸 꼭 알아야 한다는 말이냐&amp;quot;등과 같은 반응이 있었고, 저 영상에서는 그 글에 대한 반응과 함께 SIMD로 코드를 작성해보는 내용이 있었습니다. 그걸 보고 나니 저도 궁금해졌습니다. SIMD가 뭔데? 왜 이걸 다 알아야 한다는 거지? 그래서 그냥 한번 시작해봤습니다.&lt;/p>
&lt;h2 id="go-환경설정">Go 환경설정&lt;/h2>
&lt;p>우선 Go를 통해 SIMD를 테스트할 수 있는 환경을 마련해보겠습니다. 저는 Go를 brew를 통해 설치했었는데요, 2026년 8월 현재 brew를 통해 설치할 수 있는 최신 버전은 1.26.5입니다. 1.26.5버전도 SIMD가 지원되긴 하지만 AMD64 환경에서만 제한적으로 지원되고, 맥(ARM64)에서 SIMD를 활용하려면 1.27rc2를 설치해야 합니다. 기존에 정식으로 설치된 버전과 간섭없이 동작할 수 있고, 나중에 제거할 때도 아무런 간섭없이 제거할 수 있습니다.&lt;/p>
&lt;h3 id="1-래퍼-프로그램-설치">1. 래퍼 프로그램 설치&lt;/h3>
&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; go install golang.org/dl/go1.27rc2@latest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-gobin-을-path에-설정하기">2. &lt;code>~/go/bin&lt;/code> 을 PATH에 설정하기&lt;/h3>
&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; &lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;export PATH=&amp;#34;$HOME/go/bin:$PATH&amp;#34;&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">source&lt;/span> ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; which go1.27rc2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/Users/onlifecoding/go/bin/go1.27rc2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-실제-sdk-다운로드">3. 실제 SDK 다운로드&lt;/h3>
&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">go1.27rc2 download
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 0.0% &lt;span class="o">(&lt;/span> &lt;span class="m">2952&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 5.6% &lt;span class="o">(&lt;/span> &lt;span class="m">3833856&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 24.7% &lt;span class="o">(&lt;/span>&lt;span class="m">16859008&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 41.2% &lt;span class="o">(&lt;/span>&lt;span class="m">28131120&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 58.3% &lt;span class="o">(&lt;/span>&lt;span class="m">39828640&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 74.9% &lt;span class="o">(&lt;/span>&lt;span class="m">51134112&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 90.1% &lt;span class="o">(&lt;/span>&lt;span class="m">61488688&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloaded 100.0% &lt;span class="o">(&lt;/span>&lt;span class="m">68280741&lt;/span> / &lt;span class="m">68280741&lt;/span> bytes&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Unpacking /Users/onlifecoding/sdk/go1.27rc2/go1.27rc2.darwin-arm64.tar.gz ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Success. You may now run &lt;span class="s1">&amp;#39;go1.27rc2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-버전-확인하기">4. 버전 확인하기&lt;/h3>
&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; go version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go version go1.26.5 darwin/arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go1.27rc2 version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go version go1.27rc2 darwin/arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go1.27rc2 env GOROOT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/Users/onlifecoding/sdk/go1.27rc2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="5-simd-지원-확인하기">5. SIMD 지원 확인하기&lt;/h3>
&lt;p>아래 명령을 통해 simd 패키지의 설명이 출력되면 환경 설정이 잘 끝났습니다.&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; &lt;span class="nv">GOEXPERIMENT&lt;/span>&lt;span class="o">=&lt;/span>simd go1.27rc2 doc simd &lt;span class="p">|&lt;/span> head -40
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">package simd // import &lt;span class="s2">&amp;#34;simd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="6-설정-삭제하기">6. 설정 삭제하기&lt;/h3>
&lt;p>추후에 설정을 삭제하려면, SDK를 삭제하고 &lt;code>rm -rf ~/sdk/go1.27rc2&lt;/code>, 래퍼 프로그램을 삭제하고 &lt;code>rm ~/go/bin/go1.27rc2&lt;/code>, PATH 설정을 &lt;code>~/.zshrc&lt;/code> 에서 삭제하면 깔끔하게 제거할 수 있습니다.&lt;/p>
&lt;h2 id="테스트용-코드-작성하기">테스트용 코드 작성하기&lt;/h2>
&lt;p>자 이제 일반 코드와 SIMD가 적용된 코드 간의 비교를 위해서 다음과 같이 코드를 작성 해볼겁니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">simdbench/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kernels.go # 스칼라 커널
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kernel_simd.go # SIMD 커널 (goexperiment.simd 전용)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kernel_stub.go # SIMD 미지원 환경용 대체 커널
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── kernel_test.go # 정확성 테스트 + 벤치마크
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="1-폴더와-모듈-생성">1. 폴더와 모듈 생성&lt;/h3>
&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; mkdir simdbench &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> simdbench
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go mod init simdbench
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-테스트에-사용할-2개의-연산-커널">2. 테스트에 사용할 2개의 연산 커널&lt;/h3>
&lt;p>이 테스트는 2개의 연산 커널의 기준으로 모든 테스트가 진행됩니다.&lt;/p>
&lt;h4 id="2-1-합-구하기">2-1. 합 구하기&lt;/h4>
&lt;p>다음과 같이 float32 배열의 합을 구합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">s = x[0] + x[1] + ... + x[n-1]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>4 바이트를 읽어들일 때 마다 1개의 연산이 발생하기 때문에 메모리 중심의 연산 커널입니다. 또한 합을 구하기 위해서는 이전 단계의 합이 존재해야 하기 때문에 CPU가 대기해야 하는 구간이 발생합니다.&lt;/p>
&lt;h4 id="2-2-삼차-다항식-계산">2-2. 삼차 다항식 계산&lt;/h4>
&lt;p>다음과 같은 삼차 다항식을 배열의 각 요소에 대해 독립적으로 계산합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">out[i] = ((0.5*x[i] + 1.2) * x[i] - 3.1) * x[i] + 0.7
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>1개의 요소 당 6번의 연산이 발생하는 계산 중심의 연산 커널입니다. 1번 연산 커널(합 구하기)에 비해 바이트당 연산 횟수가 더 많기 때문에 SIMD가 병렬화하기 더 좋은 구조입니다.&lt;/p>
&lt;h3 id="3-커널-구현하기kernelsgo">3. 커널 구현하기(kernels.go)&lt;/h3>
&lt;p>다음과 같이 kernels.go 를 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">kernels&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">func&lt;/span> &lt;span class="nf">SumScalar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">x&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">v&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">s&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">// 0.5v³ + 1.2v² − 3.1v + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = v(0.5v² + 1.2v − 3.1) + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = v(v(0.5v + 1.2) − 3.1) + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = ((0.5v + 1.2)·v − 3.1)·v + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">PolyScalar&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">out&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&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">// 입력값을 체크한다. 동시에 컴파일러가 prove 과정에서 len(x) &amp;lt;= len(out)임을 확인한다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 그러면 컴파일러는 아래 for 루프 안의 out[i]에 인덱스 접근 가드를 넣지 않는다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">out&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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="nb">panic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;PolyScalar: out이 x보다 짧습니다.&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="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">x&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">out&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="mf">0.5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mf">1.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mf">3.1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.7&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">// SumUnrolled4는 독립된 accumulator 4개를 사용한다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 각 덧셈이 앞 덧셈의 결과를 기다리지 않는다. 그래서 CPU가 덧셈 4개를 겹쳐 실행한다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">SumUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">s1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">s2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">s3&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">m&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="o">%&lt;/span>&lt;span class="mi">4&lt;/span> &lt;span class="c1">// 동시에 4개의 계산을 진행하니까 4로 나눠떨어지는 구간을 구하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">+=&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="nx">s0&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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="nx">s1&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&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 class="nx">s2&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&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 class="nx">s3&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="o">+&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">s0&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">s1&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">s2&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">s3&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="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">n&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="c1">// 4로 나눠 남은 나머지 구간을 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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">s&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>총 3개의 함수가 작성되었는데요. SumScalar, PolyScalar는 앞서 이야기 했던 연산 커널을 그대로 작성한 함수이고, SumUnrolled4 는 추가 설명이 필요합니다.&lt;/p>
&lt;p>CPU는 float32를 더하는 작업에 3-4 사이클을 소비합니다. 그렇기 때문에 순차적으로 하나씩 더하는 SumScalar는 매 루프마다 앞선 더하기가 끝나기를 기다리는 비효율이 발생하게 되죠.&lt;/p>
&lt;blockquote>
&lt;p>float32는 숫자로 저장되지 않으며, 1 부호 비트 + 8 지수 비트 + 23비트의 가수로 묶여있습니다. 그래서 float32 덧셈을 위해 각 피연산자를 계산 가능한 형태로 묶음을 해제(unpack)하는 1사이클과 계산의 1사이클, 계산 결과를 다시 float32의 형태로 재조정하는 데 1사이클이 필요합니다. 이 과정에 CPU 아키텍처에 따라서 3-4 사이클이 필요하다고 합니다.&lt;/p>&lt;/blockquote>
&lt;p>그래서 앞선 계산을 기다리는 비효율이 발생하지 않도록 CPU가 4개의 계산을 동시에 진행한 뒤, 결과를 합치도록 하는 겁니다. 이런 방식을 ILP(Instruction-Level Parallelism)이라고 합니다.&lt;/p>
&lt;h3 id="4-simd-커널-구현하기kernel_simdgo">4. SIMD 커널 구현하기(kernel_simd.go)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//go:build goexperiment.simd&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="kn">package&lt;/span> &lt;span class="nx">kernels&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="kn">import&lt;/span> &lt;span class="s">&amp;#34;simd&amp;#34;&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">func&lt;/span> &lt;span class="nf">SumSIMDSingle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">acc&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="c1">// accumulator vector. 실행 환경이 지원하는 SIMD lane 수만큼 누적 슬롯을 가진다. 초깃값은 Mac의 경우 [0, 0, 0, 0]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// NEON은 4, AVX는 8 또는 16&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// lane 수만큼 딱 떨어지는 구간을 동시에 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span> &lt;span class="c1">// 주어진 슬라이스에서 lane 수만큼 잘라서 각 lane별로 누적&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">var&lt;/span> &lt;span class="nx">tmp&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">float32&lt;/span> &lt;span class="c1">// 스택에 배열을 생성(AVX512의 경우 최대 float32를 16개 담을 수 있으므로 최댓값으로 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tmp&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">// 각 lane의 값을 배열에 저장&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">j&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">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">j&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// 모든 lane의 결과 값을 더하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">tmp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">j&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">for&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// lane 수로 딱 떨어지지 않고 남았던 인덱스에 대해 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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">s&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">func&lt;/span> &lt;span class="nf">SumSIMDUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a3&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="c1">// 4개의 accumulator vector를 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// vector 하나가 담는 lane 수 확인&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">step&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">4&lt;/span> &lt;span class="c1">// 4개의 accumulator vector -&amp;gt; Mac의 경우 4 * 4 =&amp;gt; 16&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">step&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">step&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// step 단위로 딱 떨어지는 구간을 계산 (Mac은 한 번에 16개)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span> &lt;span class="c1">// 각 accumulator vector에 lane 수만큼 끊어서 누적&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&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">// step만큼 끊고 남은 나머지 중에서 lane 수만큼 끊을 수 있는 구간을 계산&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&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">// (a0+a1)과 (a2+a3)를 먼저 묶어서 안쪽 Add 두 개가 겹쳐 실행되게 한다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a1&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a3&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">var&lt;/span> &lt;span class="nx">tmp&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">float32&lt;/span> &lt;span class="c1">// 스택에 배열을 생성(AVX512의 경우 최대 float32를 16개 담을 수 있으므로 최댓값으로 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tmp&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">j&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">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">j&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">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">tmp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">j&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">for&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&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">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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">s&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">func&lt;/span> &lt;span class="nf">SumSIMDUnrolled8&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a6&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a7&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="c1">// 8개의 accumulator vector를 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// vector 하나가 담는 lane 수 확인&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">step&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="c1">// 8개의 accumulator vector -&amp;gt; Mac의 경우 4 * 8 =&amp;gt; 32&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">step&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">step&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// step 단위로 딱 떨어지는 구간을 계산 (Mac은 한 번에 32개)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span> &lt;span class="c1">// 각 accumulator vector에 lane 수만큼 끊어서 누적&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a4&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a4&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a5&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a5&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">6&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a6&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a6&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">6&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a7&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a7&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&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">// step만큼 끊고 남은 나머지 중에서 lane 수만큼 끊을 수 있는 구간을 계산&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&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">// (a0+a1) (a2+a3) (a4+a5) (a6+a7)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a1&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a3&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a4&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a5&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a6&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a7&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">var&lt;/span> &lt;span class="nx">tmp&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">float32&lt;/span> &lt;span class="c1">// 스택에 배열을 생성(AVX512의 경우 최대 float32를 16개 담을 수 있으므로 최댓값으로 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tmp&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">j&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">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">j&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">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">tmp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">j&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">for&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&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">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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">s&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">// splat은 모든 lane을 v로 채운 vector를 만든다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// hot loop 밖에서만 호출하므로 이 작은 루프의 비용은 무시해도 된다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&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="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="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">v&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">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&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">// 0.5v³ + 1.2v² − 3.1v + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = v(0.5v² + 1.2v − 3.1) + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = v(v(0.5v + 1.2) − 3.1) + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = ((0.5v + 1.2)·v − 3.1)·v + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">PolySIMD&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">out&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&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="kd">var&lt;/span> &lt;span class="nx">probe&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">probe&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// lane 수를 확인하는 용도로만 사용&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">// 4개의 다항식 계수를 lane 수만큼 펼쳐서 레지스터에 로드(ex: [0.5, 0.5, 0.5, 0.5])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c3&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">1.2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mf">3.1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c0&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">// 레지스터에 lane 수만큼 로드&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// c3 * v + c2 =&amp;gt; [0.5*v1+1.2, ... , 0.5*v4+1.2]&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">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c1&lt;/span>&lt;span class="p">)&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">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c0&lt;/span>&lt;span class="p">)&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="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">out&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&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="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="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// lane 수만큼 딱 떨어지지 않고 남은 항목에 대해 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">x&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="nx">out&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="mf">0.5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mf">1.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mf">3.1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.7&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>처음에 이 코드를 보면 좀 복잡할 수 있습니다. 저는 상당히 복잡하다고 느꼈었거든요&amp;hellip; 그럼 한 줄씩 천천히 가보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//go:build goexperiment.simd&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>첫 줄의 &lt;code>//go:build goexperiment.simd&lt;/code> 는 이 코드가 빌드되는 환경에서 goexperiment.simd 태그가 설정되어있다면 이 파일을 포함하고 아니면 제외하라는 의미입니다. 이 파일에는 SIMD를 사용하는 코드가 작성되어있으니, simd 활성화가 되지 않은 환경에서는 아예 제외하라는 의미인거죠. 환경을 설정하는 과정에서 &lt;code>GOEXPERIMENT=simd go1.27rc2 ...&lt;/code> 형식으로 설정을 확인했었는데요. &lt;code>GOEXPERIMENT=simd&lt;/code> 이 환경변수가 goexperiment.simd 태그를 활성화 하게 됩니다.&lt;/p>
&lt;blockquote>
&lt;p>그러면 저 환경변수가 없을 때는 어떻게 될까요? &lt;code>kernel_simd.go&lt;/code> 가 빌드에 포함되지 않으니까 빌드 오류가 발생하지 않을까요? 맞습니다. 오류가 발생하죠! 그래서 simd 활성화가 되지 않은 환경을 위해 잠시 뒤에 &lt;code>kernel_stub.go&lt;/code> 를 추가하게 됩니다. 잠시 뒤에 다시 알아보죠.&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;simd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>다음으로는 simd 패키지를 임포트합니다. 참고로 simd/archsimd 패키지도 있는데요, 그건 1.26 버전에 AMD64 전용으로 추가된 패키지입니다. arm64에서는 1.27이상에서 제공하는 simd 패키지를 써야 하죠.&lt;/p>
&lt;h4 id="4-1-sumsimdsingle-함수">4-1. SumSIMDSingle 함수&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">SumSIMDSingle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">acc&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="c1">// accumulator vector. 실행 환경이 지원하는 SIMD lane 수만큼 누적 슬롯을 가진다. 초깃값은 Mac의 경우 [0, 0, 0, 0]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// NEON은 4, AVX는 8 또는 16&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// lane 수만큼 딱 떨어지는 구간을 동시에 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span> &lt;span class="c1">// 주어진 슬라이스에서 lane 수만큼 잘라서 각 lane별로 누적&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">var&lt;/span> &lt;span class="nx">tmp&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">float32&lt;/span> &lt;span class="c1">// 스택에 배열을 생성(AVX512의 경우 최대 float32를 16개 담을 수 있으므로 최댓값으로 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tmp&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">// 각 lane의 값을 배열에 저장&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">j&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">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">j&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// 모든 lane의 결과 값을 더하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">tmp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">j&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">for&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// lane 수로 딱 떨어지지 않고 남았던 인덱스에 대해 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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">s&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>이 글에서 다루는 첫 번째 SIMD 구현입니다! 이 함수에서도, 그리고 이후로도 &lt;code>simd.Float32s&lt;/code> 타입이 중점적으로 쓰이기 때문에 별도로 설명을 하고 넘어가겠습니다.&lt;/p>
&lt;p>위 코드를 보면, simd.Float32s에 대해서 accumulator vector라고 적어뒀습니다. accumulator는 뭔가 누적해서 담아두는 걸 말하니까 단어의 뜻은 알겠는데 &amp;ldquo;vector&amp;quot;는 뭘까요?&lt;/p>
&lt;p>여기서 vector는 수학 시간에 이야기하던, 크기와 방향을 가진 화살표가 아니라 &lt;strong>같은 타입의 숫자 여러 개를 한 줄로 나란히 담은 묶음&lt;/strong>입니다. CPU에는 이 묶음을 통째로 담는 넓은 레지스터(vector register)가 있고, SIMD 명령 1개는 묶음 전체에 같은 연산을 한 번에 적용합니다. 계란판에 담긴 계란 4개가 같이 이동하고 같이 계산된다고 생각하면 됩니다. 이때 계란판의 칸 하나하나를 lane이라고 부릅니다.&lt;/p>
&lt;p>&lt;code>simd.Float32s&lt;/code>는 이 vector register 하나를 Go 타입으로 나타낸 것입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">NEON(128비트, 맥): [ f32 | f32 | f32 | f32 ] Len() = 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AVX2(256비트): [ f32 | f32 | f32 | f32 | f32 x 4 ] Len() = 8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AVX-512(512비트): [ f32 x 16 ] Len() = 16
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 타입의 특징은 4가지입니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>이름에 개수가 없습니다.&lt;/strong> 하나의 레지스터에 몇 개의 float32가 들어갈지는 하드웨어가 정하기 때문인데요, 그래서 &lt;code>Len()&lt;/code>으로 물어봐야 합니다. 맥(NEON)은 4, AVX2는 8, AVX-512는 16입니다. &lt;code>Float32s&lt;/code>를 쓰면 같은 코드가 어느 CPU에서든 그 CPU에서 지원하는 레지스터의 크기로 설정됩니다.&lt;/li>
&lt;li>&lt;strong>슬라이스가 아니라 값입니다.&lt;/strong> 크기가 고정이라 힙 할당이 없고, 컴파일러가 가능하면 변수를 레지스터에 그대로 둡니다. 루프 내내 메모리를 오가지 않는 것이 SIMD 성능의 핵심입니다.&lt;/li>
&lt;li>&lt;strong>메서드 하나가 명령 하나입니다.&lt;/strong> &lt;code>a.Add(b)&lt;/code>는 덧셈 4개를 동시에 수행하는 명령 1개입니다. 이때 lane 0은 lane 0끼리, lane 1은 lane 1끼리만 더해지고 lane 사이에는 아무 교류가 없습니다.&lt;/li>
&lt;li>&lt;strong>zero value는 모든 lane이 0.0입니다.&lt;/strong> 그래서 &lt;code>var acc simd.Float32s&lt;/code> 선언만으로 누적 계산을 시작할 준비가 끝납니다.&lt;/li>
&lt;/ul>
&lt;p>지금까지의 내용을 그림 하나로 정리하면 이렇습니다.&lt;/p>
&lt;p>&lt;img src="https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/simdVectorLanes.png" alt="vector, lane, 그리고 lane별 Add">&lt;/p>
&lt;p>이제 simd.Float32s를 이해했기 때문에 이 함수를 이해할 수 있는 준비가 됐습니다! 위 함수가 하는 일은&lt;/p>
&lt;ol>
&lt;li>accumulator vector 준비&lt;/li>
&lt;li>lane 수 만큼 x를 잘라서 SIMD로 누적 계산&lt;/li>
&lt;li>accumulator vector의 계산 결과를 배열에 저장&lt;/li>
&lt;li>배열에 저장된 각 lane의 계산 결과를 합산&lt;/li>
&lt;li>lane 수로 나눠서 딱 떨어지지 않고 남은 x의 요소들을 별도로 합산&lt;/li>
&lt;/ol>
&lt;p>생각보다 간단하죠? 이 흐름을 애니메이션으로 보면 더 명확합니다. 12개짜리 슬라이스가 4개씩 레지스터로 올라가고, lane마다 부분합이 쌓이고, 마지막에 부분합 4개가 하나로 합쳐집니다.&lt;/p>
&lt;p>&lt;img src="https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/simdAccumulator.gif" alt="accumulator vector가 부분합을 쌓는 과정">&lt;/p>
&lt;h4 id="4-2-sumsimdunrolled4-함수">4-2. SumSIMDUnrolled4 함수&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">SumSIMDUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a3&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="c1">// 4개의 accumulator vector를 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// vector 하나가 담는 lane 수 확인&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">step&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">4&lt;/span> &lt;span class="c1">// 4개의 accumulator vector -&amp;gt; Mac의 경우 4 * 4 =&amp;gt; 16&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">step&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">step&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// step 단위로 딱 떨어지는 구간을 계산 (Mac은 한 번에 16개)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span> &lt;span class="c1">// 각 accumulator vector에 lane 수만큼 끊어서 누적&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">lanes&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">// step만큼 끊고 남은 나머지 중에서 lane 수만큼 끊을 수 있는 구간을 계산&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">a0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&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">// (a0+a1)과 (a2+a3)를 먼저 묶어서 안쪽 Add 두 개가 겹쳐 실행되게 한다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">a0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a1&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">a3&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">var&lt;/span> &lt;span class="nx">tmp&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">float32&lt;/span> &lt;span class="c1">// 스택에 배열을 생성(AVX512의 경우 최대 float32를 16개 담을 수 있으므로 최댓값으로 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tmp&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">j&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">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">j&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">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">tmp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">j&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">for&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&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">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">x&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">s&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>SumSIMDSingle로 이미 4개씩 동시에 더하고 있는데, 여기서 더 빨라질 수 있을까요? 있습니다. 사실 SumSIMDSingle에는 SumScalar에서 봤던 비효율이 그대로 남아있거든요.&lt;/p>
&lt;p>&lt;code>acc = acc.Add(...)&lt;/code> 는 앞선 Add의 결과가 나와야 다음 Add를 시작할 수 있습니다. vector 덧셈도 결국 덧셈이라 3-4 사이클을 소비하는데요, 그래서 매 루프마다 CPU가 앞선 vector 덧셈이 끝나기를 기다리게 됩니다. SumScalar의 대기 문제가 vector 세계에서 똑같이 재현된 거죠.&lt;/p>
&lt;p>해법도 이미 알고 있습니다. SumUnrolled4에서 썼던 트릭을 vector에 그대로 적용하는 겁니다. 서로 독립적인 accumulator vector를 4개 만들어서 기다림 없이 겹쳐 실행되게 하는 거죠. 말하자면 SumUnrolled4의 SIMD 버전인 셈입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">accumulator 1개: Add ──▶ Add ──▶ Add ──▶ Add ──▶ ... (앞 결과를 기다린다)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">accumulator 4개: a0: Add ───▶ Add ───▶ ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a1: Add ───▶ Add ───▶ ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a2: Add ───▶ Add ───▶ ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> a3: Add ───▶ Add ───▶ ... (4개의 연산이 기다림 없이 진행된다)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그래서 이 함수의 루프는 한 번에 step(= lanes * 4, 맥에서는 16개)만큼 전진합니다. 연속된 4개 구간을 각자의 accumulator에 나눠 담는 거죠.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">한 바퀴 (i = 0, 맥 기준):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">x: [ x0~x3 ][ x4~x7 ][ x8~x11 ][ x12~x15 ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓a0 ↓a1 ↓a2 ↓a3 ← 4개의 Add가 겹쳐서 실행
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 SumSIMDSingle에는 없던 루프가 하나 추가됐는데요, 16개 단위로 끊고 남은 나머지 중에서 4개 단위로 끊을 수 있는 구간을 처리하는 중간 단계입니다. 이 구간은 a0에 몰아서 누적합니다. 많아야 3번 도는 루프라서 accumulator 1개로도 충분하거든요.&lt;/p>
&lt;p>마지막 합치기에도 눈여겨볼 부분이 있습니다. &lt;code>a0.Add(a1).Add(a2.Add(a3))&lt;/code> 는 (a0+a1)과 (a2+a3)를 먼저 계산하는 균형 트리 모양인데요, 이렇게 하면 안쪽 Add 2개도 서로 독립이라 겹쳐 실행됩니다. 마지막까지 기다림을 줄이는 거죠.&lt;/p>
&lt;p>정리하면 이 함수가 하는 일은 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>accumulator vector 4개 준비&lt;/li>
&lt;li>step(16개) 단위로 x를 잘라서 accumulator 4개에 겹쳐서 누적&lt;/li>
&lt;li>남은 구간 중 lane 수만큼 끊을 수 있는 구간은 a0에 누적&lt;/li>
&lt;li>accumulator 4개를 균형 트리로 하나로 합치기&lt;/li>
&lt;li>이후는 SumSIMDSingle과 동일 (배열에 저장 → lane 합산 → 나머지 합산)&lt;/li>
&lt;/ol>
&lt;p>구조가 SumUnrolled4 + SumSIMDSingle 이라는 게 보이시죠? 전체 흐름을 애니메이션으로 보면 이렇습니다. 20개짜리 슬라이스가 한 바퀴에 16개씩 accumulator 4개로 나눠 담기고, 남은 4개는 중간 단계에서 a0에 더해진 뒤, 균형 트리로 하나로 합쳐집니다.&lt;/p>
&lt;p>&lt;img src="https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/simdUnrolled4.gif" alt="SumSIMDUnrolled4의 전체 흐름">&lt;/p>
&lt;p>실제로 얼마나 빨라졌는지는 잠시 뒤 벤치마크에서 확인해 보겠습니다.&lt;/p>
&lt;h4 id="4-3-sumsimdunrolled8-함수">4-3. SumSIMDUnrolled8 함수&lt;/h4>
&lt;p>이 함수는 8개의 accumulator vector를 동시에 사용하면 4개보다 얼마나 빨라질까&amp;hellip;를 테스트해보기 위해서 만들었습니다.&lt;/p>
&lt;p>구조는 Unrolled4와 동일하기 때문에 설명은 생략하겠습니다!&lt;/p>
&lt;h4 id="4-4-polysimd-함수">4-4. PolySIMD 함수&lt;/h4>
&lt;p>마지막으로 두 번째 연산 커널, 삼차 다항식의 SIMD 버전입니다. 그런데 이 함수를 보기 전에 먼저 봐야 할 함수가 하나 있습니다. 바로 splat 함수입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// splat은 모든 lane을 v로 채운 vector를 만든다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// hot loop 밖에서만 호출하므로 이 작은 루프의 비용은 무시해도 된다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&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="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="p">&amp;lt;&lt;/span> &lt;span class="nx">lanes&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">v&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">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="nx">lanes&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>왜 이런 함수가 필요할까요? 다항식 계산에는 0.5, 1.2 같은 상수 계수가 등장하는데요, vector의 &lt;code>Mul&lt;/code>과 &lt;code>Add&lt;/code>는 양쪽 모두 vector여야 합니다. 계란판과 계란 1개를 곱할 수는 없는 거죠. 그래서 상수 하나를 모든 lane에 복사해서 &lt;code>[0.5, 0.5, 0.5, 0.5]&lt;/code> 같은 vector로 만들어야 하는데, 이 작업을 하는 함수가 splat입니다.&lt;/p>
&lt;blockquote>
&lt;p>splat은 &amp;ldquo;철퍼덕 퍼뜨린다&amp;quot;는 뜻으로 SIMD 관련해서 자주 쓰인다고 합니다. broadcast라고도 한다네요.&lt;/p>&lt;/blockquote>
&lt;p>슬라이스를 만들고 작은 루프를 돌긴 하지만, 잠시 뒤에 보듯 hot loop(빡세게 도는 루프) 밖에서 계수당 1번씩만 호출되기 때문에 무시해도 됩니다. splat이 하는 일을 그림으로 보면 이렇습니다.&lt;/p>
&lt;p>&lt;img src="https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/simdSplat.png" alt="splat: 상수 1개를 모든 lane에 복사">&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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">// 0.5v³ + 1.2v² − 3.1v + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = v(0.5v² + 1.2v − 3.1) + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = v(v(0.5v + 1.2) − 3.1) + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// = ((0.5v + 1.2)·v − 3.1)·v + 0.7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">PolySIMD&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">out&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&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="kd">var&lt;/span> &lt;span class="nx">probe&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Float32s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lanes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">probe&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Len&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// lane 수를 확인하는 용도로만 사용&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">// 4개의 다항식 계수를 lane 수만큼 펼쳐서 레지스터에 로드(ex: [0.5, 0.5, 0.5, 0.5])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c3&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">1.2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mf">3.1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c0&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">splat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lanes&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">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&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="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nb">len&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">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">lanes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">// 레지스터에 lane 수만큼 로드&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// c3 * v + c2 =&amp;gt; [0.5*v1+1.2, ... , 0.5*v4+1.2]&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">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c1&lt;/span>&lt;span class="p">)&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">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c0&lt;/span>&lt;span class="p">)&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="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">out&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&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="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="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nb">len&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">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// lane 수만큼 딱 떨어지지 않고 남은 항목에 대해 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">x&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="nx">out&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="mf">0.5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mf">1.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mf">3.1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.7&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>함수 위의 주석은 PolyScalar에서 봤던 호너의 방법 그대로입니다. 거듭제곱 없이 (곱하고 더하기) 3단계로 삼차 다항식을 계산하는 거죠. 이 3단계가 SIMD와 만나면 각 단계가 lane 4개를 동시에 처리하게 됩니다.&lt;/p>
&lt;p>코드를 위에서부터 따라가 보겠습니다.&lt;/p>
&lt;p>먼저 &lt;code>probe&lt;/code>는 계산에는 전혀 쓰이지 않는 변수입니다. &lt;code>Len()&lt;/code>을 물어보는 용도로만 존재하죠. 그 다음 계수 4개를 splat으로 미리 vector로 펼쳐 둡니다. 루프 밖에서 딱 1번씩만 호출되고, 루프 안에서는 이미 레지스터에 준비된 상수 vector를 가져다 쓰기만 하면 됩니다.&lt;/p>
&lt;p>메인 루프는 이렇게 한 번씩 돌아갑니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">한 바퀴 (맥 기준):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">v: [ v0 | v1 | v2 | v3 ] ← x[i:i+4]를 로드
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1단계: r = c3·v + c2 (lane 4개 동시)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2단계: r = r·v + c1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3단계: r = r·v + c0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Store: [ f(v0) | f(v1) | f(v2) | f(v3) ] → out[i:i+4]에 기록
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>lane 0은 v0만으로, lane 1은 v1만으로 자기 다항식을 계산합니다. Add에서 봤던 것과 똑같이 lane 사이에는 아무 교류가 없는 거죠.&lt;/p>
&lt;p>그런데 Sum 함수들과 비교해보면 구조가 눈에 띄게 다릅니다. 3가지가 없거든요.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>accumulator가 없습니다.&lt;/strong> &lt;code>v&lt;/code>와 &lt;code>r&lt;/code>은 매번 새로 만들어지고 해당 반복이 끝나면 사라집니다. 각 반복 사이에 이어지는 값이 없는 거죠.&lt;/li>
&lt;li>&lt;strong>horizontal reduction이 없습니다.&lt;/strong> 결과를 &lt;code>Store&lt;/code>로 out에 바로 내려쓰면 끝입니다. lane끼리 합칠 일이 없으니까요.&lt;/li>
&lt;li>&lt;strong>unrolled 버전이 없습니다.&lt;/strong> 4-2에서 봤던 대기 문제는 이전 계산의 결과를 기다릴 때 생기는데, 이 루프는 각 반복 사이에 의존성이 없습니다. 그래서 accumulator를 늘리지 않아도 CPU가 알아서 여러 반복을 겹쳐 실행합니다.&lt;/li>
&lt;/ol>
&lt;p>그러고 나면 마지막으로 동일한 계산식으로 나머지 요소들을 계산합니다.&lt;/p>
&lt;p>전체 흐름을 애니메이션으로 보면 이렇습니다. 미리 준비된 계수 vector 4개 아래로, 4개씩 로드 → 호너 3단계 → out에 기록이 반복됩니다. 각 단계에서 사용되는 계수가 주황색으로 표시됩니다.&lt;/p>
&lt;p>&lt;img src="https://netscout.github.io/posts/simd%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-feat-go/simdPoly.gif" alt="PolySIMD의 전체 흐름">&lt;/p>
&lt;p>정리하면 이 함수가 하는 일은 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>계수 4개를 splat으로 vector화 (루프 밖에서 1번)&lt;/li>
&lt;li>lane 수만큼 로드해서 호너의 방법 3단계로 계산&lt;/li>
&lt;li>결과를 out에 바로 저장&lt;/li>
&lt;li>남은 원소는 같은 모양의 스칼라 코드로 계산&lt;/li>
&lt;/ol>
&lt;p>Sum보다 오히려 단순하죠? 이제 SIMD 커널은 모두 준비되었습니다.&lt;/p>
&lt;h3 id="5-대체-커널-구현하기kernel_stubgo">5. 대체 커널 구현하기(kernel_stub.go)&lt;/h3>
&lt;p>4번 섹션 초반에 다시 돌아온다고 이야기 했던 바로 그 부분입니다. &lt;code>GOEXPERIMENT=simd&lt;/code> 없이 빌드하면 kernel_simd.go가 통째로 빠지면서 SumSIMDSingle 같은 함수들이 존재하지 않게 되고, 이 함수들을 부르는 코드는 전부 컴파일 오류가 난다고 했었죠. 그 구멍을 메우는 파일이 kernel_stub.go입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//go:build !goexperiment.simd&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="kn">package&lt;/span> &lt;span class="nx">kernels&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">func&lt;/span> &lt;span class="nf">SumSIMDUnrolled8&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nf">SumUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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="kd">func&lt;/span> &lt;span class="nf">SumSIMDUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nf">SumUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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="kd">func&lt;/span> &lt;span class="nf">SumSIMDSingle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nf">SumUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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="kd">func&lt;/span> &lt;span class="nf">PolySIMD&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">out&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nf">PolyScalar&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">out&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>첫 줄의 build tag를 보면 &lt;code>!goexperiment.simd&lt;/code>, kernel_simd.go와 정확히 반대 조건입니다. 그래서 어떤 환경에서 빌드하든 두 파일 중 정확히 하나만 빌드에 포함됩니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">GOEXPERIMENT=simd 있음: kernel_simd.go ✓ kernel_stub.go ✗ → 진짜 SIMD
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GOEXPERIMENT=simd 없음: kernel_simd.go ✗ kernel_stub.go ✓ → 스칼라 fallback
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>내용은 아주 단순합니다. SIMD 함수들과 같은 이름의 함수를 선언하고, 몸통에서는 우리가 가진 가장 빠른 스칼라 버전(SumUnrolled4, PolyScalar)을 그대로 호출합니다. 덕분에 SIMD를 지원하지 않는 환경에서도 컴파일이 항상 성공하고, 호출하는 쪽은 아무것도 바꿀 필요가 없죠.&lt;/p>
&lt;blockquote>
&lt;p>한 가지 조심할 점이 있는데요, 이 파일 덕분에 GOEXPERIMENT=simd를 빼먹어도 아무 오류 없이 잘 돌아갑니다. &amp;ldquo;SIMD가 생각보다 안 빠른데?&amp;rdquo; 싶으면 환경변수부터 확인해보세요!&lt;/p>&lt;/blockquote>
&lt;h3 id="6-테스트-작성하기kernel_testgo">6. 테스트 작성하기(kernel_test.go)&lt;/h3>
&lt;p>커널이 다 모였으니 이제 두 가지를 확인해야 합니다. 정답을 내는가(test), 그리고 얼마나 빠른가(benchmark). 순서는 아무래도 정확성이 우선이겠죠?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">kernels&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;math&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;math/rand/v2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;testing&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">x&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&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="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">x&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">rand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Float32&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">x&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">// 정확성 우선. 허용 오차를 두는 이유: SIMD는 스칼라와 다른 순서로 더하므로&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">func&lt;/span> &lt;span class="nf">TestSumImplsMatchScalar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">t&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">T&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">impls&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fn&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&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="s">&amp;#34;unrolled4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumUnrolled4&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="s">&amp;#34;simd-single&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumSIMDSingle&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="s">&amp;#34;simd-unrolled4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumSIMDUnrolled4&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="s">&amp;#34;simd-unrolled8&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumSIMDUnrolled8&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="c1">// 모든 분기를 지나도록 길이를 고른다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 32의 배수는 8-vector 본 루프만, 그 사이 값은 중간 단계와 스칼라 tail을 지난다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 0과 1은 본 루프에 한 번도 들어가지 않는 극단값이다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">int&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">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&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">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">17&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">31&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">33&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">63&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">65&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10_037&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">x&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">want&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">SumScalar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">impl&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">impls&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s/n=%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">impl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">t&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">T&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">got&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">impl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">fn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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%에 절대 오차 1e-6을 더한다. 합이 0에 가까운 극단값 대비.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Abs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">float64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">want&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">got&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="mf">1e-2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Abs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">float64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">want&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mf">1e-6&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;scalar=%v got=%v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">want&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">got&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;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">func&lt;/span> &lt;span class="nf">BenchmarkSum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&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">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">1024&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">65536&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">20&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">x&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">impl&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fn&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&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="s">&amp;#34;scalar&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumScalar&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="s">&amp;#34;unrolled4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumUnrolled4&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="s">&amp;#34;simd-single&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumSIMDSingle&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="s">&amp;#34;simd-unrolled4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumSIMDUnrolled4&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="s">&amp;#34;simd-unrolled8&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SumSIMDUnrolled8&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s/n=%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">impl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&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">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">int64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">// → MB/s in the output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Loop&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// Go 1.24+: keeps the call alive&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">impl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">fn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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;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">func&lt;/span> &lt;span class="nf">BenchmarkPoly&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&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">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">1024&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">65536&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">20&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">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">out&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;scalar/n=%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&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">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">int64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="o">*&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="k">for&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Loop&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="nf">PolyScalar&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">out&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="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;simd/n=%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&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">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">int64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="o">*&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="k">for&lt;/span> &lt;span class="nx">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Loop&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="nf">PolySIMD&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">out&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>&lt;strong>data 함수&lt;/strong>는 랜덤 float32 슬라이스를 만드는 도우미입니다. 테스트와 벤치마크가 같이 씁니다.&lt;/p>
&lt;p>&lt;strong>TestSumImplsMatchScalar&lt;/strong>는 Sum 구현 4개를 전부 SumScalar와 비교하는 테스트입니다. 가장 단순한 SumScalar를 정답지로 삼는 거죠. 눈여겨볼 부분이 2가지 있습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>다양한 길이의 데이터를 사용합니다.&lt;/strong> 0, 1, 17, 33 같은 값이 섞여 있는 이유는 모든 분기를 지나가게 만들기 위해서입니다. 32의 배수는 Unrolled8의 본 루프만 지나가고, 어중간한 값은 중간 단계와 스칼라 tail까지 지나갑니다. 0과 1은 vector 루프에 한 번도 못 들어가는 극단값이고요.&lt;/li>
&lt;li>&lt;strong>완전히 같은 값이 아니라 허용 오차로 비교합니다.&lt;/strong> float 덧셈은 더하는 순서가 바뀌면 마지막 자리가 조금 달라질 수 있습니다(결합법칙이 성립하지 않습니다). SIMD는 스칼라와 더하는 순서가 다르니까 &lt;code>==&lt;/code> 비교는 잘못된 검증인 거죠. 그래서 상대 오차 1%에, 합이 0에 가까운 경우를 대비한 절대 오차 1e-6(0.000001)을 더해서 비교합니다.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>BenchmarkSum&lt;/strong>은 구현 5개 × 크기 3개 = 15개의 서브벤치마크를 만듭니다. 크기 3개는 cache에 쏙 들어가는 1024부터 cache를 벗어나는 100만 개까지, 메모리 계층이 달라지는 구간을 골랐습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;code>b.Run&lt;/code>에 붙인 이름이 결과에 &lt;code>BenchmarkSum/scalar/n=1024&lt;/code> 형태로 그대로 나타납니다.&lt;/li>
&lt;li>&lt;code>b.SetBytes(n*4)&lt;/code>는 1번 호출이 n×4 바이트를 처리한다고 알려주는 겁니다. 이 덕분에 결과에 MB/s 컬럼이 붙어서 크기가 다른 벤치마크끼리도 처리량으로 비교할 수 있습니다.&lt;/li>
&lt;li>&lt;code>for b.Loop()&lt;/code>는 Go 1.24에 추가된 벤치마크 루프입니다. 정해진 시간(기본 1초)이 찰 때까지 알아서 반복하고, 반환값을 안 쓰더라도 컴파일러가 호출 자체를 없애버리지 않도록 지켜줍니다.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>BenchmarkPoly&lt;/strong>도 같은 패턴입니다. out 슬라이스를 벤치마크 루프 밖에서 만들어 재사용하기 때문에 측정값에 메모리 할당 비용이 섞이지 않습니다.&lt;/p>
&lt;p>이제 정말로 돌려볼 시간입니다!&lt;/p>
&lt;h2 id="테스트와-벤치마크-돌려보기">테스트와 벤치마크 돌려보기&lt;/h2>
&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; &lt;span class="nv">GOEXPERIMENT&lt;/span>&lt;span class="o">=&lt;/span>simd go1.27rc2 &lt;span class="nb">test&lt;/span> -run&lt;span class="o">=&lt;/span>Test -v ./...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN &lt;span class="nv">TestSumImplsMatchScalar&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="nv">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="nv">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="nv">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="nv">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="nv">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled8/n&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> RUN TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">10037&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="nv">10037&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="nv">10037&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> RUN TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="m">10037&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--- PASS: TestSumImplsMatchScalar &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="o">(&lt;/span>0.00s&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"> --- PASS: TestSumImplsMatchScalar/unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">10037&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-single/n&lt;span class="o">=&lt;/span>&lt;span class="m">10037&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled4/n&lt;span class="o">=&lt;/span>&lt;span class="m">10037&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --- PASS: TestSumImplsMatchScalar/simd-unrolled8/n&lt;span class="o">=&lt;/span>&lt;span class="m">10037&lt;/span> &lt;span class="o">(&lt;/span>0.00s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PASS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ok simdbench &lt;span class="o">(&lt;/span>cached&lt;span class="o">)&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nv">GOEXPERIMENT&lt;/span>&lt;span class="o">=&lt;/span>simd go1.27rc2 &lt;span class="nb">test&lt;/span> -bench&lt;span class="o">=&lt;/span>. -benchmem ./...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">goos: darwin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">goarch: arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pkg: simdbench
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cpu: Apple M5 Pro
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">2244930&lt;/span> 532.5 ns/op 7691.73 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/unrolled4/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">4383312&lt;/span> 273.3 ns/op 14985.48 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-single/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">8875933&lt;/span> 134.0 ns/op 30566.12 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-unrolled4/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">19148451&lt;/span> 62.21 ns/op 65842.86 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-unrolled8/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">20095088&lt;/span> 59.71 ns/op 68592.91 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">31131&lt;/span> &lt;span class="m">38428&lt;/span> ns/op 6821.65 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/unrolled4/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">62958&lt;/span> &lt;span class="m">19042&lt;/span> ns/op 13766.93 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-single/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">122510&lt;/span> &lt;span class="m">9654&lt;/span> ns/op 27153.36 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-unrolled4/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">318691&lt;/span> &lt;span class="m">3733&lt;/span> ns/op 70220.68 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-unrolled8/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">332295&lt;/span> &lt;span class="m">3638&lt;/span> ns/op 72056.81 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">1942&lt;/span> &lt;span class="m">613160&lt;/span> ns/op 6840.47 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/unrolled4/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">3706&lt;/span> &lt;span class="m">308846&lt;/span> ns/op 13580.58 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-single/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">7534&lt;/span> &lt;span class="m">159293&lt;/span> ns/op 26330.67 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-unrolled4/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">19327&lt;/span> &lt;span class="m">61679&lt;/span> ns/op 68001.61 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/simd-unrolled8/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">20790&lt;/span> &lt;span class="m">57774&lt;/span> ns/op 72597.91 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkPoly/scalar/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">3528297&lt;/span> 340.6 ns/op 12024.97 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkPoly/simd/n&lt;span class="o">=&lt;/span>1024-18 &lt;span class="m">6932178&lt;/span> 172.9 ns/op 23692.89 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkPoly/scalar/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">55312&lt;/span> &lt;span class="m">21780&lt;/span> ns/op 12036.22 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkPoly/simd/n&lt;span class="o">=&lt;/span>65536-18 &lt;span class="m">113785&lt;/span> &lt;span class="m">10453&lt;/span> ns/op 25077.27 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkPoly/scalar/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">3398&lt;/span> &lt;span class="m">349927&lt;/span> ns/op 11986.24 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkPoly/simd/n&lt;span class="o">=&lt;/span>1048576-18 &lt;span class="m">7154&lt;/span> &lt;span class="m">168198&lt;/span> ns/op 24936.76 MB/s &lt;span class="m">0&lt;/span> B/op &lt;span class="m">0&lt;/span> allocs/op
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PASS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ok simdbench 25.564s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="결과-한-줄-해부하기">결과 한 줄 해부하기&lt;/h3>
&lt;p>처음 보면 숫자가 잔뜩이라 어지러운데요, 한 줄만 잘라서 보면 구조가 단순합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n=1024-18 2244930 532.5 ns/op 7691.73 MB/s 0 B/op 0 allocs/op
&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;ul>
&lt;li>&lt;strong>이름&lt;/strong>: BenchmarkSum 함수 아래에 &lt;code>b.Run&lt;/code>으로 만든 서브벤치마크 이름이 그대로 붙습니다.&lt;/li>
&lt;li>&lt;strong>① &lt;code>-18&lt;/code>&lt;/strong>: GOMAXPROCS 값입니다. 이 벤치마크를 실행 중인 맥의 CPU 코어가 18개라는 표시입니다.&lt;/li>
&lt;li>&lt;strong>② 반복 횟수&lt;/strong>: 측정 시간 동안 함수를 몇 번 호출했는지 센 값입니다. 설정값이 아니라 결과물인 거죠. &lt;code>b.Loop&lt;/code>가 약 1.2초 동안 돌 수 있는 만큼 돌고 멈춘 겁니다. 실제로 2,244,930 × 532.5ns ≈ 1.2초가 나옵니다.&lt;/li>
&lt;li>&lt;strong>③ ns/op&lt;/strong>: 호출 1번에 걸린 시간인데요, 사실 이 부분 말고는 다 사족에 불과합니다!&lt;/li>
&lt;li>&lt;strong>④ MB/s&lt;/strong>: 초당 처리한 데이터 양입니다. &lt;code>b.SetBytes&lt;/code>를 호출했기 때문에 붙는 컬럼이고, 크기가 다른 벤치마크끼리 비교할 때는 이 컬럼이 제일 편합니다.&lt;/li>
&lt;li>&lt;strong>⑤ B/op, allocs/op&lt;/strong>: &lt;code>-benchmem&lt;/code> 옵션이 붙여주는 컬럼입니다. 호출 1번당 힙 할당량인데요, 전부 0인 이유는 커널들이 &lt;code>var tmp [16]float32&lt;/code>처럼 스택 배열만 쓰기 때문입니다. 여기 0이 아닌 값이 보이면 측정에 할당 비용이 섞이고 있다는 신호입니다.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>반복 횟수를 ops/sec로 읽으면 안 됩니다. 측정 창이 정확히 1초가 아니거든요. 속도 정보는 ns/op와 MB/s에 있고, 반복 횟수는 &amp;ldquo;몇 번의 호출을 바탕으로 ns/op와 MB/s를 도출했나&amp;quot;를 뜻합니다.&lt;/p>&lt;/blockquote>
&lt;p>이렇게 읽고 나면 n=1024의 Sum 결과에서 속도가 개선되는 경향이 보입니다. 532 → 273 → 134 → 62 → 60 ns로, 단계마다 절반씩 줄어들다가 마지막에 멈칫하죠. 그런데 이 표는 각 벤치마크를 1번씩만 잰 겁니다. 1번의 측정은 동전 던지기 1번과 같아서, CPU 발열이나 백그라운드 작업에 따라 몇 %씩 쉽게 흔들립니다. 마지막에 멈칫 한게 진짜인지 아니면 뭔가가 개입해서 그런지는 명확하지 않은 거죠.&lt;/p>
&lt;h3 id="10번-재서-통계로-판정하기">10번 재서 통계로 판정하기&lt;/h3>
&lt;p>그래서 제대로 잴 때는 &lt;code>-count=10&lt;/code>으로 10번 반복하고, 결과를 파일로 저장합니다.&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; &lt;span class="nv">GOEXPERIMENT&lt;/span>&lt;span class="o">=&lt;/span>simd go1.27rc2 &lt;span class="nb">test&lt;/span> -bench&lt;span class="o">=&lt;/span>. -count&lt;span class="o">=&lt;/span>&lt;span class="m">10&lt;/span> ./... &amp;gt; result.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>result.txt를 열어보면 같은 이름이 10줄씩, 조금씩 다른 숫자로 반복됩니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">goos: darwin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">goarch: arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pkg: simdbench
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cpu: Apple M5 Pro
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n=1024-18 2344214 503.0 ns/op 8142.36 MB/s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n=1024-18 2438886 492.0 ns/op 8324.60 MB/s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n=1024-18 2422869 493.4 ns/op 8300.86 MB/s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n=1024-18 2431774 493.6 ns/op 8298.66 MB/s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BenchmarkSum/scalar/n=1024-18 2439853 492.7 ns/op 8314.17 MB/s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...(이하 200여 줄 생략)...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>벤치마크를 10번씩 실행하면 몇가지 잡음이 있더라도 분포를 통해 경향을 비교적 정확하게 파악할 수 있습니다.&lt;/p>
&lt;h3 id="결과가-말해주는-것">결과가 말해주는 것&lt;/h3>
&lt;p>10개 표본의 중앙값으로 n=1024의 벤치 결과를 그려보면 이렇습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">scalar 492.7 ns ████ 8.3 GB/s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">unrolled4 254.1 ns ████████ 16.1 GB/s ×1.94
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">simd-single 126.3 ns ████████████████ 32.4 GB/s ×2.01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">simd-unrolled4 58.4 ns ███████████████████████████████ 70.2 GB/s ×2.16
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">simd-unrolled8 56.7 ns ████████████████████████████████ 72.2 GB/s ×1.03
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>각 단계가 병목을 하나씩 제거하면서 약 2배씩 빨라졌습니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>단계&lt;/th>
&lt;th>제거한 병목&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>scalar → unrolled4&lt;/td>
&lt;td>덧셈 대기 사슬. 독립 accumulator 4개로 덧셈을 겹쳐 실행&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>unrolled4 → simd-single&lt;/td>
&lt;td>명령 1개가 float32 1개만 처리하던 한계. lane 4개를 동시에 계산&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>simd-single → simd-unrolled4&lt;/td>
&lt;td>vector 덧셈의 대기 사슬. accumulator vector 4개로 다시 겹침&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>simd-unrolled4 → simd-unrolled8&lt;/td>
&lt;td>남은 병목이 거의 없음. 그래서 3~4%에 그침 (아래에서 설명)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>크기별 처리량(GB/s, 중앙값)을 보면 또 하나 재미있는 점이 있습니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>구현&lt;/th>
&lt;th>n=1024&lt;/th>
&lt;th>n=65536&lt;/th>
&lt;th>n=1M&lt;/th>
&lt;th>scalar 대비 (n=1M)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>scalar&lt;/td>
&lt;td>8.3&lt;/td>
&lt;td>7.3&lt;/td>
&lt;td>7.3&lt;/td>
&lt;td>×1.0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>unrolled4&lt;/td>
&lt;td>16.1&lt;/td>
&lt;td>14.6&lt;/td>
&lt;td>14.4&lt;/td>
&lt;td>×2.0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>simd-single&lt;/td>
&lt;td>32.4&lt;/td>
&lt;td>28.3&lt;/td>
&lt;td>28.2&lt;/td>
&lt;td>×3.9&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>simd-unrolled4&lt;/td>
&lt;td>70.2&lt;/td>
&lt;td>71.7&lt;/td>
&lt;td>70.9&lt;/td>
&lt;td>×9.7&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>simd-unrolled8&lt;/td>
&lt;td>72.2&lt;/td>
&lt;td>74.4&lt;/td>
&lt;td>73.9&lt;/td>
&lt;td>×10.1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>크기가 100만 개로 커져도(cache를 완전히 벗어나도) 처리량이 거의 그대로입니다. 병목이 memory가 아니라 계산 쪽에 있다는 뜻이죠. memory 대역폭이 한계였다면 n=1M에서 그래프가 뚝 꺾였을 겁니다.&lt;/p>
&lt;p>그러면 4개 → 8개 unroll이 정말 이득이 있긴 한 걸까요? 이럴 때 쓰는 도구가 benchstat입니다. unrolled4 줄과 unrolled8 줄을 뽑아서 같은 이름으로 바꾼 파일 2개를 만들면, benchstat가 둘을 짝지어 통계 판정을 내려줍니다.&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; grep &lt;span class="s1">&amp;#39;simd-unrolled4&amp;#39;&lt;/span> result.txt &lt;span class="p">|&lt;/span> sed &lt;span class="s1">&amp;#39;s|simd-unrolled4|X|&amp;#39;&lt;/span> &amp;gt; u4.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; grep &lt;span class="s1">&amp;#39;simd-unrolled8&amp;#39;&lt;/span> result.txt &lt;span class="p">|&lt;/span> sed &lt;span class="s1">&amp;#39;s|simd-unrolled8|X|&amp;#39;&lt;/span> &amp;gt; u8.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; benchstat u4.txt u8.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl"> unrolled4 unrolled8 delta
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Sum/n=1024 58.35n → 56.67n -2.88% (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Sum/n=65536 3.655µ → 3.522µ -3.65% (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Sum/n=1048576 59.09µ → 56.71µ -4.03% (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>p=0.000은 &amp;ldquo;이 차이가 우연일 확률이 사실상 0&amp;quot;이라는 뜻입니다. 그러니까 진짜 이득은 맞는데, 3&lt;del>4%뿐인 거죠. 이유는 4-2에서 배운 원리 그대로입니다. 여러 accumulator는 CPU가 앞선 덧셈을 기다리는 동안만 일감을 주는데, 4개면 그 대기 시간이 이미 거의 다 채워집니다. 그 다음 한계는 CPU가 1 cycle에 발행할 수 있는 명령 수와 memory에서 데이터를 끌어오는 속도인데, accumulator를 늘려도 이 둘은 개선할 수 없습니다. 남은 3&lt;/del>4%는 루프 1바퀴가 32개를 처리하면서 비교/분기 비용이 절반으로 준 몫입니다. 즉, 합계 구하기의 경우 SIMD + 4개 accumulator 사용하기가 최적인 셈이죠.&lt;/p>
&lt;p>Poly는 어떨까요?&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>구현&lt;/th>
&lt;th>n=1024&lt;/th>
&lt;th>n=65536&lt;/th>
&lt;th>n=1M&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>scalar&lt;/td>
&lt;td>12.1&lt;/td>
&lt;td>12.3&lt;/td>
&lt;td>12.2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>simd&lt;/td>
&lt;td>24.2&lt;/td>
&lt;td>25.6&lt;/td>
&lt;td>25.6&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>모든 크기에서 일관되게 약 2.1배입니다. lane이 4개인데 왜 4배가 안 될까요? 호너의 방법 3단계가 앞 단계의 결과에 의존하는 사슬이라서, vector 안에서도 대기가 남기 때문입니다. Sum에서 accumulator를 늘렸던 것처럼, 서로 다른 원소 묶음 여러 개를 겹쳐서 처리하는 unroll을 실험해볼 수 있을 것 같습니다.&lt;/p>
&lt;h3 id="후속-실험-poly도-unroll-해보기">후속 실험: Poly도 unroll 해보기&lt;/h3>
&lt;p>후보로만 남겨두면 아쉬우니 바로 실험해봤습니다. 한 번에 원소 묶음 4개(16개)를 처리하는 PolySIMDUnrolled4를 추가했는데요, 핵심은 연산을 원소별이 아니라 &lt;strong>단계별로&lt;/strong> 묶는 겁니다. 이렇게 하면 매 단계마다 서로 독립적인 연산 4개를 CPU가 처리하게 됩니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">v0&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">simd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LoadFloat32s&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">i&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">lanes&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">// v1, v2, v3도 같은 방식으로 로드&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 class="c1">// 1단계 4개를 먼저 전부, 그 다음 2단계 4개, 3단계 4개 순서로&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r0&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v0&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v1&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v2&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r3&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c3&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v3&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c2&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">r0&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">r0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v0&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ... r1, r2, r3도 동일&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sum 때처럼 정확성 테스트(TestPolyImplsMatchScalar)를 먼저 통과시키고, &lt;code>-count=10&lt;/code>으로 재서 benchstat로 판정했습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl"> PolySIMD PolySIMDUnrolled4 delta
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Poly/n=1024 169.0n → 160.6n -4.97% (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Poly/n=65536 10.358µ → 9.678µ -6.56% (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Poly/n=1048576 168.1µ → 154.4µ -8.15% (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>5&lt;del>8% 빨라졌습니다. p=0.000이니 진짜 이득이고, Sum의 4개 → 8개(3&lt;/del>4%)보다는 얻는 이익이 크긴 한데&amp;hellip; 굳이? 싶은 느낌도 있긴 하네요.&lt;/p>
&lt;blockquote>
&lt;p>참고로 PolySIMDUnrolled4의 전체 코드와 정확성 테스트(TestPolyImplsMatchScalar), 벤치마크 추가분은 이 글의 저장소 simdbench 폴더에 있습니다.&lt;/p>&lt;/blockquote>
&lt;h3 id="마지막-실험-core는-다-쓰고-있었을까">마지막 실험: core는 다 쓰고 있었을까?&lt;/h3>
&lt;p>여기까지 읽고 이런 의문이 들 수 있습니다. &amp;ldquo;SumSIMDUnrolled4가 이미 core를 다 쓰고 있는 거라면, 더 병렬화할 곳은 없을까?&amp;rdquo; 그런데 사실 지금까지의 모든 측정에는 함정이 하나 숨어 있습니다. 벤치마크 결과에 표시되던 &lt;code>-18&lt;/code>은 GOMAXPROCS 표시일 뿐이고, 벤치마크는 goroutine 1개를 쓰고 있었습니다. 70 GB/s가 전부 &lt;strong>core 1개&lt;/strong>의 처리량이었던 거죠.&lt;/p>
&lt;p>지금까지는 2개의 병렬화 수단(accumulator로 명령을 겹치기, 그리고 lane 4개로 데이터를 겹치는 SIMD)을 이용했습니다. 그런데 둘 다 core 1개 안에서 일어나는 일이기 때문에 모든 코어를 다 활용하진 못했던 거죠. 이번에 확인할 마지막 SIMD 커널에서는 슬라이스를 core 수만큼 나눠서 goroutine마다 SumSIMDUnrolled4를 돌리고, 부분합을 하나로 합쳐볼 겁니다!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// SumSIMDParallel은 슬라이스를 core 수만큼 나눠서 goroutine마다 SumSIMDUnrolled4를 돌린다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// SIMD(lane)와 ILP(accumulator)에 이어 세 번째 병렬화 요소인 core 병렬화를 추가하는 구현이다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">SumSIMDParallel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">workers&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">runtime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GOMAXPROCS&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="c1">// 작은 입력(65535 이하)는 goroutine을 만들고 기다리는 비용이 계산 비용보다 크다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">&amp;lt;&amp;lt;&lt;/span>&lt;span class="mi">16&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">workers&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 class="k">return&lt;/span> &lt;span class="nf">SumSIMDUnrolled4&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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">// 남는 데이터가 발생하지 않도록 worker-1을 더하고 worker로 나눠준다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">chunk&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">workers&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">workers&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 각 worker의 출력을 저장할 슬라이스&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">partial&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">float32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">workers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">wg&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">WaitGroup&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">w&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">w&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">workers&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">w&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">lo&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">w&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">chunk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 남는 데이터가 발생하지 않도록 chunk의 크기를 조정했기 때문에, 후반부 작업에서는 worker의 시작 지점이 데이터의 길이를 넘어서는 경우가 있음.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">lo&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&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">break&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">hi&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">lo&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nx">chunk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">x&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&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="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">hi&lt;/span> &lt;span class="kt">int&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">defer&lt;/span> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">partial&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">SumSIMDUnrolled4&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">lo&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nx">hi&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">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">hi&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">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wait&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">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="kt">float32&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="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">p&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">partial&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">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">s&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>위 코드에서 chunk를 나누는 부분이 헷갈릴 수 있는데요,(제가 그랬다는 겁니다&amp;hellip;) 그림으로 보면 좀 더 명확합니다.&lt;/p>
&lt;p align="center">&lt;img src="./chunk-dividing.png" />&lt;/p>
&lt;p>10개 입력에 대해서 3개의 worker가 작업을 나눠가질 때, 10/3을 해버리면 결과가 3이 되어서 9번 요소는 버려지는 상황이 되죠. 그래서 딱 떨어지는 경우를 제외하고 +1의 경계에 걸리도록 worker-1을 더해주고 나누도록 합니다. 뭐&amp;hellip; 그렇답니다!&lt;/p>
&lt;blockquote>
&lt;p>이 함수를 추가할 때는 kernel_simd.go의 import에 runtime과 sync를 추가해야 합니다. 측정에 쓴 벤치마크(BenchmarkSumBig)를 포함한 전체 코드는 이 글의 저장소 simdbench 폴더에 있습니다.&lt;/p>&lt;/blockquote>
&lt;p>작은 입력에 core 병렬화를 걸면 비효율이 증가합니다. 그래서 큰 입력 2개(1M, 16M 원소)로 측정해봤는데요, 16M(64MB)은 cache를 완전히 벗어나 DRAM까지 내려가는 크기입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl"> simd-unrolled4 simd-parallel
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">n=1M (4MB) 60.06µ → 28.55µ ×2.1 (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">n=16M (64MB) 1003.3µ → 329.7µ ×3.0 (p=0.000, n=10)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">처리량: 70 GB/s → 147 GB/s (n=1M) → 204 GB/s (n=16M)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>core를 18개나 줬는데 3배밖에 안 빨라졌습니다. 왜일까요? 204 GB/s가 CPU core들이 실제로 끌어낼 수 있는 실효 대역폭 근처이기 때문입니다. core 1개가 이미 70 GB/s, 그러니까 실효 대역폭의 1/3을 혼자 끌어오고 있었던 겁니다. core가 18개라도 메모리가 데이터를 그보다 빨리 못 내주는 거죠.&lt;/p>
&lt;p>앞에서 진행했던 single core 측정에서는 크기가 커져도 처리량이 안 꺾여서 계산 병목이라고 했었는데요, 정확히 말하면 &amp;ldquo;core 1개의 명령 발행 한계가 core 1개 몫의 메모리 대역폭보다 낮았던&amp;rdquo; 겁니다.&lt;/p>
&lt;p>정리하면 병렬화의 축은 3개이고, 축마다 부딪히는 벽이 다릅니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>축&lt;/th>
&lt;th>기법&lt;/th>
&lt;th>부딪히는 벽&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>ILP&lt;/td>
&lt;td>accumulator 4개로 명령 겹치기&lt;/td>
&lt;td>덧셈 latency → 명령 발행 폭&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>SIMD&lt;/td>
&lt;td>lane 4개로 데이터 겹치기&lt;/td>
&lt;td>명령 발행 폭&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>core&lt;/td>
&lt;td>goroutine으로 나눠 돌리기&lt;/td>
&lt;td>&lt;strong>DRAM 대역폭&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>축 3개를 다 쌓으면 n=16M 기준 scalar 대비 약 27배입니다(scalar 처리량 7.3 GB/s로 환산하면 약 8.8ms → 330µs). 여기까지 하고 나면 더 적용할만한 방법은 없어보입니다. 데이터를 읽는 속도보다 빠르게 더할 수는 없으니까요.&lt;/p>
&lt;p>정리하면 이번 측정의 교훈은 4가지입니다.&lt;/p>
&lt;ol>
&lt;li>최적화는 병목을 제거할 때만 배수로 돌아옵니다. 병목이 사라진 뒤의 같은 기법은 몇 %짜리입니다.&lt;/li>
&lt;li>몇 % 차이는 눈으로 판정할 수 없습니다. &lt;code>-count=10&lt;/code>과 benchstat의 p 값이 근거입니다.&lt;/li>
&lt;li>벤치마크가 빠르다는 것과 결과가 맞다는 것은 별개입니다. 정확성 테스트가 벤치마크보다 먼저입니다.&lt;/li>
&lt;li>병렬화의 축은 3개입니다(ILP, SIMD, core). 그리고 마지막 벽은 memory 대역폭입니다. 데이터를 읽는 속도보다 빠르게 계산할 수는 없습니다.&lt;/li>
&lt;/ol>
&lt;h2 id="마치면서">마치면서&lt;/h2>
&lt;p>생각보다 복잡한 과정이긴 했지만, 그래도 SIMD가 뭐다 정도는 이해를 한 거 같습니다. 곧 까먹긴 하겠지만요&amp;hellip;하하하하하&amp;hellip;&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://youtu.be/4nJ2tEPD4-k?si=vpg1EWAFg4DQ4DkX">People Are Mad They&amp;rsquo;re Told to Learn&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://mitchellh.com/writing/everyone-should-know-simd">Everyone Should Know SIMD&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Typescript: Temporal</title><link>https://netscout.github.io/posts/typescript-temporal/</link><pubDate>Tue, 04 Aug 2026 20:42:31 +0900</pubDate><guid>https://netscout.github.io/posts/typescript-temporal/</guid><description>Javascript는 아주 혼란스러운 시간을 지나왔습니다. 특히 es6로 넘어가기 전까지는 혼돈 그 자체였죠. 이제서야 어엿한 메이저 언어로서 자리매김했지만 한때는 정말 느리고, 건드리기 싫었던 언어였습니다. 그 덕에 2000년대 말에 RIA(플래시, 실버라이트 …</description><content>&lt;p>Javascript는 아주 혼란스러운 시간을 지나왔습니다. 특히 es6로 넘어가기 전까지는 혼돈 그 자체였죠. 이제서야 어엿한 메이저 언어로서 자리매김했지만 한때는 정말 느리고, 건드리기 싫었던 언어였습니다. 그 덕에 2000년대 말에 RIA(플래시, 실버라이트 등)가 아주 인기를 끌기도 했었죠.&lt;/p>
&lt;p>Javascript는 그 이후에 많은 변화를 거치면서 정돈되어 왔습니다. 그런데 아직까지도 제대로 해결되지 못한 부분도 여전히 존재합니다. 최근 Typescript 6.0에 Temporal 이라는 최상위 네임스페이스(ex: Math)가 추가되었는데요, 기존에 사용되어 왔던 Date도 아주 오래전 부터 해결되지 못했던 부분입니다. Temporal에 대한 이야기를 하기 전에 잠시 왜 이런 문제가 발생했는지 짧게 짚고 넘어가죠!&lt;/p>
&lt;h2 id="javascript의-간략한-역사">Javascript의 간략한 역사&lt;/h2>
&lt;p>Maggie Pint에 따르면 2017년에 Javascript를 만든 브랜든 아이크(Brendan Eich)와 &lt;a href="https://maggiepint.com/2017/04/09/fixing-javascript-date-getting-started/">다음과 같은 대화를 나눴다고 합니다.&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>It is now common knowledge that in 1995 Brendan was given only 10 days to write the JavaScript language and get it into Netscape. Date handling is a fundamental part of almost all programming languages, and JavaScript had to have it. That said, it’s a complex problem domain and there was a short timeline. Brendan, under orders to “make it like Java” copied the date object from the existing, infant, java.Util.Date date implementation. This implementation was frankly terrible. In fact, basically all of it’s methods were deprecated and replaced in the Java 1.1 release in 1997. Yet we’re still living with this API 20 years later in the JavaScript programming language.&lt;/p>&lt;/blockquote>
&lt;p>1995년 당시 Netscape 브라우저 출시를 앞두고 웹 페이지에 동적인 액션을 구현할 스크립트 언어가 필요했고, 그 언어를 10일 만에 완성해야 했습니다. 그리고 그 언어는 Java와 유사하게 만들어야 했다고 합니다. 그 당시 Java는 베타 버전 상태였고, 정식 1.0 출시는 1996년이었습니다. 그러니까 베타 버전이었던 언어의, 제대로 정리되지 않았던 구현을 그대로 Javascript로 옮겼고 그게 아직까지 제대로 정리되지 않았다 뭐 그런 이야기입니다. Javascript가 참고했던 Java 구현은 1997년에 교체되었다고 하니까, Javascript는 이미 이 세상에 존재하지 않는 deprecated된 구현을 30년 넘게 유지하고 있었던 셈이죠!&lt;/p>
&lt;p>10일만에 언어를 만든다는 건 정말 대단한 일이죠. 그런데 그렇게 급조한 언어가 이렇게 인기를 끌게 될 줄은 아무도 몰랐던 거죠. 그러다보니 앤더스 헬스버그
(Anders Hejlsberg)가 &lt;code>Javascript that scales&lt;/code>를 모토로 Typescript를 만들게 되기도 했죠.&lt;/p>
&lt;h2 id="temporal로-다시-돌아와서">Temporal로 다시 돌아와서&lt;/h2>
&lt;p>Temporal은 이렇게 오래된 문제를 해결하기 위해 제안되었습니다. 뒤에서 자세히 알아보겠지만 우선 Temporal에서 뭘 지원해준다는 건지 알아볼까요?&lt;/p>
&lt;p>Date는 혼자서 모든 걸 다 하려고 했지만, Temporal은 그걸 여러 타입으로 쪼갰습니다. 그래서 좀.. 복잡해 보일 수도 있긴 합니다.&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;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>PlainDate&lt;/code>&lt;/td>
&lt;td>날짜만&lt;/td>
&lt;td>&lt;code>2026-07-31&lt;/code>&lt;/td>
&lt;td>생일, 기념일&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PlainTime&lt;/code>&lt;/td>
&lt;td>시각만&lt;/td>
&lt;td>&lt;code>09:00:00&lt;/code>&lt;/td>
&lt;td>매일 같은 시각에 울리는 알람&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PlainDateTime&lt;/code>&lt;/td>
&lt;td>날짜 + 시각&lt;/td>
&lt;td>&lt;code>2026-07-31T09:00:00&lt;/code>&lt;/td>
&lt;td>아직 어느 지역인지 안 정한 일정&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ZonedDateTime&lt;/code>&lt;/td>
&lt;td>날짜 + 시각 + 타임존&lt;/td>
&lt;td>&lt;code>2026-07-31T09:00:00-04:00[America/New_York]&lt;/code>&lt;/td>
&lt;td>실제로 잡힌 뉴욕 회의&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Instant&lt;/code>&lt;/td>
&lt;td>절대적인 한 순간&lt;/td>
&lt;td>&lt;code>2026-07-31T13:00:00Z&lt;/code>&lt;/td>
&lt;td>로그 시각, 서버끼리 시각 비교&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Duration&lt;/code>&lt;/td>
&lt;td>흘러간 양(기간)&lt;/td>
&lt;td>&lt;code>P8M14D&lt;/code> (8개월 14일)&lt;/td>
&lt;td>&amp;ldquo;3일 뒤&amp;rdquo;, 두 날짜의 간격&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>잘 보시면 앞에 붙은 이름에 따라 타입이 다루는 값이 다릅니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>Plain&lt;/code>으로 시작하면 타임존이 없습니다.&lt;/strong> 달력이나 시계에 적힌 글씨 그대로라고 보면 됩니다. &amp;ldquo;7월 31일 오전 9시&amp;quot;는 서울에서 읽든 뉴욕에서 읽든 그냥 7월 31일 오전 9시죠.&lt;/li>
&lt;li>&lt;strong>&lt;code>Zoned&lt;/code>가 붙으면 값 안에 타임존까지 들어 있습니다.&lt;/strong> 그래서 &amp;ldquo;뉴욕의 오전 9시&amp;quot;처럼 지구상의 딱 한 순간을 가리킬 수 있습니다.&lt;/li>
&lt;/ul>
&lt;p>&lt;code>Instant&lt;/code>는 Date와 가장 비슷한 타입입니다. Date도 속을 들여다보면 &amp;ldquo;1970년 1월 1일부터 몇 밀리초 지났나&amp;quot;라는 숫자 하나거든요. 이 밖에 연-월만 담는 &lt;code>PlainYearMonth&lt;/code>(&lt;code>2026-07&lt;/code>), 월-일만 담는 &lt;code>PlainMonthDay&lt;/code>(&lt;code>07-31&lt;/code>), 지금 시각을 가져오는 &lt;code>Temporal.Now&lt;/code>도 있습니다.&lt;/p>
&lt;p>지금 이 테이블은 일단 확인만 해두세요, 이제 부터 Date 객체의 구체적인 문제점과 Temporal의 해결책을 하나씩 확인해 볼거니까요!&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>date-issues.js&lt;/code> 파일을 통해 각각의 문제점 코드를 확인할 수 있고, &lt;code>date-issues-fixed.mjs&lt;/code> 파일을 통해 Temporal의 코드를 확인할 수 있습니다. node 24 버전까지는 Temporal이 지원되지 않으므로, &lt;code>npm i @js-temporal/polyfill&lt;/code> 명령으로 폴리필을 설치해 줘야 합니다.&lt;/p>&lt;/blockquote>
&lt;h2 id="1-타임존은-로컬과-utc-딱-두-개뿐입니다">1. 타임존은 &amp;lsquo;로컬&amp;rsquo;과 &amp;lsquo;UTC&amp;rsquo; 딱 두 개뿐입니다&lt;/h2>
&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="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">TZ&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;America/New_York&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">const&lt;/span> &lt;span class="nx">noon&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T12:00:00Z&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;로컬: getHours()&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">noon&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getHours&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 로컬: getHours() 8
&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;UTC: getUTCHours()&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">noon&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUTCHours&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// UTC: getUTCHours() 12
&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">seoulHour&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Intl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DateTimeFormat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;en-CA&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="nx">timeZone&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Asia/Seoul&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">hour&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;2-digit&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">hourCycle&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;h23&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="nx">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">noon&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 class="s2">&amp;#34;서울: &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">seoulHour&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서울: 21
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>noon은 UTC로 7월 31일 12시 입니다. 이 시점에서 다른 타임존을 적용해서, 예를 들면 UTC-&amp;gt;Asia/Seoul 로 변환해서 값을 가져와야 할 때가 있겠죠. 그런데 Date 객체는 딱 두가지만 지원합니다. UTC로 가져오던지, 현재 이 코드를 실행 중인 장비의 로컬 타임존으로 가져오던지.&lt;/p>
&lt;p>Intl.DateTimeFormat을 활용하면 다른 타임존의 값을 가져올 수는 있지만, 결과가 문자열이기 때문에 표시이외의 용도로 사용하려면 또 다른 변환 작업을 해야만 합니다.&lt;/p>
&lt;h3 id="temporal은-날짜에-타임존을-결합할-수-있습니다">Temporal은 날짜에 타임존을 결합할 수 있습니다&lt;/h3>
&lt;p>Temporal은 날짜 값에 타임존을 결합할 수 있습니다(ZonedDateTime). 그 결과 특정 시점에서 다른 타임존의 변환은 물론, 변환된 날짜를 기반으로 계산도 가능합니다.&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">// 특정 순간을 의미하는 Instant를 생성(Date는 항상 Instant 입니다)
&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">noon&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Instant&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T12:00:00Z&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">// 각 타임존으로 noon을 변환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">tz&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;America/New_York&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;UTC&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Asia/Seoul&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="kr">const&lt;/span> &lt;span class="nx">z&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">noon&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toZonedDateTimeISO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tz&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">tz&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">padEnd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&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">z&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hour&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">시 (offset &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">z&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">offset&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">// America/New_York 8시 (offset -04:00)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// UTC 12시 (offset +00:00)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// Asia/Seoul 21시 (offset +09:00)
&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="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">seoul&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">noon&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toZonedDateTimeISO&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Asia/Seoul&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;서울 기준 3시간 뒤:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">seoul&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">hours&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">hour&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="c1">// 서울 기준 3시간 뒤: 0시
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="2-파서parser를-신뢰할-수-없습니다">2. 파서(parser)를 신뢰할 수 없습니다&lt;/h2>
&lt;p>Date 객체는 문자열을 날짜로 변환할 수 있습니다. 그런데 문자열을 해석하는 파서의 동작이 명확하지 않습니다. 아주 간단하게 살펴볼까요?&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="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">TZ&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;America/New_York&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">input&lt;/span> &lt;span class="k">of&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;2026-07-31&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 날짜만 있는 ISO → UTC로 해석
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="s1">&amp;#39;2026-07-31T09:00&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 시각이 붙은 ISO → 로컬로 해석 (!)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="s1">&amp;#39;2026-07-31 09:00&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// T 대신 공백 → 명세 밖, 엔진 재량
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="s1">&amp;#39;07/31/2026&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// ISO가 아예 아님 → 명세 밖, 엔진 재량
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="s1">&amp;#39;2026-07-31T09:00Z&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 명시적 UTC → UTC로 해석
&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 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="nx">input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toISOString&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="c1">// 2026-07-31 --&amp;gt; 2026-07-31T00:00:00.000Z
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2026-07-31T09:00 --&amp;gt; 2026-07-31T13:00:00.000Z
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2026-07-31 09:00 --&amp;gt; 2026-07-31T13:00:00.000Z
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 07/31/2026 --&amp;gt; 2026-07-31T04:00:00.000Z
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2026-07-31T09:00Z --&amp;gt; 2026-07-31T09:00:00.000Z
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>날짜만 있는 경우 UTC로, 시간이 붙어 있는 경우 로컬로&amp;hellip; 이렇게 좀 복잡한 동작을 수행합니다.&lt;/p>
&lt;h3 id="temporal은-명확한-규칙을-요구합니다">Temporal은 명확한 규칙을 요구합니다&lt;/h3>
&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">// 날짜만 있는 문자열 → PlainDate. 타임존이 아예 개입하지 않습니다.
&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">`PlainDate.from(&amp;#34;2026-07-31&amp;#34;)`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// PlainDate.from(&amp;#34;2026-07-31&amp;#34;) 2026-07-31
&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">// 시각까지 있지만 타임존이 없는 문자열 → PlainDateTime. 역시 타임존은 포함되지 않습니다.
&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">`PlainDateTime.from(&amp;#34;...T09:00&amp;#34;)`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T09:00&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// PlainDateTime.from(&amp;#34;...T09:00&amp;#34;) 2026-07-31T09:00:00
&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>&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">`ZonedDateTime.from(&amp;#34;...[NY]&amp;#34;)`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ZonedDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T09:00[America/New_York]&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// ZonedDateTime.from(&amp;#34;...[NY]&amp;#34;) 2026-07-31T09:00:00-04:00[America/New_York]
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">desc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">action&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&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="s2">&amp;#34;타임존 없이 Instant 요구&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Instant&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T09:00&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="s2">&amp;#34;타임존 없이 Zoned 요구&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ZonedDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T09:00&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="s2">&amp;#34;존재하지 않는 2월 30일&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-02-30&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="s2">&amp;#34;ISO가 아닌 07/31/2026&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;07/31/2026&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="s2">&amp;#34;banana&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;banana&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="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="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="nx">desc&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">action&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toString&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">e&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="nx">desc&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">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">constructor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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 class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 타임존 없이 Instant 요구 → RangeError 즉시 예외
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 타임존 없이 Zoned 요구 → RangeError 즉시 예외
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 존재하지 않는 2월 30일 → RangeError 즉시 예외
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ISO가 아닌 07/31/2026 → RangeError 즉시 예외
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// banana → RangeError 즉시 예외
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="3-date는-변경-가능mutable합니다">3. Date는 변경 가능(mutable)합니다&lt;/h2>
&lt;p>Date 객체는 const로 선언해서 불변하도록(immutable) 만들 수 없습니다.&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="kd">function&lt;/span> &lt;span class="nx">startOfMonth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">date&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">date&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">setDate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&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="k">return&lt;/span> &lt;span class="nx">date&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">const&lt;/span> &lt;span class="nx">someDate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-07-31T12:00:00Z&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="s2">&amp;#34;함수 호출 전&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">someDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toISOString&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">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 함수 호출 전 2026-07-31
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">startOfMonth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">someDate&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">someDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toISOString&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">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 함수 호출 후 2026-07-01
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="temporal의-날짜는-불변immutable-합니다">Temporal의 날짜는 불변(immutable) 합니다&lt;/h3>
&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="kd">function&lt;/span> &lt;span class="nx">startOfMonth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">date&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">date&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kd">with&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">day&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&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="kr">const&lt;/span> &lt;span class="nx">someDate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31&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">someDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 함수 호출 전 2026-07-31
&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">firstDay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">startOfMonth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">someDate&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">someDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&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="c1">// 함수 호출 후 2026-07-31 ← 원본 그대로
&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">firstDay&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 반환값 2026-07-01
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="4-서머타임dst-동작을-예측할-수-없습니다">4. 서머타임(DST) 동작을 예측할 수 없습니다&lt;/h2>
&lt;p>서머타임은 시간을 1시간 앞당겨서 해가 떠있는 시간을 좀 더 많이 활용할 수 있도록 하는 제도입니다. 그래서 특정 시간부터 시간이 1시간 앞당겨지고, 서머타임이 끝날 때는 1시간 밀리게 됩니다.&lt;/p>
&lt;p>미국의 경우 3월의 둘째 일요일 오전 2시에 서머타임이 시작됩니다. 그러니까 2026년으로 치면 3월 8일 오전 1시 59분에서 2시가 되는 순간, 서머타임이 적용되면서 2시가 아닌 3시가 되는겁니다.&lt;/p>
&lt;p>그림으로 보자면, 우선 뉴욕의 2026년 3월 8일 오전 1시 59분입니다.&lt;/p>
&lt;p align="center">&lt;img src="./서머타임1.png">&lt;/p>
&lt;p>그리고 오전 2시가 되는 순간, 2시가 통째로 사라지고 3시가 됩니다!&lt;/p>
&lt;p align="center">&lt;img src="./서머타임2.png">&lt;/p>
&lt;p>UTC 시간을 잘 보시면, 시간은 그대로 흘러가지만 표시되는 시간만 2시 -&amp;gt; 3시로 변경되면서 2시가 통째로 사라지게 됩니다. 즉 3월 8일 오전 2시 30분은 존재할 수 없는 시간이며, 3월 8일은 24시간이 아닌 23시간짜리 하루입니다.&lt;/p>
&lt;p>Date 객체는 이 문제를 제대로 처리하지 못합니다.&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">// 3월의 두번째 일요일 2am -&amp;gt; 3am으로 변경(-05:00 EST-&amp;gt; -04:00 EDT). 따라서 뉴욕기준 2026년 3월 8일 2am은 존재하지 않음.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 따라서 2026년 3월 8일은 23시간
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 이후 11월 1일 2am에 다시 EDT-&amp;gt;EST로 변경되며, 1am을 두번 통과하므로 11월 1일은 25시간
&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">beforeSpringForward&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-03-08T00:00:00-05:00&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="s2">&amp;#34;3월 8일 자정&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">beforeSpringForward&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 3월 8일 자정 2026-03-08T05:00:00.000Z
&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">// 다음 날(+ 86,400,000 ms) -&amp;gt; 2026-03-09T05:00:00.000Z 이므로, EDT 기준으로는 3월 9일 오전 1시.
&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">nextDay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">beforeSpringForward&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getTime&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">86400000&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">nextDay&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toLocaleString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;en-US&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">timeZone&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;America/New_York&amp;#39;&lt;/span> &lt;span class="p">}));&lt;/span> &lt;span class="c1">// 다음날 자정이 아니다! 3/9/2026, 1:00:00 AM
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="temporal은-서머타임이-정확하게-적용됩니다">Temporal은 서머타임이 정확하게 적용됩니다!&lt;/h3>
&lt;p>days/weeks/months와 같이 달력의 날짜 기준 계산에는 DST를 적용하고, 시간 단위 계산은 Date와 동일하게 정확한 시간 단위로 계산합니다.&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="kr">const&lt;/span> &lt;span class="nx">midnight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ZonedDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-03-08T00:00[America/New_York]&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;3월 8일 자정&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">midnight&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&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({ days: 1 }) &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">midnight&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">days&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&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="c1">// .add({ days: 1 }) 2026-03-09T00:00:00-04:00[America/New_York] ← 진짜 다음 날 자정
&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; .add({ hours: 24 })&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">midnight&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">hours&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">24&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="s2">&amp;#34; ← 24시간 뒤 (오전 1시)&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// .add({ hours: 24 }) 2026-03-09T01:00:00-04:00[America/New_York] ← 24시간 뒤 (오전 1시)
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">d&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;2026-03-07&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;2026-03-08&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;2026-11-01&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="kr">const&lt;/span> &lt;span class="nx">z&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toZonedDateTime&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">timeZone&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;America/New_York&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="sb">` &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> → &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">z&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hoursInDay&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="c1">// 2026-03-07 → 24시간
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2026-03-08 → 23시간
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2026-11-01 → 25시간
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">option&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;compatible&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;reject&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">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">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-03-08T02:30&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="nx">toZonedDateTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;America/New_York&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">disambiguation&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">option&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">` 02:30 (&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">option&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">padEnd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">24&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toPlainTime&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toString&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">e&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">` 02:30 (&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">option&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">padEnd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">24&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">constructor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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 class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 02:30 (compatible) 03:30:00 ← 조용히 이동함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 02:30 (reject) RangeError ← 없는 시각이라고 알려줌
&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">// 11월 1일 01:30은 두 번 존재합니다. 어느 쪽인지 고를 수 있습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">option&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;earlier&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;later&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="kr">const&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-11-01T01:30&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="nx">toZonedDateTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;America/New_York&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">disambiguation&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">option&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">` 01:30 (&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">option&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">padEnd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">24&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toInstant&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toString&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="c1">// 01:30 (earlier) 2026-11-01T05:30:00Z
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 01:30 (later) 2026-11-01T06:30:00Z
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="5-계산-api가-조악합니다">5. 계산 API가 조악합니다&lt;/h2>
&lt;p>Date 객체는 제대로된 날짜 계산 API를 제공하지 않고, 메서드의 이름은 오래 전에 혼란스러웠던 형태를 그대로 가지고 있습니다.(심지어 month는 0부터 시작합니다!)&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">// 1월 31일 + 1달 -&amp;gt; 2월 31일 -&amp;gt; 3월 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">monthEnd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-01-31T12:00:00Z&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">monthEnd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">setUTCMonth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">monthEnd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUTCMonth&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 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월 31일 + 1달&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">monthEnd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toISOString&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">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 1월 31일 + 1달 2026-03-03
&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">// 두 날짜의 차이 구하기. 86,400,000 ms를 이용해서 나눠야 함. DST를 사용하는 경우 1년에 두 번 틀림
&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">rangeStart&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-01-01T00:00:00Z&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">const&lt;/span> &lt;span class="nx">rangeEnd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-09-15T00:00:00Z&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="s2">&amp;#34;두 날짜의 간격&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rangeEnd&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">rangeStart&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">86400000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 두 날짜의 간격 257
&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">// 날짜의 각 요소 구하기, getMonth는 0부터 시작하고, getDay는 요일을 리턴 함.
&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">july31&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T12:00:00Z&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;getMonth:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUTCMonth&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;getDay:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUTCDay&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;getFullYear:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUTCFullYear&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// getMonth: 6
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// getDay: 5
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// getFullYear: 2026
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="temporal은-오해-없이-정확한-api를-제공합니다">Temporal은 오해 없이 정확한 API를 제공합니다.&lt;/h3>
&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">// 1월 31일 + 1달 → 2월 28일로 잘라냅니다(clamp). 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">endMonth&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-01-31&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월 31일 + 1달 &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">endMonth&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">months&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 1월 31일 + 1달 2026-02-28
&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">` overflow:&amp;#34;reject&amp;#34; `&lt;/span>&lt;span class="p">,&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 class="k">return&lt;/span> &lt;span class="nx">endMonth&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">months&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 class="nx">overflow&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;reject&amp;#34;&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&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">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">constructor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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="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="c1">// overflow:&amp;#34;reject&amp;#34; RangeError (넘침을 알려달라고 할 수도 있음)
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ex: 매월 구독 결제일은 최초 결제일에서 누적된 달만큼 한 번에 계산해야 합니다.
&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; 1달+1달 &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">endMonth&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">months&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&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">months&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 1달+1달 2026-03-28
&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; 2달 한 번에 &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">endMonth&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">months&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 2달 한 번에 2026-03-31
&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">// 두 날짜의 간격. 86,400,000 같은 상수가 필요 없습니다.
&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">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-01-01&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">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-09-15&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">start&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">until&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">end&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">unit&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;day&amp;#34;&lt;/span> &lt;span class="p">}));&lt;/span> &lt;span class="c1">// 두 날짜의 간격(일) 257
&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; ISO 8601 표기 &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">start&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">until&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">end&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">largestUnit&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;month&amp;#34;&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// ISO 8601 표기 P8M14D
&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">// 필드 접근. 메서드가 아니라 속성이고, 월은 1부터 시작합니다.
&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">july31&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31&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;month (1부터) &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">month&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;day (일) &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">day&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;dayOfWeek (요일) &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dayOfWeek&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;(1=월요일)&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;year &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">year&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;daysInMonth &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">daysInMonth&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// month (1부터) 7
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// day (일) 31
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// dayOfWeek (요일) 5 (1=월요일)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// year 2026
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// daysInMonth 31
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="6-그레고리력-외의-달력을-지원하지-않습니다">6. 그레고리력 외의 달력을 지원하지 않습니다&lt;/h2>
&lt;p>그레고리력은 1582년 교황 그레고리우스 13세가 반포했으며, 윤년과 관련된 오차를 없애기 위해서 제정되었습니다. 지금 전 세계가 쓰고 있는 달력이 바로 그레고리력인 셈이죠. 하지만 가끔은 다른 달력을 써야 하는 경우도 있습니다.&lt;/p>
&lt;p>우리와 가장 가까운 나라인 일본을 보자면, 2026년 7월 31일에 연호를 붙여서 &amp;ldquo;레이와 8년 7월 31일&amp;rdquo; 이라고 표현합니다. 이런 그레고리력 이외의 달력을 Date 객체는 제대로 지원하지 않습니다.&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="kr">const&lt;/span> &lt;span class="nx">july31&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31T12:00:00Z&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">// 표기는 가능하지만, 결과는 문자열 값이므로 1과 같은 이유로 날짜 계산 등은 불가능.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">calendar&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;hebrew&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;islamic-umalqura&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;japanese&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;buddhist&amp;#39;&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="nx">calendar&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">Intl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DateTimeFormat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`en-u-ca-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">calendar&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">timeZone&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;UTC&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">dateStyle&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;long&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="nx">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">july31&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="c1">// hebrew 17 Av 5786
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// islamic-umalqura Safar 17, 1448 AH
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// japanese July 31, 8 Reiwa
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// buddhist July 31, 2569 BE
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="temporal은-제대로-지원합니다">Temporal은 제대로 지원합니다.&lt;/h3>
&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="kr">const&lt;/span> &lt;span class="nx">july31&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PlainDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2026-07-31&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">calendar&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;hebrew&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;islamic-umalqura&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;japanese&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;buddhist&amp;#39;&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">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">withCalendar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">calendar&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">year&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">era&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">era&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">eraYear&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">년 (연속 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">year&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">)`&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">year&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="nx">calendar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">year&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="c1">// hebrew 5786년
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// islamic-umalqura 1448년
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// japanese reiwa 8년 (연속 2026)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// buddhist 2569년
&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>&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">japaneseJuly31&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">july31&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">withCalendar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;japanese&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="nx">japaneseJuly31&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">days&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// 2026-08-02[u-ca=japanese]
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="temporal의-현재">Temporal의 현재&lt;/h2>
&lt;p>&lt;a href="https://github.com/tc39/proposal-temporal#status">Temporal 스펙은 현재 Stage 4 단계&lt;/a>이며, 곧 표준으로 병합될 예정입니다. 주요 브라우저들(Safari는 2026년 8월 기준으로 아직 개발 중)은 구현을 마쳤고, Node.js 역시 26 버전에서 Temporal을 정식 지원하고 있습니다.&lt;/p>
&lt;p>정식 지원되지 않는 환경에서 Temporal을 사용하려면, &lt;code>@js-temporal/polyfill&lt;/code> 를 통해 폴리필해서 사용하는 방법이 권장됩니다.&lt;/p>
&lt;h2 id="typescript의-지원">Typescript의 지원&lt;/h2>
&lt;p>Typescript 6 와 7에서 Temporal을 정식 지원합니다. 하지만, 지원되는 건 어디까지나 타입 체크이고 실제 실행은 js 런타임에서 진행되니까 실행 가능 여부는 js 런타임에 의해 결정됩니다.&lt;/p>
&lt;p>우선 typescript 7 을 설치하고 node의 버전을 체크합니다.&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 i -D typescript@7 @types/node
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; node -v
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">v26.6.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>VS Code를 사용하는 경우, &lt;code>Typescript 7&lt;/code> 확장을 설치해야 7 버전의 IDE 지원을 받을 수 있습니다.&lt;/p>&lt;/blockquote>
&lt;p>그리고 &lt;code>tsconfig.json&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;esnext&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;module&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nodenext&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;moduleResolution&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nodenext&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;lib&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;esnext&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;dom&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;types&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;node&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;strict&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;skipLibCheck&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;noEmit&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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;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="c1">// Temporal은 전역(global)이므로 import가 필요 없습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 타입은 tsconfig의 lib: [&amp;#34;esnext&amp;#34;] 에서 옵니다.
&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">meeting&lt;/span>: &lt;span class="kt">Temporal.ZonedDateTime&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Temporal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ZonedDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-03-08T09:00[America/New_York]&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="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="nx">meeting&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&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">meeting&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hoursInDay&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">시간`&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 23 — 서머타임 시작
&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;서울에서는:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">meeting&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">withTimeZone&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Asia/Seoul&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toPlainDateTime&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toString&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node temporal.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2026-03-08T09:00:00-04:00&lt;span class="o">[&lt;/span>America/New_York&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">이 날은 23시간
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">서울에서는: 2026-03-08T22:00:00
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="마치면서">마치면서&lt;/h2>
&lt;p>아주 가끔씩 생각날 때 마다 Typescript 업데이트 노트를 읽어봅니다. 이번에도 Go로 재작성된 7 버전도 보고 6 버전도 읽어봤습니다. 그러던 중에 Temporal이라는 게 있는데 이게 도대체 뭔지 모르겠더군요. 그래서 이리저리 찾아보면서 미래의 저를 위해서 정리를 해봤는데 생각보다 꽤나 복잡한 내용이었습니다. 아마도 가까운 시일 내에 금방 잊어버리겠죠&amp;hellip;하하하&amp;hellip;&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/tc39/proposal-temporal#status">tc39/proposal-temporal&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://maggiepint.com/2017/04/09/fixing-javascript-date-getting-started/">Fixing JavaScript Date – Getting Started&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.usnews.com/news/national-news/articles/2026-07-16/the-house-voted-for-permanent-daylight-saving-time-here-is-what-it-means-for-you">What Permanent Daylight Saving Time Could Mean for You&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/">Announcing TypeScript 7.0&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/">Announcing TypeScript 6.0&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>go를 알아보자(3) - 포인터</title><link>https://netscout.github.io/posts/go%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-3-%ED%8F%AC%EC%9D%B8%ED%84%B0/</link><pubDate>Mon, 25 May 2026 08:46:28 +0900</pubDate><guid>https://netscout.github.io/posts/go%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-3-%ED%8F%AC%EC%9D%B8%ED%84%B0/</guid><description>아주 오래전, 중학생이었던 제가 혼자서 C를 공부했던 시절이 있었습니다. 터보C 2.0 컴파일러와 안녕하세요 터보C 라는 책을 하나 들고 열심히 공부를 했습니다. 그때 당시엔 함수라는 개념을 도저히 이해할 수 없었습니다. 그리고 시간이 1-2년 지나서 그 책을 다시 …</description><content>&lt;p>아주 오래전, 중학생이었던 제가 혼자서 C를 공부했던 시절이 있었습니다. 터보C 2.0 컴파일러와 &lt;code>안녕하세요 터보C&lt;/code> 라는 책을 하나 들고 열심히 공부를 했습니다. 그때 당시엔 함수라는 개념을 도저히 이해할 수 없었습니다. 그리고 시간이 1-2년 지나서 그 책을 다시 집어 들고 공부를 했지만 이번엔 포인터, 구조체의 개념을 도저히 이해할 수 없었던 기억이 납니다. 그리고 포인터는 대학교 2학년이 되어서야 제대로(??? 그러니까 한 2차원 배열을 다룰 정도?) 이해할 수 있었습니다.&lt;/p>
&lt;p>그리고는 그 당시 베타 버전이 출시되던 닷넷(.NET)으로 넘어가면서 포인터와의 악연은 막을 내렸죠. 그런데 말입니다&amp;hellip;&lt;/p>
&lt;p>최근에 작업을 Go로 하려다보니&amp;hellip; 어쩔 수 없이 다시 포인터를 마주하게 됩니다. 20년도 더 지나서 포인터를 다시 보니 너무 헷갈려서 머리가 빙빙 돌더군요. 그래서 저를 위해서 포인터를 다시 한 번 정리해보자, 하는 하찮은 이유로 그냥 정리해봅니다. 미래의 저를 위해서!&lt;/p>
&lt;h2 id="포인터">포인터&amp;hellip;&lt;/h2>
&lt;p>포인터는 말 그대로 그냥 어느 한 지점을 가리키는 걸 말합니다. 포인터는 두 개의 특수기호 &lt;code>*&lt;/code>, &lt;code>&amp;amp;&lt;/code> 가 연관되어 있는데요, 우선 &lt;code>*&lt;/code>는 어디에서 사용되느냐에 따라 의미가 다릅니다.&lt;/p>
&lt;ul>
&lt;li>타입의 앞에 붙은 경우(ex: *bool) -&amp;gt; 메모리에서 해당 타입의 값이 위치하는 곳을 가리키는 포인터&lt;/li>
&lt;li>표현식에서 사용된 경우(ex: *isActive) -&amp;gt; 포인터가 가리키는 지점에 위치한 값, 참조 해제(포인터-&amp;gt;실제 값)&lt;/li>
&lt;/ul>
&lt;p>그리고 &lt;code>&amp;amp;&lt;/code>는 메모리의 주소를 가져오는 연산자로, 변수나 구조체등에 붙이게 되면 값이 위치한 메모리의 주소를 리턴합니다.&lt;/p>
&lt;p>다음 예시를 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="c1">// bool 타입의 변수 b에 true를 설정 -&amp;gt; 메모리의 어딘가 변수 b가 할당됨&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">p&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="c1">// 변수 b의 메모리 주소를 bool타입의 포인터 p에 할당&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 포인터 p가 가리키는 -&amp;gt; 변수 b의 메모리 주소가 출력됨(ex: 0xc0000140a0)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 포인터 p가 가리키는 -&amp;gt; 참조 해제 -&amp;gt; 변수 b의 값이 출력됨(true)&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 class="nx">p&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="c1">// 포인터 p가 가리키는 -&amp;gt; 변수 b의 값을 false로 변경&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 변수 b의 값이 출력됨(false)&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-fallback" data-lang="fallback">&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">변수 b 0xc000014090 │ true │ ◄──┬── *p (참조 해제 결과: true)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ p가 가진 주소를
&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">포인터 p 0xc0000140a0 │0xc000014090 │ ───┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┘ (&amp;amp;b로 얻은 주소를 값으로 가짐)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>&amp;amp;b&lt;/code> 는 변수 b가 위치한 메모리 주소(&lt;code>0xc000014090&lt;/code>)를 얻어옵니다.&lt;/li>
&lt;li>포인터 p는 그 주소를 자신의 값으로 가집니다.&lt;/li>
&lt;li>&lt;code>*p&lt;/code> 는 p가 가리키는 곳으로 따라가서 실제 값(&lt;code>true&lt;/code>)을 가져옵니다.&lt;/li>
&lt;/ul>
&lt;p>간단해 보이지만, 두고두고 사람을 헷갈리게 만드는 요소입니다. 제가 그렇습니다.&lt;/p>
&lt;h3 id="nil-포인터---아무것도-가리키지-않는-포인터">nil 포인터 - 아무것도 가리키지 않는 포인터&lt;/h3>
&lt;p>포인터에는 한 가지 특별한 값이 있는데요, 바로 &lt;code>nil&lt;/code> 입니다. 다른 언어의 &lt;code>null&lt;/code> 과 비슷한 개념이라고 생각하시면 됩니다. 포인터를 선언만 하고 아무 주소도 할당하지 않으면 zero value인 &lt;code>nil&lt;/code> 이 되죠.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">p&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="c1">// 선언만 함 -&amp;gt; p는 nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// &amp;lt;nil&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="k">if&lt;/span> &lt;span class="nx">p&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;p는 아무것도 가리키지 않습니다&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// panic!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// panic: runtime error: invalid memory address or nil pointer dereference&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>nil&lt;/code> 포인터를 역참조(&lt;code>*p&lt;/code>) 하려고 하면 위 코드의 마지막 줄처럼 런타임 패닉이 터집니다. 그래서 포인터를 다룰 때는 사용하기 전에 &lt;code>nil&lt;/code> 체크를 하는 습관이 중요합니다.&lt;/p>
&lt;p>도대체 이런 포인터를 왜 사용하게 된 걸까요? 메모리를 빠르고 효율적으로 사용하기 위해서 그렇습니다. 좀 더 알아볼까요?&lt;/p>
&lt;h2 id="스택stack-vs-힙heap">스택(stack) vs 힙(heap)&lt;/h2>
&lt;p>프로그램이 실행될 때는 스택, 힙 이렇게 2가지 유형의 메모리를 사용합니다.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>스택: 여러분이 알고계시는 바로 그 스택입니다. 가장 나중에 들어온 게 가장 먼저 나가는(LIFO) 방식으로 이 타입의 메모리는 관리가 매우 쉽습니다. 메모리에 할당할 때는 스택의 꼭대기를 나타내는 top 포인터를 증가/감소 시키기만 하면 됩니다. 함수가 호출될 때 마다 해당 함수에서 사용하는 모든 지역 변수들을 하나의 프레임으로 구성하여 스택의 꼭대기에 push하고, 함수의 실행이 종료되면 프레임 전체를 pop합니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>힙: 함수의 실행 주기와 관계없이 프로그램 전체에서 공유되어야 하거나, 스택에 할당되기에 너무 큰 메모리의 경우 힙에 할당됩니다. 스택에서는 함수 실행 주기에 따라 메모리에서 자동적으로 pop을 하는 방식으로 관리가 되지만, 힙은 함수 실행과 관계없이 그대로 남아있습니다. 그래서 주기적으로 가비지 컬렉터(GC)가 더 이상 사용되지 않는 항목을 찾아서 제거하고 메모리를 압축하는 등의 작업을 수행합니다.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>이 둘의 차이를 그림으로 보면 이렇습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"> STACK (LIFO) HEAP (자유 할당)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────────────┐ ◄─ top │ ┌────┐ ┌──────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ bar() frame │ (가장 │ │obj1│ │ obj2 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─────────────────┤ 나중에 │ └────┘ └──────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ foo() frame │ push됨) │ ┌────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─────────────────┤ │ │obj3│ ← GC가 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ main() frame │ │ └────┘ 정리 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─────────────────┘ └──────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 호출 순서: main() → foo() → bar()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> push/pop 으로 관리
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>아주 단순하게 말하면 값 타입(value type)은 스택에, 참조 타입(reference type)은 힙 메모리에 할당된다고 말할 수 있는데요.(물론 Go 컴파일러는 escape analysis를 수행해서 함수의 수명보다 길게 살아남을 요소들은 힙에 할당하기도 합니다.) 그러면 값 타입과 참조 타입에 대해서 알아보죠.&lt;/p>
&lt;h2 id="값-타입value-type-vs-참조-타입reference-type">값 타입(value type) vs 참조 타입(reference type)&lt;/h2>
&lt;p>일단 값 타입(value type)과 참조 타입(reference type)부터 정리하고 가겠습니다. 너무 기본이지만 어느새 까먹어 버리는 개념이기 때문이죠.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>값 타입(value type): int, bool 같은 기본(또는 원시 primitive)타입과 배열, 구조체 등의 복합 타입은 일반적으로 스택에 할당됩니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>참조 타입(reference type): 슬라이스, 맵, 채널, 인터페이스, 포인터 처럼 힙 메모리 어딘가에 위치하는 실제 데이터를 참조하는 타입입니다.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>그런데 사실 참조 타입은 값 타입이기도 합니다. 위에서 설명드린 것 처럼 &lt;code>값 타입이지만, 메모리 어딘가에 위치하는 데이터를 가리키는 포인터를 값으로 가지는&lt;/code> 타입을 편의상 참조 타입으로 부르는 거죠.&lt;/p>
&lt;p>Go에서는 기본적으로 모든 걸 값으로 전달(pass by value, 값을 복제해서 전달함) 하는데요, 여기에서 참조 타입은 약간 헷갈릴 수도 있게 동작합니다.&lt;/p>
&lt;p>다음 코드를 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="s">&amp;#34;fmt&amp;#34;&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">func&lt;/span> &lt;span class="nf">main&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">array1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">30&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">array1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// [10 20 30]&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">array2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">array1&lt;/span> &lt;span class="c1">// array2 에 array1 할당(pass by value, 배열이 복사됨)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">array2&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">100&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">array1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// [10 20 30] array1과 array2는 별개의 복사본이므로 변화 없음&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">slice1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// len:3, cap: 5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">slice1&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">slice1&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">20&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">slice1&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">30&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">slice1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// [10 20 30]&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">slice2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">slice1&lt;/span> &lt;span class="c1">// slice1를 slice2에 할당(pass by value)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">slice2&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">100&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">slice1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// [100 20 30] slice1과 slice2는 동일한 배열 데이터를 참조함&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>array2 := array1&lt;/code> 에서 배열의 모든 요소들이 새로운 배열로 복제되어 array2에 할당됩니다. 그래서 array2의 데이터를 변경해도 array1은 전혀 영향을 받지 않죠.&lt;/p>
&lt;p>참조 타입인 슬라이스의 경우 &lt;code>slice2 := slice1&lt;/code> 에서 값 타입인 슬라이스의 헤더(실제 배열 값을 가리키는 포인터, 슬라이스 요소 개수 len, 배열 길이 cap)만 복제되어 slice2로 전달됩니다. 두 슬라이스 모두 동일한 포인터를 가지고 있기 때문에 slice2의 요소를 변경하면 slice1도 변경된 값을 참조하게 됩니다.&lt;/p>
&lt;p>이 차이를 그림으로 보면 한눈에 들어옵니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">▶ 배열 (값 타입) - array2 := array1 후, array2[0] = 100 까지 실행한 상태
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> array1 array2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──┬──┬──┐ 복사 ┌───┬──┬──┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │10│20│30│ ────────► │100│20│30│ ← 완전히 별개의 메모리
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──┴──┴──┘ └───┴──┴──┘ (array2만 100으로 변경됨)
&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">▶ 슬라이스 (참조 타입) - slice2 := slice1 후, slice2[0] = 100 까지 실행한 상태
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> slice1 헤더 slice2 헤더
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────┬───┬───┐ ┌──────┬───┬───┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ptr │ 3 │ 5 │ │ ptr │ 3 │ 5 │ ← 헤더만 복사
&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> underlying array (힙)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌───┬──┬──┬─┬─┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │100│20│30│ │ │ ← 동일한 배열을 공유
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └───┴──┴──┴─┴─┘ (slice2의 변경이 slice1에도 보임)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> len=3 cap=5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>배열은 통째로 복제되어 두 개의 독립된 메모리가 되지만, 슬라이스는 헤더만 복제되고 실제 데이터(underlying array)는 하나를 공유합니다. 그래서 &lt;code>slice2[0] = 100&lt;/code> 으로 변경한 값이 &lt;code>slice1&lt;/code> 에서도 보이는 거죠.&lt;/p>
&lt;h2 id="구조체도-헷갈리게">구조체도 헷갈리게&amp;hellip;&lt;/h2>
&lt;p>Go는 OOP를 지원하긴 하지만, 클래스를 사용하지 않습니다. 이 부분이 저도 처음엔 좀 의아하긴 했는데요, Go는 값 타입인 구조체를 활용해서 이 문제를 해결합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="s">&amp;#34;fmt&amp;#34;&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">type&lt;/span> &lt;span class="nx">Counter&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">count&lt;/span> &lt;span class="kt">int&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">func&lt;/span> &lt;span class="nf">main&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">counter1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">count&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">counter1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// {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="nx">counter2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">counter1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">counter2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">10&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">counter1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// {1}&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>구조체는 값 타입이기 때문에 counter1을 counter2에 대입할 때 count 값이 새로운 복제본으로 복사되어 전달됩니다.&lt;/p>
&lt;h3 id="구조체의-메서드-정의">구조체의 메서드 정의&lt;/h3>
&lt;p>클래스는 지원하는 언어는 클래스의 내부에 메서드(멤버 메서드)를 정의합니다. 하지만 Go는 클래스 대신 구조체를 사용하죠. 그래서 구조체 외부에 메서드를 정의하고 그 메서드의 수신자가 누구인지를 명시하도록 되어 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="s">&amp;#34;fmt&amp;#34;&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">type&lt;/span> &lt;span class="nx">Counter&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">count&lt;/span> &lt;span class="kt">int&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">// 값 수신자(value receiver) - 복사본에 대해 수행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">IncrementOnCopy&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">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="o">++&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">// 포인터 수신자(pointer receiver) - 원본에 대해 수행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Increment&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">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="c1">// 원래는 (*c).count++ 이어야 하지만, Go는 자동으로 참조를 해제해준다&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">func&lt;/span> &lt;span class="nf">main&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">counter&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">count&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="c1">// 위 라인은 아래 두 줄과 동일&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// tmp := Counter{count: 0}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// counter := &amp;amp;tmp&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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="k">for&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="p">&amp;lt;&lt;/span> &lt;span class="mi">5&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IncrementOnCopy&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%d번 증가: %d \n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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="k">for&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="p">&amp;lt;&lt;/span> &lt;span class="mi">5&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Increment&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%d번 증가: %d \n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&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;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">❯ go run .
&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번 증가: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2번 증가: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3번 증가: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4번 증가: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5번 증가: &lt;span class="m">0&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번 증가: &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2번 증가: &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3번 증가: &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4번 증가: &lt;span class="m">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5번 증가: &lt;span class="m">5&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Go에서는 데이터를 전달할 때 항상 값으로 전달(pass by value)을 사용합니다. 따라서 값 수신자에 전달되는 구조체 데이터 역시 복제되어 전달됩니다. 아무리 &lt;code>IncrementOnCopy()&lt;/code> 를 호출해도 복사본에 대해 수행되기 때문에 카운트는 제자리에 멈추게 됩니다.&lt;/p>
&lt;p>포인터 수신자는 구조체의 참조를 전달받아 연산을 수행하기 때문에 원본의 값을 변경하게 되고 카운트가 계속 증가하게 됩니다.&lt;/p>
&lt;p>이 차이를 그림으로 정리하면 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="err">▶&lt;/span> &lt;span class="err">값&lt;/span> &lt;span class="err">수신자&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="n">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">IncrementOnCopy&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="p">[&lt;/span>&lt;span class="err">호출자의&lt;/span> &lt;span class="err">메모리&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="err">함수의&lt;/span> &lt;span class="err">스택&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="err">값&lt;/span> &lt;span class="err">복사&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Counter의&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 class="err">───────────►&lt;/span> &lt;span class="err">┌────────┐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└────────┘&lt;/span> &lt;span class="err">└───┬────┘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">▲&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="o">++&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span> &lt;span class="err">▼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span> &lt;span class="err">┌────────┐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span> &lt;span class="err">──&lt;/span> &lt;span class="err">함수가&lt;/span> &lt;span class="err">끝나도&lt;/span> &lt;span class="err">──&lt;/span> &lt;span class="err">│&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="err">│&lt;/span> &lt;span class="err">←&lt;/span> &lt;span class="err">복사본만&lt;/span> &lt;span class="err">증가&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└──&lt;/span> &lt;span class="err">여전히&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="err">└────────┘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">함수&lt;/span> &lt;span class="err">종료&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="err">폐기&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="err">▶&lt;/span> &lt;span class="err">포인터&lt;/span> &lt;span class="err">수신자&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">Increment&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="p">[&lt;/span>&lt;span class="err">호출자의&lt;/span> &lt;span class="err">메모리&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="err">함수의&lt;/span> &lt;span class="err">스택&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="err">주소&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="err">전달&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">Counter&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 class="err">─────────────────►&lt;/span> &lt;span class="err">┌────────────┐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span>&lt;span class="mh">0xc000014090&lt;/span>&lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span> &lt;span class="err">↓&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">◄──&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="err">으로&lt;/span> &lt;span class="err">─────&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="err">원본을&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">│&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="err">│&lt;/span> &lt;span class="err">원본을&lt;/span> &lt;span class="err">직접&lt;/span> &lt;span class="err">수정&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">가리킴&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└────────┘&lt;/span> &lt;span class="err">└────────────┘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">원본이&lt;/span> &lt;span class="err">변경됨&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>IncrementOnCopy()&lt;/code> 는 호출할 때마다 매번 새로운 복사본을 만들어 그 위에서 1을 더한 뒤, 함수가 끝나면 복사본은 사라집니다. 그래서 원본인 &lt;code>counter.count&lt;/code> 는 계속 0인 거죠. 반면 &lt;code>Increment()&lt;/code> 는 &lt;code>counter&lt;/code> 의 주소만 받기 때문에 함수 안에서 &lt;code>c.count++&lt;/code> 를 하면 원본의 메모리가 직접 바뀝니다.&lt;/p>
&lt;h3 id="new-와-counter---둘-다-구조체-포인터를-만들어요">&lt;code>new()&lt;/code> 와 &lt;code>&amp;amp;Counter{}&lt;/code> - 둘 다 구조체 포인터를 만들어요&lt;/h3>
&lt;p>위 예시에서 &lt;code>counter := &amp;amp;Counter{count: 0}&lt;/code> 으로 구조체 포인터를 만들었는데요, Go에는 내장 함수 &lt;code>new()&lt;/code> 라는 또 다른 방법도 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">counter1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">{}&lt;/span> &lt;span class="c1">// 빈 구조체 포인터 (모든 필드가 zero value)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">counter2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Counter&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">counter3&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="c1">// 초기값을 지정하고 싶다면 이쪽&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>new(T)&lt;/code> 는 타입 T의 zero value를 가지는 새 메모리를 할당하고 그 포인터를 돌려주는 내장 함수입니다. 초기화할 필드가 없다면 &lt;code>new(Counter)&lt;/code> 가 좀 더 간결하긴 한데요, 실제로는 초기값을 함께 지정하는 경우가 대부분이라서 &lt;code>&amp;amp;Counter{...}&lt;/code> 쪽이 훨씬 자주 사용됩니다.&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>Tucker의 Go 언어 프로그래밍&lt;/li>
&lt;li>Claude&lt;/li>
&lt;/ul></content></item><item><title>npm 공급망 공격 방어 가이드: min-release-age 설정하기</title><link>https://netscout.github.io/posts/npm-%EA%B3%B5%EA%B8%89%EB%A7%9D-%EA%B3%B5%EA%B2%A9-%EB%B0%A9%EC%96%B4-%EA%B0%80%EC%9D%B4%EB%93%9C-min-release-age-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/</link><pubDate>Mon, 20 Apr 2026 21:17:43 +0900</pubDate><guid>https://netscout.github.io/posts/npm-%EA%B3%B5%EA%B8%89%EB%A7%9D-%EA%B3%B5%EA%B2%A9-%EB%B0%A9%EC%96%B4-%EA%B0%80%EC%9D%B4%EB%93%9C-min-release-age-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/</guid><description>2026년 3월 31일, 주당 약 1억 회 다운로드되는 인기 HTTP 클라이언트 라이브러리 axios 의 메인테이너 npm 계정이 탈취되어, 악성 버전 &lt;a href="mailto:axios@1.14.1">axios@1.14.1&lt;/a>과 &lt;a href="mailto:axios@0.30.4">axios@0.30.4&lt;/a>가 npm 레지스트리에 공개되었습니다.</description><content>&lt;blockquote>
&lt;p>이 문서는 CLAUDE를 통해 작성되었습니다.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>목적&lt;/strong>: 이 가이드는 npm 생태계에 대한 &lt;strong>공급망 공격(Supply Chain Attack)&lt;/strong> 으로부터 개발자와 조직을 보호하기 위한 실무 설정 가이드입니다. macOS, Windows 모두에서 동일하게 적용할 수 있도록 작성되었습니다.&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="배경-왜-이-설정이-필요한가--axios-사태-2026년-3월-31일">배경: 왜 이 설정이 필요한가 — axios 사태 (2026년 3월 31일)&lt;/h2>
&lt;p>2026년 3월 31일, 주당 약 &lt;strong>1억 회&lt;/strong> 다운로드되는 인기 HTTP 클라이언트 라이브러리 &lt;strong>axios&lt;/strong> 의 메인테이너 npm 계정이 탈취되어, 악성 버전 &lt;code>axios@1.14.1&lt;/code>과 &lt;code>axios@0.30.4&lt;/code>가 npm 레지스트리에 공개되었습니다.&lt;/p>
&lt;p>&lt;strong>공격 요약&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>공격자는 사회공학 기법(위장된 회사, 가짜 Slack/Teams 미팅)으로 메인테이너 계정을 탈취했습니다.&lt;/li>
&lt;li>악성 버전은 &lt;code>plain-crypto-js@4.2.1&lt;/code>이라는 별도의 악성 패키지를 의존성으로 주입했습니다.&lt;/li>
&lt;li>해당 패키지의 &lt;code>postinstall&lt;/code> 훅은 &lt;code>npm install&lt;/code> 실행 즉시 macOS / Windows / Linux용 RAT(원격 제어 트로이목마)을 자동으로 다운로드하여 실행했습니다.&lt;/li>
&lt;li>코드를 한 줄도 import 하지 않고, &lt;strong>단지 설치만 해도&lt;/strong> 감염되었습니다.&lt;/li>
&lt;li>악성 버전은 공개 후 &lt;strong>약 3시간&lt;/strong> 뒤에 npm 레지스트리에서 제거되었습니다.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>핵심 교훈&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>만약 새 패키지 버전의 설치를 &lt;strong>단 하루만&lt;/strong> 지연시켰다면, 대부분의 개발자는 이 공격에서 안전했을 것입니다.&lt;/p>&lt;/blockquote>
&lt;p>대부분의 악성 npm 패키지는 보안 연구자들에 의해 수 시간 내에 탐지·제거됩니다. 따라서 갓 출시된 버전의 설치를 일정 기간 지연시키는 &amp;ldquo;쿨다운(cooldown)&amp;rdquo; 전략이 가장 단순하고 강력한 공급망 공격 방어 수단 중 하나입니다.&lt;/p>
&lt;p>npm CLI는 &lt;strong>11.10.0 버전부터&lt;/strong> 이 기능을 &lt;code>min-release-age&lt;/code> 설정으로 제공합니다.&lt;/p>
&lt;hr>
&lt;h2 id="전체-절차-개요">전체 절차 개요&lt;/h2>
&lt;ol>
&lt;li>Node.js 및 npm 버전 확인&lt;/li>
&lt;li>Node.js를 24.x로 업그레이드 (필요 시)&lt;/li>
&lt;li>npm을 11.10.0 이상으로 업그레이드&lt;/li>
&lt;li>&lt;code>min-release-age&lt;/code> 설정&lt;/li>
&lt;li>설정이 실제로 적용되는지 검증&lt;/li>
&lt;li>추가 보안 설정 (선택)&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="1단계-현재-버전-확인">1단계: 현재 버전 확인&lt;/h2>
&lt;p>터미널(macOS: Terminal / Windows: PowerShell 또는 CMD)을 열고 다음을 실행합니다.&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">node -v
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>요구 버전&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Node.js: &lt;strong>v24.x 이상&lt;/strong> (npm 11은 공식적으로 Node 24와 함께 제공됨)&lt;/li>
&lt;li>npm: &lt;strong>11.10.0 이상&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>두 버전 모두 충족한다면 &lt;a href="https://netscout.github.io/posts/npm-%EA%B3%B5%EA%B8%89%EB%A7%9D-%EA%B3%B5%EA%B2%A9-%EB%B0%A9%EC%96%B4-%EA%B0%80%EC%9D%B4%EB%93%9C-min-release-age-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/#3%eb%8b%a8%ea%b3%84-min-release-age-%ec%84%a4%ec%a0%95">3단계&lt;/a>로 건너뛸 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="2단계-nodejs-설치--업그레이드">2단계: Node.js 설치 / 업그레이드&lt;/h2>
&lt;p>Node 버전을 유연하게 관리하기 위해 &lt;strong>nvm(Node Version Manager)&lt;/strong> 사용을 권장합니다.&lt;/p>
&lt;h3 id="macos--linux--nvm">macOS / Linux — nvm&lt;/h3>
&lt;p>&lt;strong>nvm 설치&lt;/strong> (이미 설치되어 있다면 생략):&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">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh &lt;span class="p">|&lt;/span> bash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>설치 후 &lt;strong>새 터미널을 엽니다&lt;/strong>. 그 다음:&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"># 최신 Node 24.x 설치 및 기본값으로 지정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvm install &lt;span class="m">24&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvm &lt;span class="nb">alias&lt;/span> default &lt;span class="m">24&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>이미 Node 24.x가 설치된 경우, 최신 패치 버전으로 업데이트&lt;/strong>:&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">nvm install &lt;span class="m">24&lt;/span> --reinstall-packages-from&lt;span class="o">=&lt;/span>current
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nvm &lt;span class="nb">alias&lt;/span> default &lt;span class="m">24&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>--reinstall-packages-from=current&lt;/code>는 기존에 전역 설치한 패키지들을 새 버전으로 자동 이전합니다.&lt;/p>
&lt;h3 id="windows--nvm-windows">Windows — nvm-windows&lt;/h3>
&lt;p>Windows는 별도의 프로젝트인 &lt;strong>nvm-windows&lt;/strong>를 사용합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://github.com/coreybutler/nvm-windows/releases">https://github.com/coreybutler/nvm-windows/releases&lt;/a> 에서 &lt;code>nvm-setup.exe&lt;/code> 다운로드 후 설치&lt;/li>
&lt;li>&lt;strong>관리자 권한으로&lt;/strong> PowerShell 또는 CMD 실행&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 최신 Node 24.x 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nvm&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="mf">24&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="c"># 설치된 24.x 중 하나를 활성화&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nvm&lt;/span> &lt;span class="n">use&lt;/span> &lt;span class="mf">24&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Windows에서 최신 패치 버전으로 업데이트&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">nvm&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="mf">24&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nvm&lt;/span> &lt;span class="n">use&lt;/span> &lt;span class="mf">24&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># (선택) 이전 버전 제거&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nvm&lt;/span> &lt;span class="n">uninstall&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">이전버전&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="설치-확인">설치 확인&lt;/h3>
&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">node -v &lt;span class="c1"># v24.x.x 가 출력되어야 함&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="3단계-npm-11100-이상으로-업그레이드">3단계: npm 11.10.0 이상으로 업그레이드&lt;/h2>
&lt;p>Node 24를 설치해도 번들된 npm이 11.10.0보다 낮을 수 있습니다.&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 -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>11.10.0 미만이라면 업그레이드:&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 install -g npm@latest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>nvm 사용자 참고&lt;/strong>: 이 명령은 &lt;code>-g&lt;/code>로 설치하지만, nvm이 각 Node 버전마다 독립된 전역 디렉터리를 두기 때문에 &lt;strong>sudo / 관리자 권한 없이&lt;/strong> 실행할 수 있습니다. 단, 이렇게 업그레이드한 npm은 &lt;strong>현재 활성화된 Node 버전에만&lt;/strong> 적용됩니다. 다른 Node 버전으로 전환(&lt;code>nvm use&lt;/code>)하면 해당 버전에 번들된 npm으로 되돌아갑니다.&lt;/p>&lt;/blockquote>
&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">npm -v &lt;span class="c1"># 11.10.0 이상이어야 함&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="4단계-min-release-age-설정">4단계: &lt;code>min-release-age&lt;/code> 설정&lt;/h2>
&lt;p>사용자 수준 &lt;code>.npmrc&lt;/code> 파일에 설정을 추가합니다. 이 파일의 위치는 OS별로 다릅니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>macOS / Linux&lt;/strong>: &lt;code>~/.npmrc&lt;/code>&lt;/li>
&lt;li>&lt;strong>Windows&lt;/strong>: &lt;code>C:\Users\&amp;lt;사용자명&amp;gt;\.npmrc&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>OS 공통 명령&lt;/strong>으로 설정:&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 config &lt;span class="nb">set&lt;/span> min-release-age &lt;span class="m">7&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>7&lt;/code>은 &lt;strong>일(day)&lt;/strong> 단위입니다 (pnpm은 분 단위이니 혼동 주의). 권장값:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>값&lt;/th>
&lt;th>설명&lt;/th>
&lt;th>적합한 환경&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>1&lt;/code>&lt;/td>
&lt;td>최소 방어. 대부분의 악성 패키지는 수 시간 내 제거됨&lt;/td>
&lt;td>빠른 업데이트가 필요한 개인 프로젝트&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>3&lt;/code>&lt;/td>
&lt;td>균형 잡힌 선택. Renovate Bot의 권장 기본값&lt;/td>
&lt;td>일반 팀/프로젝트&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>7&lt;/code>&lt;/td>
&lt;td>견고한 방어. 일반적으로 추천&lt;/td>
&lt;td>대부분의 팀에 적합&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>14&lt;/code>+&lt;/td>
&lt;td>매우 보수적&lt;/td>
&lt;td>엔터프라이즈 / 금융권&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="파일-내용-확인">파일 내용 확인&lt;/h3>
&lt;p>&lt;strong>macOS / Linux:&lt;/strong>&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">cat ~/.npmrc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Windows (PowerShell):&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-Content&lt;/span> &lt;span class="nv">$HOME&lt;/span>&lt;span class="p">\.&lt;/span>&lt;span class="py">npmrc&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>출력에 &lt;code>min-release-age=7&lt;/code>이 포함되어 있으면 정상입니다.&lt;/p>
&lt;hr>
&lt;h2 id="5단계-설정이-실제로-작동하는지-검증">5단계: 설정이 실제로 작동하는지 검증&lt;/h2>
&lt;blockquote>
&lt;p>⚠️ &lt;strong>알려진 버그 주의&lt;/strong>: 현재 npm 버전(~11.12.x)에서는 &lt;code>npm config get min-release-age&lt;/code>가 &lt;code>null&lt;/code>을 반환하는 표시 버그(&lt;a href="https://github.com/npm/cli/issues/9199">npm/cli#9199&lt;/a>)가 있습니다. &lt;strong>실제 기능은 정상 동작&lt;/strong>합니다. 따라서 &lt;code>config get&lt;/code> 대신 아래의 &lt;strong>실제 설치 시험&lt;/strong>으로 검증하세요.&lt;/p>&lt;/blockquote>
&lt;h3 id="최근-7일-이내에-배포된-버전을-찾아-설치-시도">최근 7일 이내에 배포된 버전을 찾아 설치 시도&lt;/h3>
&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"># 최근에 자주 배포되는 패키지의 버전 히스토리 확인&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm view @types/node &lt;span class="nb">time&lt;/span> --json
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>출력에서 &lt;strong>최근 7일 이내 날짜&lt;/strong>의 버전을 하나 고릅니다. 그 다음 임시 폴더에서:&lt;/p>
&lt;p>&lt;strong>macOS / Linux:&lt;/strong>&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">mkdir -p /tmp/npm-test &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> /tmp/npm-test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm install @types/node@&amp;lt;최근버전&amp;gt; --dry-run
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Windows (PowerShell):&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mkdir&lt;/span> &lt;span class="nv">$env:TEMP&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">npm-test&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nb">cd &lt;/span>&lt;span class="nv">$env:TEMP&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">npm-test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">npm&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="nv">@types&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="p">@&amp;lt;&lt;/span>&lt;span class="n">최근버전&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-dry-run&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="성공-시-출력-예시">성공 시 출력 예시&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">npm error code ETARGET
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm error notarget No matching version found for @types/node@X.X.X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> with a date before 2026/M/D, HH:MM:SS.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&amp;ldquo;&lt;code>with a date before ...&lt;/code>&amp;rdquo; 의 날짜가 &lt;strong>오늘로부터 설정한 일수 이전&lt;/strong>이면 정상 동작 중입니다. 설정된 기간보다 최근에 배포된 버전은 설치가 거부됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="긴급-상황-대응-일회성으로-제한-우회">긴급 상황 대응: 일회성으로 제한 우회&lt;/h2>
&lt;p>긴급 CVE 패치가 출시되어 최신 버전을 즉시 설치해야 하는 경우, 한 번만 제한을 우회할 수 있습니다:&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 install &amp;lt;패키지명&amp;gt; --min-release-age&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>전역 &lt;code>.npmrc&lt;/code> 설정은 유지되며, 이 명령에서만 제한이 무시됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="주의사항-및-한계">주의사항 및 한계&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>&lt;code>npm ci&lt;/code>는 영향을 받지 않습니다.&lt;/strong>
&lt;code>min-release-age&lt;/code>는 &lt;code>npm install&lt;/code>의 의존성 해결 과정에 적용됩니다. &lt;code>package-lock.json&lt;/code>이 이미 있고 &lt;code>npm ci&lt;/code>로 설치하는 경우(주로 CI 환경), 락파일에 고정된 버전을 설치하므로 이 설정과 무관합니다. → &lt;strong>개발자의 로컬 머신에서 락파일에 악성 버전이 고정되는 것을 막는 것이 핵심 방어선&lt;/strong>입니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>~&lt;/code> 버전 범위 관련 버그.&lt;/strong>
의존성에 &lt;code>~1.2.3&lt;/code>처럼 &lt;code>~&lt;/code> 범위 지정자가 포함된 경우 min-release-age가 에러를 일으킬 수 있습니다 (&lt;a href="https://github.com/npm/cli/issues/9005">npm/cli#9005&lt;/a>). 수정되기 전까지는 &lt;code>^&lt;/code>를 사용하는 것을 권장합니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>숫자만 허용.&lt;/strong>
&lt;code>7d&lt;/code>, &lt;code>7days&lt;/code> 같은 문자 접미사 형식은 지원하지 않습니다. 오직 &lt;strong>정수(일수)&lt;/strong> 만 유효한 값입니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>예외 목록 미지원.&lt;/strong>
pnpm과 달리 현재 npm은 &amp;ldquo;이 패키지만 예외&amp;rdquo; 같은 화이트리스트 기능이 없습니다(&lt;a href="https://github.com/npm/cli/issues/8979">npm/cli#8979&lt;/a>). 긴급한 경우 위의 &lt;code>--min-release-age=0&lt;/code> 플래그로 우회하세요.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="추가-보안-권장-사항-axios-공격에서-배운-교훈">추가 보안 권장 사항 (axios 공격에서 배운 교훈)&lt;/h2>
&lt;p>&lt;code>min-release-age&lt;/code>는 공급망 공격 방어의 &lt;strong>첫 번째 방어선&lt;/strong>일 뿐입니다. 다음 조치들을 함께 적용하면 훨씬 안전합니다.&lt;/p>
&lt;h3 id="1-package-lockjson을-반드시-git에-커밋">(1) &lt;code>package-lock.json&lt;/code>을 반드시 Git에 커밋&lt;/h3>
&lt;p>락파일이 있어야 &lt;code>npm ci&lt;/code>로 재현 가능한 설치가 가능하고, 의도치 않은 버전 업그레이드를 막을 수 있습니다.&lt;/p>
&lt;h3 id="2-ci에서는-npm-install-대신-npm-ci-사용">(2) CI에서는 &lt;code>npm install&lt;/code> 대신 &lt;code>npm ci&lt;/code> 사용&lt;/h3>
&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 ci
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>락파일에 고정된 버전만 설치하므로, 악성 패키지가 떠도 CI가 자동으로 업그레이드하지 않습니다.&lt;/p>
&lt;h3 id="3-프로덕션-빌드에서-lifecycle-script-비활성화-고려">(3) 프로덕션 빌드에서 lifecycle script 비활성화 고려&lt;/h3>
&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 config &lt;span class="nb">set&lt;/span> ignore-scripts &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>postinstall&lt;/code> 같은 훅이 실행되지 않아 axios 사례와 같은 공격 대부분을 원천 차단합니다. 단, 일부 정상 패키지(네이티브 바인딩 포함)는 빌드에 실패할 수 있으므로 &lt;strong>CI에서 먼저 검증&lt;/strong> 후 적용하세요.&lt;/p>
&lt;h3 id="4-axios-감염-여부-직접-점검">(4) axios 감염 여부 직접 점검&lt;/h3>
&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">npm ls axios
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm ls plain-crypto-js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>다음 중 하나라도 발견되면 &lt;strong>즉시 감염으로 간주&lt;/strong>하고 조치하세요:&lt;/p>
&lt;ul>
&lt;li>&lt;code>axios@1.14.1&lt;/code>&lt;/li>
&lt;li>&lt;code>axios@0.30.4&lt;/code>&lt;/li>
&lt;li>&lt;code>plain-crypto-js&lt;/code>(모든 버전)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>조치 절차&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>해당 머신의 &lt;strong>모든 자격증명 즉시 회전(rotate)&lt;/strong>:
API 키, SSH 키, GitHub/npm 토큰, 클라우드 크리덴셜 등&lt;/li>
&lt;li>캐시 삭제:
&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 cache clean --force
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>안전한 버전으로 다운그레이드 (&lt;code>axios@1.14.0&lt;/code> 또는 &lt;code>axios@0.30.3&lt;/code>)&lt;/li>
&lt;li>CI/CD 로그에서 2026년 3월 31일 00:21 UTC ~ 03:15 UTC 사이 &lt;code>npm install&lt;/code> 실행 이력 검토&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age">npm 공식 문서: min-release-age&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/axios/axios/issues/10636">axios 공격 Post Mortem (GitHub Issue #10636)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/">Microsoft Security: Mitigating the Axios npm supply chain compromise&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://socket.dev/blog/npm-introduces-minimumreleaseage-and-bulk-oidc-configuration">Socket Blog: npm Introduces minimumReleaseAge&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/nvm-sh/nvm">nvm (macOS / Linux)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/coreybutler/nvm-windows">nvm-windows&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>최종 업데이트: 2026년 4월 16일&lt;/em>&lt;/p></content></item><item><title>go를 알아보자(2) - gRPC</title><link>https://netscout.github.io/posts/go%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-2-grpc/</link><pubDate>Sun, 22 Mar 2026 10:34:52 +0900</pubDate><guid>https://netscout.github.io/posts/go%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-2-grpc/</guid><description>paxos 기반의 분산 key-value 스토어 를 만들면서 가장 처음에 &amp;lsquo;뭐지?&amp;rsquo; 싶었던 건 gRPC였습니다. gRPC자체를 몰랐다기 보다는 뭔가 코드가 완전 암호같아 보였기 때문이죠. 그래서 gRPC만 우선 따로 간단하게 이해해보자 하고 간단한 앱을 만들어 보고 …</description><content>&lt;p>&lt;a href="https://github.com/netscout/dkv">paxos 기반의 분산 key-value 스토어&lt;/a> 를 만들면서 가장 처음에 &amp;lsquo;뭐지?&amp;rsquo; 싶었던 건 gRPC였습니다. gRPC자체를 몰랐다기 보다는 뭔가 코드가 완전 암호같아 보였기 때문이죠. 그래서 gRPC만 우선 따로 간단하게 이해해보자 하고 간단한 앱을 만들어 보고 한 줄씩 따라가면서 이해해보려고 노력했습니다.&lt;/p>
&lt;h2 id="grpc란">gRPC란?&lt;/h2>
&lt;p>RPC(Remote Procedure Call)은 원격, 그러니까 실행되는 프로그램 입장에서 자신이 아닌 외부의 함수를 호출하는 걸 뜻합니다. 그럼 &lt;code>g&lt;/code>는 뭐냐 싶었는데 찾아보니 이건 좀 &lt;a href="https://github.com/bojand/fsto-2018-grpc/blob/master/slides.md#grpc-">개발자들의 말장난&lt;/a> 같은 느낌이네요&amp;hellip; 버전마다 뜻이 다르답니다. 뭐 그렇다니까 그렇겠죠&amp;hellip;&lt;/p>
&lt;p align="center">&lt;img src="./image.png" width="400">&lt;/p>
&lt;p>어쨌거나 gRPC는 바이너리 형식인 Protocol Buffer(protobuf)를 사용해서 REST에 비해 매우 빠르고, 엄격하게 주고 받을 데이터 및 호출할 함수에 대한 정의를 해줄 수 있는 등의 장점이 있습니다. 단점이라면&amp;hellip; 코드가 기존 REST에 비해서 좀 이해하기 어렵다? 별도의 코드 생성과정이 필요하다? 뭐 이런게 있겠네요. 그래서 저도 좀 이해하기가 어려웠었죠.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>항목&lt;/th>
&lt;th>REST&lt;/th>
&lt;th>gRPC&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>프로토콜&lt;/td>
&lt;td>HTTP/1.1 (주로)&lt;/td>
&lt;td>HTTP/2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>데이터 형식&lt;/td>
&lt;td>JSON (텍스트)&lt;/td>
&lt;td>Protocol Buffers (바이너리)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>계약 정의&lt;/td>
&lt;td>OpenAPI/Swagger (선택)&lt;/td>
&lt;td>.proto 파일 (필수)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>스트리밍&lt;/td>
&lt;td>제한적&lt;/td>
&lt;td>양방향 스트리밍 지원&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>브라우저 지원&lt;/td>
&lt;td>네이티브&lt;/td>
&lt;td>gRPC-Web 필요&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="그냥-한-번-해보자">그냥 한 번 해보자!&lt;/h2>
&lt;p>자 그럼 일단 그냥 한 번 해보죠. 아주 간단하게 서버와 클라이언트가 인사를 주고 받는 예제를 만들어 보겠습니다.&lt;/p>
&lt;h3 id="폴더-생성-및-go-모듈을-초기화">폴더 생성 및 go 모듈을 초기화&lt;/h3>
&lt;p>&lt;code>grpc-helloworld&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; mkdir grpc-helloworld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">cd&lt;/span> grpc-helloworld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go mod init grpc-helloworld
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="grpc-계약contract-작성">gRPC 계약(contract) 작성&lt;/h3>
&lt;p>이제 gRPC를 통해 어떤 메시지를 주고 받을 거며, 어떤 원격의 함수를 호출할 수 있는지 계약(contract)을 정의해주겠습니다. &lt;code>greet.proto&lt;/code> 파일에 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-proto" data-lang="proto">&lt;span class="line">&lt;span class="cl">&lt;span class="n">syntax&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;proto3&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&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="err">&lt;/span>&lt;span class="kn">package&lt;/span> &lt;span class="nn">greet&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&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="err">&lt;/span>&lt;span class="c1">// 생성된 go 코드가 위치할 경로
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">option&lt;/span> &lt;span class="n">go_package&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;./pb&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&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="err">&lt;/span>&lt;span class="c1">// gRPC를 통해 제공할 서비스(함수 목록)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">service&lt;/span> &lt;span class="n">Greeter&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="k">rpc&lt;/span> &lt;span class="n">SayHello&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">HelloRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">returns&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">HelloReply&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">
&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="err">&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="kd">message&lt;/span> &lt;span class="nc">HelloRequest&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&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 class="err">
&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="err">&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="kd">message&lt;/span> &lt;span class="nc">HelloReply&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="kd">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&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 class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>추가 설명이 필요 없을 정도로 직관적이죠?&lt;/p>
&lt;h3 id="grpc-코드-생성">gRPC 코드 생성&lt;/h3>
&lt;p>이제 proto 파일에 정의된 서비스를 호출 및 응답하는데 사용할 코드를 생성해야 합니다.&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"># Protocol Buffer 컴파일러 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># macos의 경우&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; brew install protobuf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># linux(ubuntu / debian)의 경우&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;gt; sudo apt install -y protobuf-compiler&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"># go install 명령으로 설치된 패키지 경로 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;export PATH=&amp;#34;$PATH:$(go env GOPATH)/bin&amp;#34;&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">source&lt;/span> ~/.zshrc
&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"># grpc 관련 패키지 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
&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">&amp;gt; protoc --go_out&lt;span class="o">=&lt;/span>. --go-grpc_out&lt;span class="o">=&lt;/span>. greet.proto
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>pb 폴더를 확인해보면 &lt;code>greet_grpc.pb.go&lt;/code>, &lt;code>greet.pb.go&lt;/code> 파일 두 개가 생성된 걸 확인할 수 있습니다.&lt;/p>
&lt;ul>
&lt;li>greet.pb.go: proto 파일에 정의된 서비스와 요청/응답 메시지에 대한 정의를 담고 있습니다.&lt;/li>
&lt;li>greet_grpc.pb.go: gRPC 클라이언트와 서버의 구현을 담고 있습니다.&lt;/li>
&lt;/ul>
&lt;h3 id="서버-코드-작성">서버 코드 작성&lt;/h3>
&lt;p>일단 코드를 작성하기 전에 다음 명령을 실행하여 부수적으로 필요한 패키지들을 모두 설치하고, go.mod 파일을 정리하도록 합니다.&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; go mod tidy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>server/main.go&lt;/code> 파일을 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net&amp;#34;&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="s">&amp;#34;grpc-helloworld/pb&amp;#34;&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="s">&amp;#34;google.golang.org/grpc&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">server&lt;/span> &lt;span class="kd">struct&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"> &amp;#34;protoc --go_out=. --go-grpc_out=. greet.proto&amp;#34; 명령으로 생성된 코드에는 greeterServer, greeterClient 구현이 있음.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> UnimplementedGreeterServer 구조체 역시 protoc가 생성한 것으로 proto 파일에 정의된 Greeter 서비스의 모든 메서드에 대해 껍데기 구현을 가지고 있음.(호출시 codes.Unimplemented 오류 리턴)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> 이는 추후 proto 파일에 새로운 메서드가 추가되고, 이에 대한 구현이 되지 않았을 때 컴파일 단계의 오류를 방지하기 위함.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> -&amp;gt; 컴파일은 잘 되지만, 해당 함수를 호출하면 codes.Unimplemented 오류 리턴.
&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">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">UnimplementedGreeterServer&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">// pb.UnimplementedGreeterServer의 SayHello 함수를 오버라이드(더 정확히는 덮어서 감추기, shadowing)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">SayHello&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">in&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HelloRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HelloReply&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">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="k">return&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HelloReply&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Hello &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">in&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetName&lt;/span>&lt;span class="p">()},&lt;/span> &lt;span class="kc">nil&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">func&lt;/span> &lt;span class="nf">main&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">// 50051번 포트에 대해 네트워크 리스너를 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lis&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;tcp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:50051&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to listen: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">// gRPC 서버 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServer&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Greeter grpc 서비스에 서버를 등록&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RegisterGreeterServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">server&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="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Server listening at :50051&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Serve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">lis&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to serve: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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>이 코드에서 유심히 봐야 할 내용은(처음에 제가 이해를 못했던 내용은) server 선언부 입니다. 이걸 이해하려면 일단 &lt;code>greet_grpc.pb.go&lt;/code> 파일의 다음 내용을 봐야 합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="c1">// UnimplementedGreeterServer must be embedded to have&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// forward compatible implementations.&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">// NOTE: this should be embedded by value instead of pointer to avoid a nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// pointer dereference when methods are called.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">UnimplementedGreeterServer&lt;/span> &lt;span class="kd">struct&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">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">UnimplementedGreeterServer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">SayHello&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">HelloRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">HelloReply&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">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="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">status&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">codes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Unimplemented&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;method SayHello not implemented&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>proto 파일에 정의했던 서비스의 함수가 UnimplementedGreeterServer의 메서드로 정의되어 있는데요, 내용을 보면 &lt;code>이 함수가 아직 구현되지 않았다&lt;/code> 는 오류 메시지입니다. 그러니까 gRPC 서버를 구현할 때, &lt;code>SayHello&lt;/code> 라는 메서드를 직접 구현하지 않으면 대신 이 기본 구현이 실행되면서 &lt;code>서비스를 제공할 함수를 구현해야 한다&lt;/code> 고 알려주는 거죠.&lt;/p>
&lt;p>근데 왜 이렇게 할까요? 이건 안전장치입니다.&lt;/p>
&lt;p>새로운 gRPC 함수가 추가된다고 생각해보죠. 그러면 우선 proto 파일을 수정합니다. 그리고 코드를 생성하죠. 그런데 만약 gRPC 서버 구현을 맡은 사람이 새 함수를 구현하는 걸 깜빡하면 어떻게 될까요? 갑자기 gRPC 서버는 컴파일 조차 되지 않을겁니다. 인터페이스의 함수를 모두 구현하지 못했기 때문이죠.&lt;/p>
&lt;p>그런데 기본 구현을 생성해서 그걸 서버 구현에 임베드 하게 된다면? 구현을 못 한 상태에서도 기존 구현은 아무 문제 없이 컴파일 되고 동작합니다!&lt;/p>
&lt;h3 id="클라이언트-코드-작성">클라이언트 코드 작성&lt;/h3>
&lt;p>다음으로 &lt;code>client/main.go&lt;/code>를 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;grpc-helloworld/pb&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;time&amp;#34;&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="s">&amp;#34;google.golang.org/grpc&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;google.golang.org/grpc/credentials/insecure&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&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. 서버에 연결하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">conn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;localhost:50051&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTransportCredentials&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">insecure&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCredentials&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to dial: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">defer&lt;/span> &lt;span class="nx">conn&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&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. GreeterClient 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewGreeterClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">conn&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. SayHello 메서드 호출(1초 동안 서버가 응답하지 않으면 컨텍스트 취소)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">cancel&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">r&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SayHello&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">pb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HelloRequest&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;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="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to say hello: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Server response: %s&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetMessage&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>gRPC 서버에 연결해서 요청을 보내고 응답을 수신합니다. 만약 요청이 처리되는데 1초 이상 소요된다면, 요청을 취소하게 설정되었습니다.&lt;/p>
&lt;h3 id="서버와-클라이언트-실행">서버와 클라이언트 실행&lt;/h3>
&lt;p>2개의 터미널을 열고 먼저 서버를 실행합니다.&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">❯ go run server/main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2026/03/22 10:22:14 Server listening at :50051
&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">❯ go run client/main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2026/03/22 10:22:51 Server response: Hello World
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="정리">정리&lt;/h2>
&lt;p>매우 간단한 예제지만 gRPC 계약 정의와 코드 생성 및 서버와 클라이언트 사용 등의 내용은 충분히 확인할 수 있었습니다. 이걸 몰랐을 때는 완전히 블랙박스 처럼 보였던 코드와 과정을 좀 더 명확하게 이해할 수 있었죠! 이 글의 모든 학습과정은 gemini가 생성한 튜토리얼을 기반으로 저의 삽질을 통해 진행하였습니다.&lt;/p></content></item><item><title>분산 시스템 설계(1) - Paxos 심화(분산 Key-Value 스토어)</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%8B%AC%ED%99%94-%EB%B6%84%EC%82%B0-key-value-%EC%8A%A4%ED%86%A0%EC%96%B4/</link><pubDate>Sun, 22 Mar 2026 10:34:52 +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%8B%AC%ED%99%94-%EB%B6%84%EC%82%B0-key-value-%EC%8A%A4%ED%86%A0%EC%96%B4/</guid><description>Paxos 알고리즘 관련 세번째 글입니다. 그러니까&amp;hellip; 지난 글에서는 아주 간단하게 리더를 선출하는 예제를 만들어 봤었지만, 그게 뭐랄까 좀 예제를 위한 예제 같은 느낌이 들었습니다. Multi-Paxos를 제대로 구현하지도 못했고 말이죠. 그래서 Claude와 …</description><content>&lt;p>Paxos 알고리즘 관련 세번째 글입니다. 그러니까&amp;hellip; 지난 글에서는 아주 간단하게 리더를 선출하는 예제를 만들어 봤었지만, 그게 뭐랄까 좀 예제를 위한 예제 같은 느낌이 들었습니다. Multi-Paxos를 제대로 구현하지도 못했고 말이죠. 그래서 Claude와 함께 고민을 하던 중에 Redis 같은 분산 Key-Value 스토어(이하 &lt;code>dkv&lt;/code>)를 만들어 보는 게 어떨까 싶었습니다. 그래서 관련해서 튜토리얼을 생성해내고, 직접 쳐보기도 하면서 Claude Code와 예제를 완성하고 코드를 모두 한 줄씩 읽어가면서 동작을 이해하려 노력해봤습니다.&lt;/p>
&lt;p>그 과정의 결과물을 &lt;a href="https://github.com/netscout/dkv">여기&lt;/a> 에 올려두었습니다.&lt;/p>
&lt;h2 id="간략한-소개">간략한 소개&lt;/h2>
&lt;p>해당 리포지토리에 방문하시면 훨씬 더 상세한 README가 기다리고 있긴 합니다. 해당 내용은 초반에 틀을 제가 손으로 직접 잡고 나머지 부분을 Claude Code와 함께 완성 및 보강했습니다. 여기서는 어떻게 &lt;code>dkv&lt;/code>가 동작하는지를 좀 설명드려보고자 합니다.&lt;/p>
&lt;h3 id="paxos-prepare-accept를-통한-리더-선출">Paxos Prepare-Accept를 통한 리더 선출&lt;/h3>
&lt;p>우선 3개의 노드를 실행하는 과정을 볼까요? 일단 첫 번째 노드부터 실행합니다.&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">❯ go run ./cmd/dkv -id&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">2026/03/17 14:16:53 gRPC listening on 127.0.0.1:9001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:53.495+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;wal: 재생 완료&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">lastApplied&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">totalRecords&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">42&lt;/span> &lt;span class="nv">acceptorStates&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">41&lt;/span> &lt;span class="nv">committedEntries&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 class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:53.495+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;paxos: 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;[1 2 3]&amp;#34;&lt;/span> &lt;span class="nv">electionTimeout&lt;/span>&lt;span class="o">=&lt;/span>500ms &lt;span class="nv">heartbeatInterval&lt;/span>&lt;span class="o">=&lt;/span>100ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:53.495+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http: HTTP 서버 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">addr&lt;/span>&lt;span class="o">=&lt;/span>127.0.0.1:8001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Node &lt;span class="m">1&lt;/span> started — HTTP: 127.0.0.1:8001, gRPC: 127.0.0.1:9001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.253+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 타이머 발동, 선거 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">elapsed&lt;/span>&lt;span class="o">=&lt;/span>2562047h47m16.854775807s &lt;span class="nv">threshold&lt;/span>&lt;span class="o">=&lt;/span>500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.253+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.1 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.256+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.1 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9002: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.256+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.1 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9003: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.257+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.1 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N30.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.257+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.1 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>실패
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.257+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: ballot fast-forward&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">from&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.257+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;🔴election failed: 0/2 promises&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.988+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 타이머 발동, 선거 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">elapsed&lt;/span>&lt;span class="o">=&lt;/span>2562047h47m16.854775807s &lt;span class="nv">threshold&lt;/span>&lt;span class="o">=&lt;/span>500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.988+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N31.1 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.988+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N31.1 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9002: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.988+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N31.1 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9003: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.994+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 약속 받음&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N31.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.994+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N31.1 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>실패
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:16:54.994+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;🔴election failed: 1/2 promises&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>뭔가 로그가 복잡해 보이지만, 리더 선출을 위한 선거(Prepare)를 진행하고 있습니다. 🟢과 🔴를 주목해서 보시면 선거가 시작됐지만 아직 자신 밖에 없으므로 선거는 실패하고 있습니다. 자 이제 2번 노드 투입합니다!&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">❯ go run ./cmd/dkv -id&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2026/03/17 14:17:01 gRPC listening on 127.0.0.1:9002
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:01.908+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;wal: 재생 완료&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">lastApplied&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">totalRecords&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">20&lt;/span> &lt;span class="nv">acceptorStates&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">19&lt;/span> &lt;span class="nv">committedEntries&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 class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:01.908+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;paxos: 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;[1 2 3]&amp;#34;&lt;/span> &lt;span class="nv">electionTimeout&lt;/span>&lt;span class="o">=&lt;/span>500ms &lt;span class="nv">heartbeatInterval&lt;/span>&lt;span class="o">=&lt;/span>100ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:01.908+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http: HTTP 서버 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">addr&lt;/span>&lt;span class="o">=&lt;/span>127.0.0.1:8002
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Node &lt;span class="m">2&lt;/span> started — HTTP: 127.0.0.1:8002, gRPC: 127.0.0.1:9002
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.429+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 타이머 발동, 선거 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">elapsed&lt;/span>&lt;span class="o">=&lt;/span>2562047h47m16.854775807s &lt;span class="nv">threshold&lt;/span>&lt;span class="o">=&lt;/span>500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.429+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.2 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.431+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.2 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9003: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.431+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.2 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N42.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.432+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.2 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N25.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.432+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.2 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>실패
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.432+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: ballot fast-forward&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">from&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">42&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.432+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;🔴election failed: 0/2 promises&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.991+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 타이머 발동, 선거 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">elapsed&lt;/span>&lt;span class="o">=&lt;/span>2562047h47m16.854775807s &lt;span class="nv">threshold&lt;/span>&lt;span class="o">=&lt;/span>500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.991+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">42&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.991+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9003: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.996+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 약속 받음&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.000+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 약속 받음&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.000+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>성공
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.000+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 미커밋 엔트리 재제안&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">slot&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">key&lt;/span>&lt;span class="o">=&lt;/span>hahaha
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.008+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🎉 리더 당선&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">committedUpTo&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>마지막 줄을 보시면 2번 노드는 시작되자 마자 과반수의 지지를 받아 리더가 됩니다! 이때 1번 노드의 상황을 볼까요?&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.997+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.1 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">42&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.997+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.1 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9003: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:02.997+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 전송 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.1 &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;rpc error: code = Unavailable desc = connection error: desc = \&amp;#34;transport: Error while dialing: dial tcp 127.0.0.1:9002: connect: connection refused\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.000+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.1 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.000+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.1 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>실패
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.000+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;🔴election failed: 0/2 promises&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:03.110+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;heartbeat: 새 리더 감지&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">leader&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">prevLeader&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>자신이 리더가 되고자 하는 요청(Prepare)는 한 표도 받지 못했고, 2번 노드가 2표를 모두 약속받은 뒤 Accept까지 진행해버렸기 때문에 2번 노드가 새로운 리더임을 인정하게 됩니다.&lt;/p>
&lt;p>이제 3번 노드가 또 합류합니다!&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">❯ go run ./cmd/dkv -id&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">2026/03/17 14:17:11 gRPC listening on 127.0.0.1:9003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:11.717+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;wal: 재생 완료&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">lastApplied&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">totalRecords&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">31&lt;/span> &lt;span class="nv">acceptorStates&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">30&lt;/span> &lt;span class="nv">committedEntries&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 class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:11.717+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;paxos: 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;[1 2 3]&amp;#34;&lt;/span> &lt;span class="nv">electionTimeout&lt;/span>&lt;span class="o">=&lt;/span>500ms &lt;span class="nv">heartbeatInterval&lt;/span>&lt;span class="o">=&lt;/span>100ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:11.717+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http: HTTP 서버 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">addr&lt;/span>&lt;span class="o">=&lt;/span>127.0.0.1:8003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Node &lt;span class="m">3&lt;/span> started — HTTP: 127.0.0.1:8003, gRPC: 127.0.0.1:9003
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.468+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 타이머 발동, 선거 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">elapsed&lt;/span>&lt;span class="o">=&lt;/span>2562047h47m16.854775807s &lt;span class="nv">threshold&lt;/span>&lt;span class="o">=&lt;/span>500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.469+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.3 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.471+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.3 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.471+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.3 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N34.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.471+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.3 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.471+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N1.3 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>실패
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.471+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: ballot fast-forward&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">from&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">43&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:12.471+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;🔴election failed: 0/2 promises&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:13.092+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 타이머 발동, 선거 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">elapsed&lt;/span>&lt;span class="o">=&lt;/span>2562047h47m16.854775807s &lt;span class="nv">threshold&lt;/span>&lt;span class="o">=&lt;/span>500ms
&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="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:16.468+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 🟢 prepare 시작&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N49.3 &lt;span class="nv">prevNumber&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">48&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:16.469+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N49.3 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:16.469+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 거부됨&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N49.3 &lt;span class="nv">promisedBallot&lt;/span>&lt;span class="o">=&lt;/span>N43.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:16.472+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 약속 받음&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">peer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N49.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:16.472+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: prepare 결과&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">promises&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="nv">majority&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N49.3 &lt;span class="nv">result&lt;/span>&lt;span class="o">=&lt;/span>실패
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:16.472+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;election: 실패&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">err&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;🔴election failed: 1/2 promises&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">time&lt;/span>&lt;span class="o">=&lt;/span>2026-03-17T14:17:17.109+09:00 &lt;span class="nv">level&lt;/span>&lt;span class="o">=&lt;/span>INFO &lt;span class="nv">msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;heartbeat: 새 리더 감지&amp;#34;&lt;/span> &lt;span class="nv">node&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span> &lt;span class="nv">leader&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="nv">ballot&lt;/span>&lt;span class="o">=&lt;/span>N43.2 &lt;span class="nv">prevLeader&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>처음에는 자신을 리더로 제안(Propose)하지만, 결국 2번 노드가 리더임을 인정하게 됩니다.&lt;/p>
&lt;h3 id="paxos-accept를-통한-key-value-전파">Paxos Accept를 통한 key-value 전파&lt;/h3>
&lt;p>3번 노드에 키를 &lt;code>gundam&lt;/code>으로 하고 값을 &lt;code>건담이말을건담&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">❯ curl -L -X PUT http://127.0.0.1:8003/kv/gundam -d &lt;span class="s2">&amp;#34;건담이말을건담&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OK &lt;span class="o">(&lt;/span>&lt;span class="nv">index&lt;/span>&lt;span class="o">=&lt;/span>2&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>참고로 &lt;code>-L&lt;/code> 플래그는 요청이 리다이렉트 될 수 있도록 해줍니다. 현재 &lt;code>dkv&lt;/code>는 리더가 아닌 노드에게 쓰기 요청을 하면 해당 요청을 리더에게 리다이렉트 하도록 구현되었거든요! 자 그럼 1번 노드에서 키를 조회해볼까요?&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">❯ curl http://127.0.0.1:8001/kv/gundam
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">건담이말을건담
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>3번 노드가 쓰기 요청을 리더인 2번 노드에게 전달하고, 리더는 다른 노드들에 Accept 요청을 전송해서 쓰기 요청이 다른 노드에 전파되도록 합니다. 그래서 1번 노드에서도 키로 값을 조회해올 수 있는 거죠.&lt;/p>
&lt;blockquote>
&lt;p>리더 선출 과정에서 이미 Prepare를 거쳤기 때문에 바로 Accept단계로 넘어갑니다!&lt;/p>&lt;/blockquote>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>이걸 구현하고 이해해보려고 노력하면서 왜 Paxos가 더 이상 쓰이지 않는지 이해했습니다. 이게 좀 많이 복잡하네요. 솔직히 아직도 좀 헷갈립니다. 다음 부터는 &lt;code>dkv&lt;/code>를 만드는 과정에서 제가 새롭게 배운 내용들을 따로 한 번 정리해보려고 합니다. 그러고 나서 Raft를 추가로 구현해볼 생각입니다.&lt;/p></content></item><item><title>분산 시스템 설계(1) - Paxos 알고리즘(GO)</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-go/</link><pubDate>Sun, 22 Feb 2026 08:03:55 +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-go/</guid><description>지난 글에서는 Paxos 알고리즘에 대해서 정리를 했고, 리더 선출 예시를 살펴보면서 구체적인 알고리즘 동작과정을 살펴봤습니다. 해당 예시는 node.js로 작성을 했었죠. 이번에는 그 예시의 Go 버전을 확인해볼까 합니다. 이유는&amp;hellip; 별게 없고 그냥 제가 최근에 …</description><content>&lt;p>&lt;a href="../%EB%B6%84%EC%82%B0%20%EC%8B%9C%EC%8A%A4%ED%85%9C%20%EC%84%A4%EA%B3%84%281%29%20-%20Paxos%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/%EB%B6%84%EC%82%B0%20%EC%8B%9C%EC%8A%A4%ED%85%9C%20%EC%84%A4%EA%B3%84%281%29%20-%20Paxos%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98.md">지난 글&lt;/a>에서는 Paxos 알고리즘에 대해서 정리를 했고, 리더 선출 예시를 살펴보면서 구체적인 알고리즘 동작과정을 살펴봤습니다. 해당 예시는 node.js로 작성을 했었죠. 이번에는 그 예시의 Go 버전을 확인해볼까 합니다. 이유는&amp;hellip; 별게 없고 그냥 제가 최근에 Go를 좀 보기 시작 했기 때문입니다! 하하하하하&amp;hellip;.&lt;/p>
&lt;p>구조와 동작은 지난 글의 내용과 똑같습니다. 다만, Go는 접근 방식이 다르다 보니 구체적인 구현에서는 좀 차이를 보이는데요. 그런 특징적인 부분들을 정리해보고자 합니다. 미래의 저를 위해서 말이죠!&lt;/p>
&lt;h2 id="코드-실행-방법">코드 실행 방법&lt;/h2>
&lt;p>go는 원래 컴파일을 통해 실행 파일을 생성하지만, 다음 명령을 통해 바로 실행 해볼 수 있으며, 실행 과정은 이전의 node.js 버전과 동일합니다!&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; go run . cluster
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>go run 명령도 컴파일을 합니다! 컴파일을 통해 임시 바이너리를 생성해서 실행을 하지만, 눈에 보이지 않을 뿐입니다. 개발 중에 계속 컴파일 -&amp;gt; 실행은 너무 귀찮죠 하하하하&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;h2 id="프로그램-진입점-구현">프로그램 진입점 구현&lt;/h2>
&lt;p>프로그램이 시작되는 &lt;code>main.go&lt;/code>는 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&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="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; go run . cluster -- 클러스터 모드 (3개 노드 실행)&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">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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="k">switch&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&lt;/span>&lt;span class="p">[&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="k">case&lt;/span> &lt;span class="s">&amp;#34;node&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="nf">runNode&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// 클러스터 관리 cli가 각 노드를 실행할 때 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;cluster&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="nf">runCluster&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// 클러스터 관리 cli를 실행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;알 수 없는 명령어: %s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&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="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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 class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>프로그램에 설정된 인자에 따라서 서로 다른 모드로 실행합니다.&lt;/p>
&lt;ul>
&lt;li>cluster: 클러스터 관리 cli를 실행&lt;/li>
&lt;li>node: 서버 노드를 실행&lt;/li>
&lt;/ul>
&lt;p>앞서 실행 방법에서 봤듯이 우리는 &amp;ldquo;cluster&amp;rdquo; 모드로 클러스터 관리 cli를 실행합니다. 그리고 클러스터 관리 cli가 &amp;ldquo;node&amp;rdquo; 모드로 3개의 서버 노드를 실행하게 되죠. &lt;code>main.go&lt;/code>와 이후에 살펴볼 &lt;code>cluster.go&lt;/code>, &lt;code>node.go&lt;/code> 가 모두 같은 &lt;code>main&lt;/code> 패키지에 포함되어 있기 때문에 다른 파일의 함수를 자유롭게 가져다 쓸 수 있습니다.&lt;/p>
&lt;h2 id="클러스터-관리자-cli-구현">클러스터 관리자 cli 구현&lt;/h2>
&lt;p>&lt;code>cluster.go&lt;/code>의 전체 소스코드는 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;bufio&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;encoding/json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os/exec&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os/signal&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;strconv&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;strings&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;sync&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;syscall&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;time&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">nodeConfig&lt;/span> &lt;span class="kd">struct&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">int&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">int&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="kd">var&lt;/span> &lt;span class="nx">nodes&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">nodeConfig&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="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">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="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">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="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">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="kd">var&lt;/span> &lt;span class="nx">colors&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">]&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="mi">1&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;\033[36m&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 파란색 (cyan)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">2&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;\033[33m&amp;#34;&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="mi">3&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;\033[35m&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 핑크색 (magenta)&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">const&lt;/span> &lt;span class="nx">colorReset&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;\033[0m&amp;#34;&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="kd">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 키 타입이 int이고, 값 타입이 *exec.Cmd인 맵&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="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">exec&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Cmd&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 맵에 대한 동시성 접근을 위한 뮤텍스(lock)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processesMu&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Mutex&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="kd">func&lt;/span> &lt;span class="nf">startNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nc&lt;/span> &lt;span class="nx">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="kd">var&lt;/span> &lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">nodes&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="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">nc&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="nx">peers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:%d&amp;#34;&lt;/span>&lt;span class="p">,&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="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">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">nc&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s노드 %d 시작...%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nc&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">colorReset&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="nx">args&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;run&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;node&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Itoa&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nc&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">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Itoa&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">args&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">peers&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="nx">cmd&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">exec&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;go&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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="nx">stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StdoutPipe&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;노드 %d stdout pipe 실패: %v\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nc&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">err&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>&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">stderr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StderrPipe&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;노드 %d stderr pipe 실패: %v\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nc&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">err&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>&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="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Start&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;노드 %d 시작 실패: %v\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nc&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">err&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>&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">// 여러 goroutine에서 동시에 접근할 수 있으므로, 동시성 접근을 위해 뮤텍스를 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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">nc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">cmd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">// goroutine: 노드 프로세스의 출력에 색상을 적용하여 출력&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&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">scanner&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bufio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewScanner&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">stdout&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="nx">scanner&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Scan&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s%s%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">scanner&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Text&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">colorReset&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">// goroutine: 노드 프로세스의 에러 출력에 색상을 적용하여 출력&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&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">scanner&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bufio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewScanner&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">stderr&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="nx">scanner&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Scan&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s[노드 %d 에러] %s%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nc&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">scanner&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Text&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">colorReset&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">// goroutine: 노드 프로세스가 종료될 때 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&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">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">exitCode&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ProcessState&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">exitCode&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">ProcessState&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ExitCode&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s[노드 %d] 프로세스 종료 (code: %d, err: %v)%s\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="nx">color&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nc&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">exitCode&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">colorReset&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">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">delete&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">nc&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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="kd">func&lt;/span> &lt;span class="nf">killNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">int&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">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">cmd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">exists&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">processes&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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="k">if&lt;/span> &lt;span class="nx">exists&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Signal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[클러스터] 노드 %d 시그널 전송 실패: %v\n&amp;#34;&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">err&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">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">id&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n%s[클러스터] 노드 %d 종료%s\n\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&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">colorReset&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n[클러스터] 노드 %d 실행 중이 아님\n\n&amp;#34;&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 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="kd">func&lt;/span> &lt;span class="nf">printStatus&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 타임아웃이 1초인 http.Client 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">node&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">nodes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 즉시 실행되는 익명 함수. 함수 종료시 defer 처리된 리소스 해제&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">func&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">url&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:%d/status&amp;#34;&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">port&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&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="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="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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s 노드 %d: 다운%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&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="p">,&lt;/span> &lt;span class="nx">colorReset&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>&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="c1">// err 발생시 resp는 nil. 따라서 err 체크 후 defer 처리가 일반적인 방법.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&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">var&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="nx">StatusResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// resp.Body를 JSON -&amp;gt; StatusResponse 타입으로 변환; 그리고 err 발생시 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s 노드 %d: (응답 파싱 실패)%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&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="p">,&lt;/span> &lt;span class="nx">colorReset&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>&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">leaderMark&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">leaderMark&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;👑&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">leaderStr&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;none&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&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="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">leaderStr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Itoa&lt;/span>&lt;span class="p">(&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">CurrentLeaderID&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">promisedStr&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;none&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&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="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promisedStr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Itoa&lt;/span>&lt;span class="p">(&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">PromisedProposal&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">acceptedStr&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;none&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&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="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedStr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Itoa&lt;/span>&lt;span class="p">(&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">AcceptedProposal&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s%s 노드 %d: leader=%s, term=%d, isLeader=%v, promised=%s, accepted=%s%s\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="nx">color&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">leaderMark&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="p">,&lt;/span> &lt;span class="nx">leaderStr&lt;/span>&lt;span class="p">,&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="p">,&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="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promisedStr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">acceptedStr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">colorReset&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&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">func&lt;/span> &lt;span class="nf">isValidNodeID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">bool&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">nodes&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="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">id&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">true&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="k">return&lt;/span> &lt;span class="kc">false&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">func&lt;/span> &lt;span class="nf">findNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">nodeConfig&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">n&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">nodes&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="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">id&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="o">&amp;amp;&lt;/span>&lt;span class="nx">n&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="k">return&lt;/span> &lt;span class="kc">nil&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 구현 ===&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">func&lt;/span> &lt;span class="nf">runCluster&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n🚀 Starting Paxos Leader Election Cluster...\n\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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">node&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">nodes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">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="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AfterFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;=&amp;#34;&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; kill &amp;lt;id&amp;gt; - 노드 종료 (특정 노드에 장애가 발생하여 종료되는 걸 시뮬레이션)&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; start &amp;lt;id&amp;gt; - 종료된 노드 재시작&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; status - 클러스터 상태 출력&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; exit - 모든 노드 종료 및 종료&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Repeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;=&amp;#34;&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&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">// goroutine 끼리 메세지를 주고받기 위한 채널 생성, 버퍼 크기를 1로 설정하여 수신자가 메세지를 받을 때까지 대기할 수 있도록 함&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sigChan&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Signal&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="c1">// Ctrl+C 발생시 해당 시그널을 채널로 전달&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Notify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sigChan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Interrupt&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">go&lt;/span> &lt;span class="kd">func&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">// 채널에서 메세지(Ctrl+C)를 받을 때까지 대기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">sigChan&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n\n🛑 모든 노드 종료...\n\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="c1">// 모든 노드 프로세스에 SIGTERM 시그널 전송&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cmd&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">processes&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="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Signal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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">// 대화형 CLI 명령어 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">scanner&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bufio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewScanner&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Stdin&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Stdin이 종료될 때까지 한 줄씩 읽어서 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">scanner&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Scan&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">line&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">TrimSpace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">scanner&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Text&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="nx">line&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s">&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="k">continue&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">parts&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SplitN&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">line&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&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">cmd&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">parts&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="nx">arg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">parts&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;gt;&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="nx">arg&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">parts&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="c1">// 명령어 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">switch&lt;/span> &lt;span class="nx">cmd&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="s">&amp;#34;kill&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">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Atoi&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nf">isValidNodeID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;잘못된 노드 ID: %s\n&amp;#34;&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">continue&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="nf">killNode&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="k">case&lt;/span> &lt;span class="s">&amp;#34;start&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">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Atoi&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nf">isValidNodeID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;잘못된 노드 ID: %s\n&amp;#34;&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">continue&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">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">running&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">processes&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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="nx">running&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n노드 %d 이미 실행 중\n\n&amp;#34;&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="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">nc&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">findNode&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="k">if&lt;/span> &lt;span class="nx">nc&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">startNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">nc&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">case&lt;/span> &lt;span class="s">&amp;#34;status&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="nf">printStatus&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="s">&amp;#34;exit&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n🛑 모든 노드 종료...\n\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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cmd&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">processes&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="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Signal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;help&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; kill &amp;lt;id&amp;gt; - 노드 종료&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; start &amp;lt;id&amp;gt; - 종료된 노드 재시작&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; status - 클러스터 상태 출력&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; exit - 모든 노드 종료 및 종료&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="k">default&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;알 수 없는 명령어: %s. &amp;#39;help&amp;#39; 명령어를 사용하세요.\n\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cmd&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="map-타입">map 타입&lt;/h3>
&lt;p>두 군데에서 map을 사용하고 있는데 타입 정의 생김새가 좀 다릅니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 키 타입이 int이고, 값 타입이 string인 맵&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">colors&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">]&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="mi">1&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;\033[36m&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 파란색 (cyan)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">2&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;\033[33m&amp;#34;&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="mi">3&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;\033[35m&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 핑크색 (magenta)&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 키 타입이 int이고, 값 타입이 *exec.Cmd인 맵&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="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">exec&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Cmd&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>우선 배열의 인덱스 처럼 둘러싸인 타입이 키의 타입이고, 그 뒤에 붙은 타입이 값의 타입이다 하는 건 몇 번 보다보면 알 수 있습니다. 그런데 &lt;code>colors&lt;/code>는 그냥 map을 생성했고, &lt;code>processes&lt;/code>는 &lt;code>make&lt;/code> 함수를 호출해서 생성하고 있네요?&lt;/p>
&lt;p>우선 colors는 map 리터럴(literal) 방식으로 map을 생성하고 있습니다. map안에 어떤 값이 들어갈지 명시해주면서 생성하고 있죠. 그래서 이 때는 Go가 map의 생성을 암묵적으로(implicitly) 처리해줍니다. 그런데 processes는 빈 map을 생성하고 있습니다. 그래서 암묵적으로 map을 생성할 수 없고, 따라서 make 함수를 호출해서 Go가 런타임 초기화를 실행하도록 해줘야 합니다.&lt;/p>
&lt;blockquote>
&lt;p>map은 javascript의 map, C#의 Dictionary와 같은 key-value 매핑을 저장하는 자료 구조이고 동작방식도 동일합니다. 다만, Go에서는 map을 생성하기 위해서 &lt;code>make&lt;/code> 함수를 사용해야 합니다. map외에도 make 함수를 사용하는 경우는 슬라이스와 채널이 있는데요, 이 타입들은 내부적으로 해시테이블, 동적 배열, 큐 등의 자료구조를 사용하기 때문에 Go가 런타임 초기화를 실행해야 합니다. 그래서 make를 실행해야 한다고 하네요! struct나 배열, int등의 다른 기본 타입들은 make없이도 각 타입의 기본 값(zero value)으로도 충분합니다.&lt;/p>&lt;/blockquote>
&lt;p>또 한가지 Go의 특징은 map에서 값을 찾을 때도 볼 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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">cmd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">exists&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">processes&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;/code>&lt;/pre>&lt;/div>&lt;p>map에서 값을 조회할 때, 리턴 받을 변수를 2개 지정해주면 2번째 변수에는 map에 특정 키에 해당하는 값이 있는지 여부를 같이 리턴해주는데요, 예를 들어 타입스크립트에서는 다음과 같이 해야 합니다.&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">const&lt;/span> &lt;span class="nx">cmd&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 class="kr">const&lt;/span> &lt;span class="nx">exists&lt;/span> &lt;span class="o">=&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">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>typescript 버전과 비교하면 가독성은 떨어지는 느낌입니다. 하지만 간결하죠? 간결함을 우선하는 Go의 철학이 느껴지네요!&lt;/p>
&lt;h3 id="동시성-처리-및-lock-걸기">동시성 처리 및 lock 걸기&lt;/h3>
&lt;h4 id="스레드와-고루틴-비교">스레드와 고루틴 비교&lt;/h4>
&lt;p>각 언어와 런타임은 여러 작업을 동시에 처리하기 위해 스레드(thread)를 활용합니다. 스레드는 간단하게 말하면, 코드 실행 묶음이라고 볼 수 있는데요, CPU의 각 코어는 한 번에 하나의 스레드만 처리할 수 있습니다. 예를 들어 1번 코어가 처리 중인 스레드가 네트워크를 통해 응답이 아주 오래걸리는 요청을 처리 중이라면 어떻게 될까요? 1번 코어가 그 스레드를 계속 처리하게 둔다면 우리는 코어 1개를 낭비하는 셈이죠. OS는 이런 비효율을 참지 못합니다. 오래 걸리는 스레드는 응답이 올 때 까지 잠깐 대기 시키고 다른 스레드를 처리하게 하는 등 멀티 스레드 작업을 진행하게 하죠. 언어와 런타임은 이런 OS의 스레드 관리를 기반으로 해서 자체적인 동시성 처리 모델을 제시합니다.&lt;/p>
&lt;p>다만, 이 멀티 스레드라는 게 공짜가 아닙니다. 코어가 처리해야 되는 스레드를 변경할 때는 문맥 교환(context-switching)이 발생합니다. 스레드1-&amp;gt;스레드2로 문맥 교환되는 과정을 간략하게 살펴보면요,&lt;/p>
&lt;ul>
&lt;li>커널 모드 진입*: CPU는 필요한 권한을 얻기 위해 유저 모드에서 커널 모드로 진입&lt;/li>
&lt;li>스레드1의 상태 저장: CPU 레지스터, 프로그램 카운터, 스택 포인터 등 스레드1의 모든 상태를 스레드1의 메모리 블록에 기록(TCB, Thread Control Block)&lt;/li>
&lt;li>스케쥴러 상태 갱신: OS가 스레드1을 대기 상태로 설정하고 스레드2를 대기 큐에서 선택&lt;/li>
&lt;li>TLB/cache 초기화*: 스레드1과 스레드2가 서로 다른 프로세스의 스레드라면, TLB(translation lookaside buffer)를 초기화&lt;/li>
&lt;li>스레드2의 상태 불러오기: 스레드2의 TCB에서 모든 상태를 불러오기&lt;/li>
&lt;li>유저 모드로 복귀*: CPU가 유저 모드로 돌아와서 스레드2의 이전 상태로 부터 계속 실행&lt;/li>
&lt;/ul>
&lt;p>저도 정확하게 다 아는 건 아니지만, 어쨌든 이런 복잡한 작업을 매번 진행해야 하고 특히 &lt;code>*&lt;/code> 가 붙은 부분들이 비교적 많은 시간이 걸리는 작업이라고 합니다. 게다가 또 하나의 문제점도 있습니다. 저 유명한 &lt;code>StackOverflow&lt;/code> 에러를 다들 아시겠지만, OS의 스레드는 대략 1-8MB 정도의 스택 메모리를 가지는데, 스레드 내부에서 발생하는 각 로컬 변수와 함수 호출 등을 기록하기 위해서 사용하죠. 그래서 어마어마하게 큰 로컬 변수나 어마어마하게 복잡한 제귀 호출을 하다보면 띵하고 StackOverflow가 발생합니다. 그런데 Go는 이걸 조금 다르게 접근한다고 하네요.&lt;/p>
&lt;p>Go는 이런 동시성 처리의 문제를 고루틴(Goroutine)으로 접근합니다. 고루틴은 OS레벨의 스레드가 아니라 각 스레드에서 스위칭해서 실행할 수 있도록 하는 일종의 경량 스레드라고 할 수 있을 거 같네요. 그리고 OS레벨의 스레드가 아니라는 건, 앞서 살펴본 문맥 교환이 발생하지 않도록 하면서도 수많은 동시성 작업을 처리할 수 있다는 말이 됩니다. 게다가 한 가지 더 장점이 있는데요, 고루틴은 스택 메모리를 유연하게 사용합니다. 매우 작게 시작해서(2-4KB) 필요에 따라 늘리고 줄일 수 있게 설계되었습니다.&lt;/p>
&lt;p>그러니까 OS 레벨의 스레드를 사용하는 경우,&lt;/p>
&lt;pre class="mermaid">graph TD
subgraph OS[&amp;#34;OS 커널 (스케줄러)&amp;#34;]
direction LR
OSS[&amp;#34;OS 스케줄러&amp;lt;br/&amp;gt;모든 스레드를 직접 관리&amp;lt;br/&amp;gt;⚠️ 비싼 문맥 교환&amp;#34;]
end
subgraph CPU[&amp;#34;하드웨어 (4코어)&amp;#34;]
C1[&amp;#34;코어 1&amp;#34;]
C2[&amp;#34;코어 2&amp;#34;]
C3[&amp;#34;코어 3&amp;#34;]
C4[&amp;#34;코어 4&amp;#34;]
end
subgraph Threads[&amp;#34;OS 스레드 (각 1-8 MB 고정 스택)&amp;#34;]
T1[&amp;#34;스레드 1&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T2[&amp;#34;스레드 2&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T3[&amp;#34;스레드 3&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T4[&amp;#34;스레드 4&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T5[&amp;#34;스레드 5&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T6[&amp;#34;스레드 6&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T7[&amp;#34;스레드 7&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
T8[&amp;#34;스레드 8&amp;lt;br/&amp;gt;📦 1 MB 스택&amp;#34;]
end
T1 &amp;amp; T2 &amp;amp; T3 &amp;amp; T4 &amp;amp; T5 &amp;amp; T6 &amp;amp; T7 &amp;amp; T8 --&amp;gt;|&amp;#34;OS 문맥 교환&amp;lt;br/&amp;gt;🐌 ~1-10 μs&amp;lt;br/&amp;gt;커널 모드 전환 + 레지스터 저장&amp;lt;br/&amp;gt;+ 캐시 플러시&amp;#34;| OSS
OSS --&amp;gt; C1 &amp;amp; C2 &amp;amp; C3 &amp;amp; C4
style OS fill:#8b0000,color:#fff
style CPU fill:#1a3a5c,color:#fff
style Threads fill:#2e2e1a,color:#fff&lt;/pre>&lt;p>스레드의 문맥 교환 비용도 막대 하지만, &lt;code>스레드의 개수 * 스택 메모리&lt;/code> 를 계산해보면, 메모리 사용량 역시 부담이 되죠.&lt;/p>
&lt;p>하지만 고루틴을 사용하는 경우,&lt;/p>
&lt;pre class="mermaid">graph TD
subgraph CPU[&amp;#34;하드웨어 (4코어)&amp;#34;]
C1[&amp;#34;코어 1&amp;#34;]
C2[&amp;#34;코어 2&amp;#34;]
C3[&amp;#34;코어 3&amp;#34;]
C4[&amp;#34;코어 4&amp;#34;]
end
subgraph OSThreads[&amp;#34;OS 스레드 (코어에 고정)&amp;#34;]
M1[&amp;#34;OS 스레드 1&amp;lt;br/&amp;gt;(M)&amp;#34;]
M2[&amp;#34;OS 스레드 2&amp;lt;br/&amp;gt;(M)&amp;#34;]
M3[&amp;#34;OS 스레드 3&amp;lt;br/&amp;gt;(M)&amp;#34;]
M4[&amp;#34;OS 스레드 4&amp;lt;br/&amp;gt;(M)&amp;#34;]
end
C1 --- M1
C2 --- M2
C3 --- M3
C4 --- M4
subgraph GoRuntime[&amp;#34;Go 런타임 스케줄러 (유저 공간)&amp;#34;]
GS[&amp;#34;Go 스케줄러&amp;lt;br/&amp;gt;⚡ 저비용 고루틴 전환&amp;lt;br/&amp;gt;스택 포인터만 교체&amp;#34;]
end
M1 &amp;amp; M2 &amp;amp; M3 &amp;amp; M4 --&amp;gt; GS
subgraph Goroutines[&amp;#34;고루틴 (초기 스택 2-4 KB, 필요시 자동 확장)&amp;#34;]
G1[&amp;#34;G1 🪶&amp;#34;]
G2[&amp;#34;G2 🪶&amp;#34;]
G3[&amp;#34;G3 🪶&amp;#34;]
G4[&amp;#34;G4 🪶&amp;#34;]
G5[&amp;#34;G5 🪶&amp;#34;]
G6[&amp;#34;G6 🪶&amp;#34;]
G7[&amp;#34;G7 🪶&amp;#34;]
G8[&amp;#34;G8 🪶&amp;#34;]
G9[&amp;#34;G9 🪶&amp;#34;]
G10[&amp;#34;G10 🪶&amp;#34;]
G11[&amp;#34;G11 🪶&amp;#34;]
G12[&amp;#34;G12 🪶&amp;#34;]
GN[&amp;#34;... 100K+ 🪶&amp;#34;]
end
GS --&amp;gt; G1 &amp;amp; G2 &amp;amp; G3 &amp;amp; G4 &amp;amp; G5 &amp;amp; G6 &amp;amp; G7 &amp;amp; G8 &amp;amp; G9 &amp;amp; G10 &amp;amp; G11 &amp;amp; G12 &amp;amp; GN
style CPU fill:#1a3a5c,color:#fff
style OSThreads fill:#2e1a2e,color:#fff
style GoRuntime fill:#2d5a27,color:#fff
style Goroutines fill:#1a2e1a,color:#fff&lt;/pre>&lt;p>각 고루틴은 CPU의 커널 전환 같은 문맥 교환 없이 사용할 수 있으므로 스레드 전환에 비해 50-100배 정도 빠르고 메모리 사용량도 현저히 적습니다.&lt;/p>
&lt;blockquote>
&lt;p>위 다이어그램에서는 간단하게 설명하기 위해서 마치 Go가 코어와 OS 스레드를 1:1 매핑 시키는 것 처럼 묘사했지만, 사실은 그렇지 않습니다. Go는 GMP(Goroutine-Machine-Processor) 스케쥴러를 사용하는데요, 이 스케쥴러는 OS 스레드(M)를 코어에 1:1 매핑하지는 않습니다. 실제로는 GOMAXPROCS 설정에 따라 논리적 프로세서(P)를 생성하고, 각 P는 OS 스레드(M)에 붙어서 고루틴(G)을 실행합니다. 그러니까 코어와 OS 스레드(M)가 1:N이 될 수도 있는거죠. 또한 특정 P의 대기열이 비면, 다른 P의 대기 중인 고루틴(G)을 빼앗아 올 수도 있습니다!&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Java의 Project Loom(JDK 21)에서 지원하는 Virtual Thread가 이 고루틴을 구현한 것입니다!&lt;/p>&lt;/blockquote>
&lt;h4 id="동시성-처리-작업에-lock-걸기">동시성 처리 작업에 lock 걸기&lt;/h4>
&lt;p>자 지금까지 미래의 저를 위해서 스레드와 고루틴을 비교해서 적어봤는데요, 어떤 방법을 사용하든 동시성을 다루다 보면 상태 값을 어떻게 공유해야 할지 문제가 생깁니다. 스레드나 고루틴은 생성 순서가 처리 순서를 보장하지 못하기 때문에 예상하지 못한 결과가 나올 수 있죠. 그래서 먼저 접근 한 쪽이 처리 우선권을 가지도록 lock을 걸 필요가 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 키 타입이 int이고, 값 타입이 *exec.Cmd인 맵&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="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">exec&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Cmd&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 맵에 대한 동시성 접근을 위한 뮤텍스(lock)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processesMu&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Mutex&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 여러 goroutine에서 동시에 접근할 수 있으므로, 동시성 접근을 위해 뮤텍스를 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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">nc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">cmd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mutex를 통해 lock을 획득한 고루틴만 값을 변경할 수 있도록 하고 있습니다. lock은 필요한 작업을 마치면 반드시 해제 해야 하고, lock을 거는 시간은 최소화 해야 합니다. 따라서 다음과 같은 두 가지 패턴역시 이후 살펴볼 &lt;code>node.go&lt;/code> 파일에서 등장합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">runElection&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&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 class="nx">allNodes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">allNodes&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"> * lock은 최대한 짧게 유지하는게 중요하므로 필요한 작업 이후에 해제해야 함.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 따라서 이후 작업에 사용할 node.allNodes를 로컬 변수에 복사하여 사용.
&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="nb">copy&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">majority&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">majority&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">nodeID&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// lock 해제&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">// 함수 종료시 리더 선출 상태를 초기화하기 위해 defer 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="kd">func&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionInProgress&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="o">...&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;ul>
&lt;li>lock이 필요한 최소한의 작업을 진행하고 그 값을 로컬 변수에 복사. 그리고 lock을 해제하고, 이후의 작업에서는 복사한 로컬 변수로 현재 상태에 대한 작업 진행&lt;/li>
&lt;li>함수 종료시 실행되어야 하는 작업을 defer로 감싸고 lock을 통해 상태 값을 변경&lt;/li>
&lt;/ul>
&lt;h3 id="채널">채널&lt;/h3>
&lt;p>채널은 고루틴끼리 서로 메세지를 주고 받을 수 있는 메세지 큐 같은 자료구조입니다. 이 코드에서는 다음과 같이 사용되고 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">runCluster&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// goroutine 끼리 메세지를 주고받기 위한 채널 생성, 버퍼 크기를 1로 설정하여 수신자가 메세지를 받을 때까지 대기할 수 있도록 함&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sigChan&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Signal&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="c1">// Ctrl+C 발생시 해당 시그널을 채널로 전달&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Notify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sigChan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Interrupt&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">go&lt;/span> &lt;span class="kd">func&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">// 채널에서 메세지(Ctrl+C)를 받을 때까지 대기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">sigChan&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n\n🛑 모든 노드 종료...\n\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="c1">// 모든 노드 프로세스에 SIGTERM 시그널 전송&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cmd&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">processes&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="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cmd&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Signal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&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="nx">processesMu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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 class="o">...&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>runCluster&lt;/code>를 실행하는 메인 고루틴에서 프로그램 종료 시그널을 수신하면, 해당 시그널을 채널을 통해 고루틴에게 전송합니다. 그리고 시그널을 받은(&amp;lt;-sigChan) 고루틴은 각 프로세스에 종료 시그널을 전송해서 모든 노드가 종료될 수 있도록 합니다.&lt;/p>
&lt;p>여기서 주목할 점은 채널을 생성할 때 버퍼 크기를 1로 설정해준 부분입니다. 만약 버퍼 크기가 없다면, 채널을 통해 전송한 메세지는 그 즉시 수신되어야 하며 수신자가 없다면 메세지를 전송한 고루틴은 수신자가 나타날 때 까지 계속 대기 하게됩니다. 그리고 만약 모든 고루틴이 메세지를 수신할 수 없는 상황이 된다면 deadlock이 발생하여 종료됩니다. 이후 &lt;code>node.go&lt;/code> 파일에서 좀 더 다양한 채널의 사용 예시가 등장합니다!&lt;/p>
&lt;h3 id="즉시-실행-익명-함수iife로-defer-보장">즉시 실행 익명 함수(IIFE)로 defer 보장&lt;/h3>
&lt;p>&lt;code>printStatus&lt;/code> 함수를 보면, 클러스터의 상태를 조회하기 위해 각 노드에 상태 조회 api 요청을 전송합니다. 그리고 api 응답을 닫기(close) 위해 defer를 사용하는데요, 이 작업이 항상 실행되도록 보장하기 위해 즉시 실행되는 익명 함수(IIFE, Immediately Invoked Function Express)를 사용하고 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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">func&lt;/span> &lt;span class="nf">printStatus&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 타임아웃이 1초인 http.Client 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&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="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">node&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">nodes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 즉시 실행되는 익명 함수. 함수 종료시 defer 처리된 리소스 해제&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">func&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">url&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:%d/status&amp;#34;&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">port&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&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="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="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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s 노드 %d: 다운%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&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="p">,&lt;/span> &lt;span class="nx">colorReset&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>&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="c1">// err 발생시 resp는 nil. 따라서 err 체크 후 defer 처리가 일반적인 방법.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&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">var&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="nx">StatusResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// resp.Body를 JSON -&amp;gt; StatusResponse 타입으로 변환; 그리고 err 발생시 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s 노드 %d: (응답 파싱 실패)%s\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">color&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="p">,&lt;/span> &lt;span class="nx">colorReset&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>&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="o">...&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>각 API 요청을 IIFE로 감싸면서 해당 함수가 끝날 때 defer된 &lt;code>defer resp.Body.Close()&lt;/code> 가 실행될 수 있도록 하고 있습니다.&lt;/p>
&lt;h2 id="웹-서버-노드-구현">웹 서버 노드 구현&lt;/h2>
&lt;p>이제 웹 서버 노드의 구현을 확인해보겠습니다. 우선 &lt;code>node.go&lt;/code>의 전체 코드는 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;bytes&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;encoding/json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;math/rand/v2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os/signal&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;strconv&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;strings&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;sync&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;syscall&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;time&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>&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">PrepareRequest&lt;/span> &lt;span class="kd">struct&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">int&lt;/span> &lt;span class="s">`json:&amp;#34;proposalNumber&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span> &lt;span class="kd">struct&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">bool&lt;/span> &lt;span class="s">`json:&amp;#34;promise&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">AcceptedProposal&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;acceptedProposal&amp;#34;`&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="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;acceptedValue&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">AcceptRequest&lt;/span> &lt;span class="kd">struct&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">int&lt;/span> &lt;span class="s">`json:&amp;#34;proposalNumber&amp;#34;`&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">int&lt;/span> &lt;span class="s">`json:&amp;#34;value&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span> &lt;span class="kd">struct&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">bool&lt;/span> &lt;span class="s">`json:&amp;#34;accepted&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">HeartbeatMessage&lt;/span> &lt;span class="kd">struct&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">int&lt;/span> &lt;span class="s">`json:&amp;#34;leaderId&amp;#34;`&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">int&lt;/span> &lt;span class="s">`json:&amp;#34;term&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">StatusResponse&lt;/span> &lt;span class="kd">struct&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="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;nodeId&amp;#34;`&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="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;currentLeaderId&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">CurrentTerm&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;currentTerm&amp;#34;`&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="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;promisedProposal&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">AcceptedProposal&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;acceptedProposal&amp;#34;`&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="kt">int&lt;/span> &lt;span class="s">`json:&amp;#34;acceptedValue&amp;#34;`&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">bool&lt;/span> &lt;span class="s">`json:&amp;#34;isLeader&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>&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// PaxosNode는 Paxos 노드의 모든 상태를 관리합니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">PaxosNode&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mu&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Mutex&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">// 컨텍스트 (graceful shutdown 용도)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cancel&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CancelFunc&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="nx">nodeID&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 노드 아이디&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">int&lt;/span> &lt;span class="c1">// 노드 포트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="c1">// 통신해야 할 다른 노드의 URL 목록&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">allNodes&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="c1">// 자신을 포함한 모든 노드의 URL 목록&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">majority&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 다수 필요 개수 (전체 노드 수 / 2 + 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="c1">// === Paxos Acceptor 상태 변수들 ===&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="kt">int&lt;/span> &lt;span class="c1">// 약속한 제안 번호 (nil = 아직 약속 없음)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="c1">// 수락된 제안 번호 (nil = 아직 수락 없음)&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="kt">int&lt;/span> &lt;span class="c1">// 수락된 값(리더의 노드 아이디) (nil = 아직 수락 없음)&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="nx">currentLeaderID&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="c1">// 현재 리더 아이디 (nil = 리더 없음)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentTerm&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 현재 턴&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionRound&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 선거 라운드&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="nx">heartbeatStop&lt;/span> &lt;span class="kd">chan&lt;/span> &lt;span class="kd">struct&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="nx">electionTimer&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Timer&lt;/span> &lt;span class="c1">// 하트비트 타임아웃 감시 타이머 (3~5초, 리더는 선출 후 중지)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">nextElectionTimer&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Timer&lt;/span> &lt;span class="c1">// 다음 선거 시작 타이머&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">electionInProgress&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="c1">// 선거 진행 중 플래그 (동시 선거 방지)&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">// HTTP 클라이언트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">httpClient&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&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">// newPaxosNode는 새로운 Paxos 노드를 생성합니다.&lt;/span>
&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="kd">func&lt;/span> &lt;span class="nf">newPaxosNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&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">PaxosNode&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithCancel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">selfURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">allNodes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">selfURL&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">peers&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">majority&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">)&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">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="k">return&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cancel&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">cancel&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 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">port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">port&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="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="nx">allNodes&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">majority&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">majority&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">httpClient&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&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">// intPtr는 int를 *int로 변환하는 헬퍼 함수입니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">intPtr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">int&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="o">&amp;amp;&lt;/span>&lt;span class="nx">v&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">// ceilDiv는 올림 나눗셈을 수행합니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">ceilDiv&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="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">int&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">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nx">b&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 Proposer 로직 ===&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">// runElection은 리더 선출 시작&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">runElection&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="k">if&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionInProgress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="k">return&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionInProgress&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&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="k">if&lt;/span> &lt;span class="nx">node&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">nil&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: max(0, ceilDiv(6, 3)) = 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">node&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">max&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">electionRound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">ceilDiv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">promisedProposal&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">allNodes&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionRound&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="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="nx">proposalNumber&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionRound&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nb">len&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">allNodes&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">allNodes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">allNodes&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"> * lock은 최대한 짧게 유지하는게 중요하므로 필요한 작업 이후에 해제해야 함.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 따라서 이후 작업에 사용할 node.allNodes를 로컬 변수에 복사하여 사용.
&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="nb">copy&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">majority&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">majority&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">nodeID&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// lock 해제&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">// 함수 종료시 리더 선출 상태를 초기화하기 위해 defer 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="kd">func&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionInProgress&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n[노드 %d] 🗳️ 리더 선출 시작 / 제안 번호 #%d\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&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>&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 1: 모든 노드에 PREPARE 요청 전송...\n&amp;#34;&lt;/span>&lt;span class="p">,&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">type&lt;/span> &lt;span class="nx">prepareResult&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PrepareResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&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">results&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nx">prepareResult&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">wg&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">WaitGroup&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="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">allNodes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&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="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">idx&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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">defer&lt;/span> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendPrepare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&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="nx">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">idx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">prepareResult&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">err&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">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeURL&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">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wait&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">// PREPARE 요청 결과 처리 - 약속을 받은 것들만 추출&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">promises&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">PrepareResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">results&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="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&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">resp&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Promise&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">promises&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">promises&lt;/span>&lt;span class="p">,&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">resp&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 1: 약속을 받은 노드 %d/%d 개 (필요 %d 개)\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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">promises&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">len&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">majority&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="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">promises&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">majority&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] ❌ Phase 1 실패: 약속을 받은 노드가 다수 필요 개수보다 적습니다. 리더 선출을 중단하고 다음 선거 시작.\n&amp;#34;&lt;/span>&lt;span class="p">,&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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>&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="nx">proposedValue&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nodeID&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="o">-&lt;/span>&lt;span class="mi">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="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">p&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">promises&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="k">if&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">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&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">&amp;gt;&lt;/span> &lt;span class="nx">highestAcceptedProposal&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="p">=&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proposedValue&lt;/span> &lt;span class="p">=&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>&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="nx">highestAcceptedProposal&lt;/span> &lt;span class="p">&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 1: 이전에 수락된 값 (노드 %d) 채택 / 제안 번호 #%d\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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">proposedValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">highestAcceptedProposal&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 1: 이전에 수락된 값이 없으므로 자신을 제안 (노드 %d)\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">proposedValue&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 2: 모든 노드에 ACCEPT (값: 노드 %d) 요청 전송...\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">proposedValue&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">type&lt;/span> &lt;span class="nx">acceptResult&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">AcceptResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&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="c1">// ACCEPT 요청 결과를 저장할 슬라이스 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptResults&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nx">acceptResult&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 모든 작업이 완료될 때까지 대기할 WaitGroup 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">wg&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">WaitGroup&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">// 자신을 포함한 모든 노드에 ACCEPT 요청 전송, 병렬 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">allNodes&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개 추가&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&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="c1">// goroutine: ACCEPT 요청 전송&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">idx&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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개 제거&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendAccept&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&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">proposedValue&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptResults&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">idx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">acceptResult&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">err&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">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeURL&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="c1">// 모든 작업이 완료될 때까지 대기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wait&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">// ACCEPT 요청 결과 처리 - 수락된 것들 카운트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">accepts&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">acceptResults&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="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&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">resp&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">resp&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="nx">accepts&lt;/span>&lt;span class="o">++&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 2: 수락된 노드 %d/%d 개 (필요 %d 개)\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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">accepts&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">majority&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="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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] ✅ 리더 선출 성공! 노드 %d 가 리더가 되었습니다. (제안 번호 %d)\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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">proposedValue&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 👑 나는 리더!\n&amp;#34;&lt;/span>&lt;span class="p">,&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="c1">// 리더는 자신의 선출 타임아웃을 중지 (팔로워만 타임아웃을 감시)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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 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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] ❌ Phase 2 실패: 수락된 노드가 다수 필요 개수보다 적습니다. 리더 선출을 중단하고 다음 선거 시작.\n&amp;#34;&lt;/span>&lt;span class="p">,&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">scheduleNextElection&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">sendPrepare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">proposalNumber&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">PrepareResponse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">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">body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Marshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">PrepareRequest&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">proposalNumber&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;marshal prepare request: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewRequestWithContext&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">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="s">&amp;#34;/paxos/prepare&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewReader&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 class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;create prepare request: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">httpClient&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;send prepare to %s: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">defer&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&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="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCode&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;prepare request to %s returned status %d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCode&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">var&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;decode prepare response from %s: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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="o">&amp;amp;&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&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">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">sendAccept&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">AcceptResponse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">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">body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Marshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">AcceptRequest&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">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="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="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;marshal accept request: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewRequestWithContext&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">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="s">&amp;#34;/paxos/accept&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewReader&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 class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;create accept request: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">httpClient&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;send accept to %s: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">defer&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&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="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCode&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;accept request to %s returned status %d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCode&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">var&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;decode accept response from %s: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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="o">&amp;amp;&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&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">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">sendHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&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="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Marshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">HeartbeatMessage&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">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="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="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;marshal heartbeat: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewRequestWithContext&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">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="s">&amp;#34;/leader/heartbeat&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewReader&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 class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;create heartbeat request: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">httpClient&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;send heartbeat to %s: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">defer&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&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">return&lt;/span> &lt;span class="kc">nil&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">startHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">term&lt;/span> &lt;span class="kt">int&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 기존 하트비트 중지 (TOCTOU 경합 방지를 위해 인라인 처리)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nb">close&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">heartbeatStop&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="c1">// 데이터는 없이 신호만 수신할 채널 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stopCh&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">stopCh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">// goroutine: 각 채널의 메세지 수신 및 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&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초 간격 타이머 채널 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ticker&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewTicker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// goroutine 종료시 타이머 종료&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">ticker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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="c1">// 종료될 때까지 무한 반복하는 이벤트 루프&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 각 채널에서 수신되는 신호를 대기하며, 수신된 신호에 따라 동작 결정(select)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">select&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="o">&amp;lt;-&lt;/span>&lt;span class="nx">stopCh&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="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">():&lt;/span> &lt;span class="c1">// context 취소 신호&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">ticker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1">// 1초 간격 타이머 채널 신호&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 💓 하트비트 전송 (턴 %d)\n&amp;#34;&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">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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 이후 작업에 사용할 node.peers를 로컬 변수에 복사하여 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">peers&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">peers&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&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">peers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// lock 해제&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">peerURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&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="c1">// goroutine: 하트비트 전송&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&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">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="p">}(&lt;/span>&lt;span class="nx">peerURL&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;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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">stopHeartbeat&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">close&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">heartbeatStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">// 모든 타이머와 하트비트를 정리하고 context를 취소하는 함수&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">shutdown&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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">if&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextElectionTimer&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextElectionTimer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">stopHeartbeat&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">cancel&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>&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">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">resetElectionTimeout&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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="nx">timeout&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Duration&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Float64&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 class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&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">// goroutine: 리더 선출 타임아웃 타이머 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">electionTimer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AfterFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">timeout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] ⏰ 하트비트 타임아웃 - 리더 없음 감지, 리더 선출 시작...\n&amp;#34;&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">nodeID&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentLeaderID&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&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">// 따라서 수락된 상태를 리셋하여 진행하며, 각 리더 선출은 사실상 새로운 Paxos 인스턴스가 되도록 함&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedValue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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>&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">scheduleNextElection&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextElectionTimer&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextElectionTimer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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="nx">delay&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Duration&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Float64&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 class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&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">// goroutine: 다음 리더 선출 타이머 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextElectionTimer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AfterFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">delay&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">hasLeader&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&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">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="k">if&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">hasLeader&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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>&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">// === HTTP 핸들러들 ===&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">handlePrepare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&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">// 요청 본문을 JSON -&amp;gt; PrepareRequest 타입으로 변환; 그리고 err 발생시 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="nx">PrepareRequest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;bad request&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&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>&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] PREPARE 요청 수신 / 제안 번호 #%d\n&amp;#34;&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">nodeID&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">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="c1">// 함수 내부에서만 쓰이므로 포인터(*) 없이 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">resp&lt;/span> &lt;span class="nx">PrepareResponse&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="nx">node&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">nil&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">ProposalNumber&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">intPtr&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">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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedValue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] → 제안 번호 #%d 약속\n&amp;#34;&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">nodeID&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">ProposalNumber&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Promise&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">AcceptedProposal&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">AcceptedValue&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nil&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] → 이미 약속한 제안 번호 #%d 거절\n&amp;#34;&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">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&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">resp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">PrepareResponse&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Promise&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">AcceptedProposal&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">AcceptedValue&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nil&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Header&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEncoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 응답 쓰기 실패: %v\n&amp;#34;&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">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">handleAccept&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&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="kd">var&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="nx">AcceptRequest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;bad request&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&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>&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] ACCEPT 요청 수신 / 제안 번호 #%d, 값: 노드 %d\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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&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">ProposalNumber&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">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="kd">var&lt;/span> &lt;span class="nx">resp&lt;/span> &lt;span class="nx">AcceptResponse&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="k">if&lt;/span> &lt;span class="nx">node&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">nil&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">ProposalNumber&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&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="c1">// 약속된 제안 번호 갱신&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">promisedProposal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">intPtr&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">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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">intPtr&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">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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acceptedValue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">intPtr&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">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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentLeaderID&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">intPtr&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">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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&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">ProposalNumber&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] → 수락! 새로운 리더: 노드 %d (제안 번호 %d)\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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&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">Value&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">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="c1">// 리더가 아니라면 하트비트 중지&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">shouldStopHeartbeat&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">Value&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="nx">shouldStopHeartbeat&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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">resp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Accepted&lt;/span>&lt;span class="p">:&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] → 이미 약속한 제안 번호 #%d 거절\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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">AcceptResponse&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Accepted&lt;/span>&lt;span class="p">:&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="nx">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Header&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEncoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 응답 쓰기 실패: %v\n&amp;#34;&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">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">handleHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&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="kd">var&lt;/span> &lt;span class="nx">msg&lt;/span> &lt;span class="nx">HeartbeatMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">msg&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;bad request&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&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>&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">shouldReset&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">msg&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">shouldReset&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="k">if&lt;/span> &lt;span class="nx">node&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">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentLeaderID&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">.&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 💓 새로운 리더로부터 하트비트 수신 / 리더: 노드 %d (턴 %d)\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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">msg&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">msg&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="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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentLeaderID&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">intPtr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">msg&lt;/span>&lt;span class="p">.&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Term&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">shouldReset&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Header&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEncoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">bool&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;ok&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">});&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 응답 쓰기 실패: %v\n&amp;#34;&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">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">handleStatus&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="o">:=&lt;/span> &lt;span class="nx">node&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">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentLeaderID&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nodeID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">StatusResponse&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 class="nx">node&lt;/span>&lt;span class="p">.&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="nx">CurrentLeaderID&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">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 class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&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 class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&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 class="nx">node&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">AcceptedValue&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">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="p">:&lt;/span> &lt;span class="nx">isLeader&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Header&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;application/json&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEncoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 응답 쓰기 실패: %v\n&amp;#34;&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">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">runNode&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="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Usage: go run . node &amp;lt;id&amp;gt; &amp;lt;port&amp;gt; &amp;lt;peers...&amp;gt;&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">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Atoi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&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="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Stderr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;invalid node ID %q: %v\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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="nx">port&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Atoi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&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="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Stderr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;invalid port %q: %v\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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="nx">peers&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Args&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">newPaxosNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 시작 with 통신해야 할 다른 노드의 URL 목록: %s\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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 다수 필요: %d 중 %d 이상\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="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">allNodes&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">majority&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">mux&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServeMux&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;POST /paxos/prepare&amp;#34;&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">handlePrepare&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;POST /paxos/accept&amp;#34;&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">handleAccept&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;POST /leader/heartbeat&amp;#34;&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">handleHeartbeat&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;GET /status&amp;#34;&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">handleStatus&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">server&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Server&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Addr&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;:%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Handler&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">mux&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">go&lt;/span> &lt;span class="kd">func&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 🚀 포트 %d에서 수신 중...\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&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="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">server&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ErrServerClosed&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 서버 에러: %v\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&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 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="nx">delay&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Duration&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Float64&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 class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 리더 조회 전 %dms 대기...\n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">delay&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Milliseconds&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="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AfterFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">delay&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">func&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">hasLeader&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&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">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">hasLeader&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 리더 없음, 리더 선출 시작...\n&amp;#34;&lt;/span>&lt;span class="p">,&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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>&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">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">sigChan&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Signal&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="nx">signal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Notify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sigChan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Interrupt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">sigChan&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 종료 중...\n&amp;#34;&lt;/span>&lt;span class="p">,&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">shutdown&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">shutdownCtx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">shutdownCancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">shutdownCancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">server&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Shutdown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">shutdownCtx&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="포인터를-이용한-nullable-필드int">포인터를 이용한 nullable 필드(*int)&lt;/h3>
&lt;p>이제와서 뒷북이긴 하지만, Go는 포인터를 사용합니다! 하하하하&amp;hellip; 그런데 정말 다행인 건 C, C++ 처럼 직접 메모리를 관리하거나 포인터 연산을 걱정할 필요는 없습니다. Go에서 포인터는 그저 모든 게 값인 세상에서 어떤 값을 참조로 사용할지 선택권을 주는 수단입니다. 포인터에 대한 자세한 설명은 다음으로 미루고, 일단 여기서는 nullable 필드를 눈 여겨 보도록 할까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">PaxosNode&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&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 class="c1">// 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">nodeID&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 노드 아이디&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">int&lt;/span> &lt;span class="c1">// 노드 포트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="c1">// 통신해야 할 다른 노드의 URL 목록&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">allNodes&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="c1">// 자신을 포함한 모든 노드의 URL 목록&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">majority&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 다수 필요 개수 (전체 노드 수 / 2 + 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="c1">// === Paxos Acceptor 상태 변수들 ===&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="kt">int&lt;/span> &lt;span class="c1">// 약속한 제안 번호 (nil = 아직 약속 없음)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acceptedProposal&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="c1">// 수락된 제안 번호 (nil = 아직 수락 없음)&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="kt">int&lt;/span> &lt;span class="c1">// 수락된 값(리더의 노드 아이디) (nil = 아직 수락 없음)&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 class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>똑같은 int 변수 지만 어떤 변수는 int로, 어떤 변수는 *int로 선언되었습니다. Go에서는 변수를 선언할 때 값을 선언하지 않으면 해당 변수 타입의 기본 값으로 초기화를 진행합니다. int의 경우 0이고, *int의 경우는 nil 이죠. 즉, 반드시 값이 존재하지만 초기값이 0인 변수와 &lt;code>값이 존재하지 않을 수도 있는 변수&lt;/code> 를 선언하는 거죠.&lt;/p>
&lt;h3 id="모든-고루틴이-종료될-때까지-기다리는-waitgroup">모든 고루틴이 종료될 때까지 기다리는 WaitGroup&lt;/h3>
&lt;p>자신을 포함해서 모든 노드에 prepare를 전송하는 코드를 살펴보죠.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] Phase 1: 모든 노드에 PREPARE 요청 전송...\n&amp;#34;&lt;/span>&lt;span class="p">,&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">prepareResult&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PrepareResponse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&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">results&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nx">prepareResult&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">wg&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">WaitGroup&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 요청 전송 ----&amp;gt; 모든 요청을 병렬로 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">allNodes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 고루틴 시작전 ----&amp;gt; WaitGroup 1 증가&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">idx&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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">defer&lt;/span> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// 고루틴 종료시 작업 완료 ----&amp;gt; WaitGroup 1 감소&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendPrepare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&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="nx">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">idx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">prepareResult&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">resp&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">err&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">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeURL&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">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wait&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// 모든 작업이 완료될 때 까지 대기&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>각 고루틴을 실행하기 전에 Add로 작업 수를 증가시키고, 병렬로 실행되는 고루틴이 종료될 때(defer) Done으로 작업 완료 처리합니다. 그리고 Wait으로 모든 작업이 완료될 때까지 대기하게 되죠. node.js의 &lt;code>Promise.allSettled&lt;/code> 와 닮았네요!&lt;/p>
&lt;h3 id="채널-select---채널을-위한-switch">채널 select -&amp;gt; 채널을 위한 switch&lt;/h3>
&lt;p>리더가 자신의 생존을 다른 노드에게 알리는 &lt;code>startHeartbeat&lt;/code> 함수를 보면, 여러 채널에서 수신되는 메세지에 따라 다른 동작을 수행하도록 하고 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">startHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">term&lt;/span> &lt;span class="kt">int&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 기존 하트비트 중지 (TOCTOU 경합 방지를 위해 인라인 처리)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nb">close&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">heartbeatStop&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="c1">// 데이터는 없이 신호만 수신할 채널 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">stopCh&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">stopCh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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">// goroutine: 각 채널의 메세지 수신 및 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&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초 간격 타이머 채널 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ticker&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewTicker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// goroutine 종료시 타이머 종료&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">ticker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Stop&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="c1">// 종료될 때까지 무한 반복하는 이벤트 루프&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 각 채널에서 수신되는 신호를 대기하며, 수신된 신호에 따라 동작 결정(select)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">select&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="o">&amp;lt;-&lt;/span>&lt;span class="nx">stopCh&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="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">():&lt;/span> &lt;span class="c1">// context 취소 신호&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">ticker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1">// 1초 간격 타이머 채널 신호&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;[노드 %d] 💓 하트비트 전송 (턴 %d)\n&amp;#34;&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">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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 이후 작업에 사용할 node.peers를 로컬 변수에 복사하여 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">peers&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&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">peers&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&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">peers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// lock 해제&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">peerURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&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="c1">// goroutine: 하트비트 전송&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span> &lt;span class="kt">string&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">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&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">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="p">}(&lt;/span>&lt;span class="nx">peerURL&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;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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">stopHeartbeat&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">close&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">heartbeatStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">heartbeatStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">nil&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">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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>1초 간격의 타이머를 설정하고&lt;/li>
&lt;li>무한 반복하는 이벤트 루프 시작&lt;/li>
&lt;li>stopCh에 메세지가 들어오면, 이벤트 루프 중지하고 고루틴 종료&lt;/li>
&lt;li>context 취소 메세지가 들어오면, 이벤트 루프 중지하고 고루틴 종료&lt;/li>
&lt;li>1초 간격 타이머 메세지가 들어오면, 나머지 노드에 하트비트 전송&lt;/li>
&lt;/ul>
&lt;p>그리고 그 아래 &lt;code>stopHeartbeat&lt;/code>를 보면 고루틴에서 수신하는 stopCh를 &lt;code>close&lt;/code> 함수로 닫아버리는데요, 이렇게 하면 모든 수신자에게 중지 신호가 전달됩니다.&lt;/p>
&lt;h3 id="contextcontext-로-우아한-종료graceful-shutdown">context.Context 로 우아한 종료(graceful shutdown)&lt;/h3>
&lt;p>새로운 노드를 실행하는 &lt;code>runNode&lt;/code>, &lt;code>newPaxosNode&lt;/code> 함수를 살펴보면, context와 해당 context를 중지시킬 수 있는 cancelFunc를 생성하여 이후 처리 과정에서 계속 사용하도록 전달하고 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">newPaxosNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&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">PaxosNode&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithCancel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// context를 중지 시킬 수 있는 cancelFunc 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">selfURL&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">allNodes&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">selfURL&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">peers&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">majority&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">allNodes&lt;/span>&lt;span class="p">)&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">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="k">return&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cancel&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">cancel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 노드에 cancelFunc 전달&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 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">port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">port&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="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="nx">allNodes&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">allNodes&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">majority&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">majority&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">httpClient&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Client&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&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="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="kd">func&lt;/span> &lt;span class="nf">runNode&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">newPaxosNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">nodeID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&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="o">...&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">// 모든 타이머와 하트비트를 정리하고 context를 취소하는 함수&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">node&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">PaxosNode&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">shutdown&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// 노드 중지시 cancelFunc를 실행하여 모든 작업 중지&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>shutdown&lt;/code> 함수를 보면, 노드의 cancelFunc를 실행하여 해당 context와 관련된 모든 작업(http 요청, 타이머, 고루틴 등)을 중지하도록 하고 있습니다.&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>Tucker의 Go 언어 프로그래밍, 공봉식, 골든래빗&lt;/li>
&lt;li>요즘 개발자를 위한 시스템 설계 수업. 디렌드라 신하, 테자스 초프라 저. 길벗&lt;/li>
&lt;li>CLAUDE&lt;/li>
&lt;/ul></content></item><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>go를 알아보자(1)</title><link>https://netscout.github.io/posts/go%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-1/</link><pubDate>Sat, 10 Jan 2026 10:03:50 +0900</pubDate><guid>https://netscout.github.io/posts/go%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-1/</guid><description>새해가 되고 뭔가 해야하지 않을까&amp;hellip; 싶은 불안감이 커지던 중 평소에 node.js의 다음은 go 아니겠나 하고 생각하던 게 떠올랐습니다. 물론 node.js는 대체 불가입니다. 풀스택 개발자에게는 내 고향같은 편안함이 있죠. DB 부터 백엔드, 프론트까지 모든 …</description><content>&lt;p>새해가 되고 뭔가 해야하지 않을까&amp;hellip; 싶은 불안감이 커지던 중 평소에 &lt;code>node.js의 다음은 go 아니겠나&lt;/code> 하고 생각하던 게 떠올랐습니다. 물론 node.js는 대체 불가입니다. 풀스택 개발자에게는 내 고향같은 편안함이 있죠. DB 부터 백엔드, 프론트까지 모든 스택을 하나로 다룰 수 있고 Typescript를 사용하면 정적 타입의 편안함까지 느낄 수 있죠.(물론 타입이 너무 복잡해서 아직도 잘 모릅니다&amp;hellip; 하하하하&amp;hellip;)&lt;/p>
&lt;p>그런데 아무래도 만드는 제품을 on-premiss 솔루션화 하려다 보면, node.js 는 약간 어색함이 있습니다. 어쨌든 스크립트니까 코드가 그대로 노출된다는 단점이 있죠. 난독화를 하긴 하지만, 여전히 불안한 느낌은 있습니다. 물론 바이너리 역시도 100% 안전한 건 없지만, 뭐 그래도 말이죠.&lt;/p>
&lt;p>하여간 이제 아주 천천히, 하던대로 go를 알아볼까 합니다. 평소에 많이 하던 코드를 go로 간단하게 작성해보면서 말이죠!&lt;/p>
&lt;h2 id="mongodb야-반갑다">mongodb야 반갑다!&lt;/h2>
&lt;p>예, mongodb를 이용할 겁니다. 그리고 아주 간단하게 도큐먼트를 하나 집어넣고, 집어 넣은 도큐먼트를 다시 꺼내오는 작업을 할 겁니다.&lt;/p>
&lt;h3 id="go를-설치하자">go를 설치하자.&lt;/h3>
&lt;p>현재 go 최신버전은 1.25.5 입니다. 저는 맥에서 작성중이기 때문에 homebrew로 그냥 설치하면 됩니다.&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; brew install go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go version go1.25.5 darwin/arm64
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="프로젝트-셋업">프로젝트 셋업&lt;/h3>
&lt;p>그리고 적당한 곳에 폴더를 생성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;gt; mkdir mongodb-example
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; cd mongodb-example
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># go 모듈 초기화
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; go mod init mongodb-example
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 가장 좋아하는 ide를 띄웁니다. 그러면 go.mod 파일이 하나 보일겁니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">module test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go 1.25.5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>모듈 이름과 go 버전이 보이는데요, 이 파일은 node.js의 package.json 같은 역할을 합니다. 일단 이번에 우리가 사용할 모듈을 설치해볼까요?&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; go get go.mongodb.org/mongo-driver/v2/mongo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added github.com/golang/snappy v1.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added github.com/klauspost/compress v1.16.7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added github.com/xdg-go/pbkdf2 v1.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added github.com/xdg-go/scram v1.1.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added github.com/xdg-go/stringprep v1.0.4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added go.mongodb.org/mongo-driver/v2 v2.4.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added golang.org/x/crypto v0.33.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added golang.org/x/sync v0.11.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go: added golang.org/x/text v0.22.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>자 그리고 다시 go.mod 파일을 보면!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">module test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go 1.25.5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/golang/snappy v1.0.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/klauspost/compress v1.16.7 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/xdg-go/pbkdf2 v1.0.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/xdg-go/scram v1.1.2 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/xdg-go/stringprep v1.0.4 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go.mongodb.org/mongo-driver/v2 v2.4.1 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> golang.org/x/crypto v0.33.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> golang.org/x/sync v0.11.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> golang.org/x/text v0.22.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>설치된 패키지 목록이 보입니다. package.json 이랑 진짜 비슷하죠? 근데 저 많은 &lt;code>// indirect&lt;/code> 는 뭘까요?&lt;/p>
&lt;p>그러니까 직접적으로 사용하는 모듈(&lt;code>go.mongodb.org/mongo-driver/v2&lt;/code>)가 간접적으로 사용하는 모듈이라는 뜻입니다. 근데 왜 &lt;code>go.mongodb.org/mongo-driver/v2&lt;/code> 도 &lt;code>// indirect&lt;/code> 냐고요? 아직 이 모듈을 사용하는 코드가 없기 때문입니다!&lt;/p>
&lt;h3 id="그럼-코드를-작성해보자">그럼 코드를 작성해보자.&lt;/h3>
&lt;p>main.go를 작성할 차례입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;time&amp;#34;&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="s">&amp;#34;go.mongodb.org/mongo-driver/v2/bson&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;go.mongodb.org/mongo-driver/v2/mongo&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;go.mongodb.org/mongo-driver/v2/mongo/options&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>&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">func&lt;/span> &lt;span class="nf">main&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"> * 컨텍스트에 10초 동안 아무런 신호가 전달되지 않으면 컨텍스트를 취소하고 프로그램을 종료
&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="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// defer는 프로그램이 종료될 때 실행되도록 지연시킨다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">cancel&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">// mongodb에 연결&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">mongo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Connect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Client&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">ApplyURI&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;mongodb://localhost:27017&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// log.Fatal은 오류가 발생하면 프로그램을 종료하고 오류 메시지를 출력&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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="c1">// 프로그램이 종료될 때 연결을 끊기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Disconnect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&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">// mongodb 서버에 연결 확인, ping 성공시 컨텍스트가 취소되지 않고 계속 실행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Ping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Connected to MongoDB&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">// users 컬렉션을 가져옴&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">collection&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;testdb&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;users&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">// bson.M은 자바스크립트 객체와 유사한 형태의 데이터를 저장하는 Binary JSON 형식&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">user&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bson&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">M&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;John Doe&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="s">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;john.doe@example.com&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="s">&amp;#34;age&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">30&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="nx">insertResult&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">collection&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">InsertOne&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;User inserted with ID:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">insertResult&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">InsertedID&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="nx">filter&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bson&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">M&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;john.doe@example.com&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">var&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="nx">bson&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">M&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">collection&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">FindOne&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">filter&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&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="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;User found:&amp;#34;&lt;/span>&lt;span class="p">,&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;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>코드를 보면 Go의 특징적인 에러 처리 패턴을 볼 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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;code>try-catch&lt;/code> 와는 다르게, Go는 에러를 명시적으로 반환값으로 처리합니다. 이게 처음엔 좀 번거로워 보이지만, 에러가 발생할 수 있는 지점을 명확하게 알 수 있다는 장점이 있죠. 예외가 여러개 중첩 된 경우 예외가 어디서 발생한 건지 어렵게 찾을 필요가 없습니다!&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>그리고 코드를 실행할 때마다 같은 데이터가 계속 쌓이는 게 싫다면, 삽입 전에 기존 데이터를 삭제하는 코드를 추가할 수도 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">collection&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">DeleteMany&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bson&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">M&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;john.doe@example.com&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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;/blockquote>
&lt;p>그리고 &lt;code>go mod tidy&lt;/code> 를 실행한 뒤 다시 go.mod 파일을 보면 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">module test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go 1.25.5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require go.mongodb.org/mongo-driver/v2 v2.4.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">require (
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/golang/snappy v1.0.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/klauspost/compress v1.16.7 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/xdg-go/pbkdf2 v1.0.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/xdg-go/scram v1.1.2 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/xdg-go/stringprep v1.0.4 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> golang.org/x/crypto v0.33.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> golang.org/x/sync v0.11.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> golang.org/x/text v0.22.0 // indirect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>직접 임포트해서 사용하는 &lt;code>go.mongodb.org/mongo-driver/v2&lt;/code> 모듈이 &lt;code>// indirect&lt;/code> 에서 독립했습니다!&lt;/p>
&lt;h3 id="mongodb를-띄우자">mongodb를 띄우자.&lt;/h3>
&lt;p>간단한 &lt;code>docker-compose.yml&lt;/code> 을 사용해서 mongodb를 띄울 겁니다. 프로젝트 루트 폴더(mongodb-example)에 &lt;code>docker-compose.yml&lt;/code> 파일을 생성해주세요.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">mongo:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;27017:27017&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">mongodb_data:/data/db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">mongodb_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; docker-compose up -d
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="이제-결과를-보자">이제 결과를 보자&lt;/h3>
&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; go run main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connected to MongoDB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">User inserted with ID: ObjectID&lt;span class="o">(&lt;/span>&lt;span class="s2">&amp;#34;6961a3ea4f62d4037b2611bb&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">User found: &lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;_id&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$oid&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;6961a3ea4f62d4037b2611bb&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;John Doe&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;john.doe@example.com&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;age&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$numberInt&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;30&amp;#34;&lt;/span>&lt;span class="o">}}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>예&amp;hellip; 이게 끝입니다. 처음이니까요. 하하하하하하.&lt;/p>
&lt;h3 id="실습-환경-정리하기">실습 환경 정리하기&lt;/h3>
&lt;p>실습이 끝났으면 깔끔하게 정리해줍시다. MongoDB 컨테이너를 종료하고 데이터까지 삭제하려면 다음 명령어를 실행하면 됩니다.&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; docker-compose down -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>-v&lt;/code> 옵션은 볼륨까지 삭제한다는 의미입니다. 실습용 데이터니까 깔끔하게 지워버리는 게 속이 시원하죠!&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://pkg.go.dev/go.mongodb.org/mongo-driver/v2#section-readme">MongoDB Go Driver&lt;/a>&lt;/li>
&lt;li>Perplexity와 Claude&lt;/li>
&lt;/ul></content></item><item><title>Typescript: import attributes</title><link>https://netscout.github.io/posts/typescript-import-attributes/</link><pubDate>Sat, 20 Sep 2025 00:55:52 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-import-attributes/</guid><description>최근에 틈이 날 때 마다 이전 타입스크립트 버전에 대한 릴리즈 노트를 읽어보고 있습니다. 리액트, Nest.js로 개발하면서 자연스럽게 타입스크립트를 몇 년간 사용해왔지만&amp;hellip; 사실 제대로 아는 게 없었기 때문이죠. 타입스크립트차 처음 소개된 게 2012년이니까 벌써 …</description><content>&lt;h2 id="개요">개요&lt;/h2>
&lt;p>최근에 틈이 날 때 마다 이전 타입스크립트 버전에 대한 릴리즈 노트를 읽어보고 있습니다. 리액트, Nest.js로 개발하면서 자연스럽게 타입스크립트를 몇 년간 사용해왔지만&amp;hellip; 사실 제대로 아는 게 없었기 때문이죠. 타입스크립트차 처음 소개된 게 2012년이니까 벌써 13년이나 지났습니다. 긴 시간 동안 참 많은 일이 있었고 단순히 언어를 사용하는 것 만으로는 제대로 이해할 수 없겠죠.&lt;/p>
&lt;p>그러던 중에 &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#import-attributes">5.3 버전에서 &lt;code>Import Attributes&lt;/code> 지원이 추가되었다는 걸&lt;/a> 봤습니다. 내용은 짧았지만 이게 무슨 소리인지 이해하는 데는 시간이 좀 걸렸습니다. 이제 부터 그 이야기를 좀 해볼까 합니다.&lt;/p>
&lt;h2 id="나도-모르게-자연스럽게-쓰던-코드">나도 모르게 자연스럽게 쓰던 코드&lt;/h2>
&lt;p>예전에 Node.js로 코드를 짜다 보면 뭔가 데이터를 구성하고 가공하는 코드를 많이 짜곤했습니다. 그럴때 json 파일을 가져오고 활용하는 경우가 많았죠.&lt;/p>
&lt;p>예를 들어서 다음과 같은 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">// src/import-attribute/src/test.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;test&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;test&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;isActive&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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">// src/import-attribute/src/import-json.js
&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">test&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;./test.json&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="nx">test&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 json 파일을 가져오면, Node.js 런타임이 파일을 읽어와서 변환까지 처리해줍니다. 그리고 가져온 데이터 객체의 타입을 추론해서 코드 자동 완성까지도 지원해줬죠. Node.js 런타임이 변환까지 처리해주기 때문에 json 파일의 형식이 잘못되어 있다면, require 구문에서 오류가 발생합니다.&lt;/p>
&lt;h2 id="typescript-에서는">Typescript 에서는&amp;hellip;?&lt;/h2>
&lt;p>위 코드를 Typescript로 작성해보겠습니다.&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="c1">// src/import-attribute/src/import-json.ts
&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="nx">test&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./test.json&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="nx">test&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 코드를 작성하자 마자, 다음과 같이 오류가 발생합니다.&lt;/p>
&lt;p align="center">
&lt;img src="./01.png" alt="error" />
&lt;/p>
&lt;p>타입 스크립트에서 json 파일을 임포트 하려면 &lt;a href="https://www.typescriptlang.org/tsconfig/#resolveJsonModule">&lt;code>resolveJsonModule&lt;/code> 옵션&lt;/a> 을 true로 설정해야 합니다. tsconfig.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">// tsconfig.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;compilerOptions&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;resolveJsonModule&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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;p align="center">
&lt;img src="./02.png" alt="error" />
&lt;/p>
&lt;p>그러니까 export default 없이 정의된 json 파일을 임포트 해야 하니 &lt;a href="https://www.typescriptlang.org/tsconfig/#allowSyntheticDefaultImports">&lt;code>allowSyntheticDefaultImports&lt;/code> 옵션&lt;/a> 을 true로 설정해야 합니다. tsconfig.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">// tsconfig.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;compilerOptions&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;resolveJsonModule&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;allowSyntheticDefaultImports&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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;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; npx tsc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; node import-json.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">undefined
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>json 파일이 제대로 임포트되지 않고 undefined가 출력됩니다. 컴파일된 코드를 확인해보면 다음과 같습니다.&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">// src/import-json.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="s2">&amp;#34;use strict&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="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">defineProperty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;__esModule&amp;#34;&lt;/span>&lt;span class="p">,&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">true&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">test_json_1&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;./test.json&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="nx">test_json_1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">default&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>import가 아닌 require 구문을 사용하도록 컴파일이 되었네요. 현재 타입스크립트 버전(5.8.3)에서 지원하는 가장 최신의 javascript 문법을 사용하도록 tsconfig.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">// tsconfig.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;compilerOptions&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;resolveJsonModule&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;esnext&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;moduleResolution&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;node&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;allowSyntheticDefaultImports&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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>target&lt;/code>에는 가장 최신의 javascript 문법을 사용하도록 &lt;code>esnext&lt;/code>를 사용하도록 지정했습니다. 그리고 변경된 &lt;code>target&lt;/code>에서 &lt;code>resolveJsonModule&lt;/code> 옵션을 같이 사용할 수 있도록 &lt;code>moduleResolution&lt;/code>을 &lt;code>node&lt;/code>로 지정했습니다.&lt;/p>
&lt;blockquote>
&lt;p>생성되는 코드에서 import 구문을 사용하기 위해서 &lt;code>package.json&lt;/code>에서 &lt;code>type&lt;/code>을 &lt;code>module&lt;/code>로 지정해줍니다.&lt;/p>&lt;/blockquote>
&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; npx tsc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; node import-json.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">node:internal/modules/esm/assert:88
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> throw new ERR_IMPORT_ATTRIBUTE_MISSING&lt;span class="o">(&lt;/span>url, &lt;span class="s1">&amp;#39;type&amp;#39;&lt;/span>, validType&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>&lt;/span>&lt;span class="line">&lt;span class="cl">TypeError &lt;span class="o">[&lt;/span>ERR_IMPORT_ATTRIBUTE_MISSING&lt;span class="o">]&lt;/span>: Module &lt;span class="s2">&amp;#34;file:///Users/onlifecoding/src/test/typescript-studies/src/import-attribute/src/test.json&amp;#34;&lt;/span> needs an import attribute of &lt;span class="s2">&amp;#34;type: json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at validateAttributes &lt;span class="o">(&lt;/span>node:internal/modules/esm/assert:88:15&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at defaultLoad &lt;span class="o">(&lt;/span>node:internal/modules/esm/load:130:3&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at async ModuleLoader.loadAndTranslate &lt;span class="o">(&lt;/span>node:internal/modules/esm/loader:514:32&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_IMPORT_ATTRIBUTE_MISSING&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Node.js v22.14.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>뭔가 복잡해보이지만 결론은 &lt;code>type: json&lt;/code> 이라는 &lt;code>import attribute&lt;/code>를 지정해야 한다는 오류입니다.&lt;/p>
&lt;p>참고로 만약 Node.js 버전이 20 또는 18 이라면 다음과 같은 오류가 발생합니다.&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">node:internal/errors:497
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ErrorCaptureStackTrace&lt;span class="o">(&lt;/span>err&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>&lt;/span>&lt;span class="line">&lt;span class="cl">TypeError &lt;span class="o">[&lt;/span>ERR_IMPORT_ASSERTION_TYPE_MISSING&lt;span class="o">]&lt;/span>: Module &lt;span class="s2">&amp;#34;file:///Users/onlifecoding/src/test/typescript-studies/src/import-attribute/src/test.json&amp;#34;&lt;/span> needs an import assertion of &lt;span class="nb">type&lt;/span> &lt;span class="s2">&amp;#34;json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new NodeError &lt;span class="o">(&lt;/span>node:internal/errors:406:5&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at validateAssertions &lt;span class="o">(&lt;/span>node:internal/modules/esm/assert:94:15&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at defaultLoad &lt;span class="o">(&lt;/span>node:internal/modules/esm/load:122:3&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at ModuleLoader.load &lt;span class="o">(&lt;/span>node:internal/modules/esm/loader:396:13&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at ModuleLoader.moduleProvider &lt;span class="o">(&lt;/span>node:internal/modules/esm/loader:278:56&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new ModuleJob &lt;span class="o">(&lt;/span>node:internal/modules/esm/module_job:65:26&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at &lt;span class="c1">#createModuleJob (node:internal/modules/esm/loader:290:17)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at ModuleLoader.getJobFromResolveResult &lt;span class="o">(&lt;/span>node:internal/modules/esm/loader:248:34&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at ModuleLoader.getModuleJob &lt;span class="o">(&lt;/span>node:internal/modules/esm/loader:229:17&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at async ModuleWrap.&amp;lt;anonymous&amp;gt; &lt;span class="o">(&lt;/span>node:internal/modules/esm/module_job:85:21&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_IMPORT_ASSERTION_TYPE_MISSING&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;h2 id="import-attribute">import attribute&lt;/h2>
&lt;p>위 오류를 해결하기 위해서 임포트 구문에 &lt;code>import attribute&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="c1">// src/import-attribute/src/import-json.ts
&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="nx">test&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./test.json&amp;#34;&lt;/span> &lt;span class="kd">with&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="kr">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;json&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="nx">test&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그런데 여기서 또 하나의 오류가 발생합니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>가져오기 특성은 '--module' 옵션이 'esnext', 'node18', 'node20', 'nodenext' 또는 'preserve'로 설정된 경우에만 지원됩니다.&lt;/code>&lt;/p>&lt;/blockquote>
&lt;p>그러니까, tsconfig.json에서 &lt;code>module&lt;/code> 옵션을 &lt;code>esnext&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;resolveJsonModule&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// JSON을 임포트 할 수 있도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nt">&amp;#34;module&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;esnext&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// import attribute를 사용하기 위해서 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nt">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;esnext&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;moduleResolution&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;node&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;allowSyntheticDefaultImports&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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;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; npx tsc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; node import-json.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span> name: &lt;span class="s1">&amp;#39;test&amp;#39;&lt;/span>, description: &lt;span class="s1">&amp;#39;test&amp;#39;&lt;/span>, isActive: &lt;span class="nb">true&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>json 파일 하나를 임포트하고 실행하기 위해서 참 먼 길을 돌아왔습니다. 도대체 왜 이렇게 복잡해진 걸까요? 이 모든 건 다 &lt;code>보안&lt;/code> 때문입니다.&lt;/p>
&lt;p>위와 같이 json 파일을 임포트할 때, 확장자 만을 확인하고 파일의 내용이 json 인지는 확인하지 않는 문제가 있었고 이게 보안상 아주 취약한 문제가 될 수 있었습니다. 확장자는 json 파일이지만 실제로는 뭔가 이상한 동작을 수행하는 스크립트를 포함하고 있을 수도 있었죠.&lt;/p>
&lt;p>그래서 &lt;code>import attribute&lt;/code>를 사용해서 인터넷의 파일을 로드할 때, 확장자 대신에 파일의 MIME 타입이나 CSP 등의 정책에 맞는지 등을 확인할 수 있도록 처리합니다. Node.js에서도 마찬가지 입니다. 누군가 임포트되는 json 파일을 바꿔치기 한다면 동일한 문제가 발생할 수도 있겠죠.&lt;/p>
&lt;h2 id="import-assertion-attribute-헷갈리네">import assertion? attribute? 헷갈리네!&lt;/h2>
&lt;p>이 기능은 최초 제안된 이후 이름이 여러번 바뀌었는데요, 그 &lt;a href="https://github.com/tc39/proposal-import-attributes?tab=readme-ov-file#history">과정&lt;/a>을 살펴보면 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>2019년 12월: &lt;code>module attributes&lt;/code> 로 제안되어 Stage 1 승인&lt;/li>
&lt;li>2020년 6월: &lt;code>module attributes&lt;/code> Stage 2 승인&lt;/li>
&lt;li>2020년 9월: 이름이 &lt;code>import assertions&lt;/code>로 변경되면 Stage 3 승인&lt;/li>
&lt;li>2021~2022: &lt;code>import assertions&lt;/code> 및 &lt;a href="https://github.com/tc39/proposal-json-modules">JSON Modules 제안&lt;/a> 을 Chrome, Node.js, Deno에서 구현함.&lt;/li>
&lt;li>2023년 1월: HTML에서 javascript가 아닌 모듈에 대한 처리에 호환되지 않는 부분이 있어서 &lt;code>import assertions&lt;/code>는 Stage 2로 돌아감&lt;/li>
&lt;li>2023년 3월: 이름이 &lt;code>import attributes&lt;/code>로 변경되면 Stage 3 으로 다시 승인됨&lt;/li>
&lt;/ul>
&lt;p>그리고 현재 &lt;code>import attribute&lt;/code>는 Stage 4(Finished) 단계로 공식 스펙에 편입될 준비를 모두 마친 상태입니다. 앞서 Node.js 20과 18 코드를 실행했을 때 오류 메세지가 &lt;code>needs an import assertion of type &amp;quot;json&amp;quot;&lt;/code> 이라고 나왔던 건 바로 이런 이유 때문이었죠.&lt;/p>
&lt;h2 id="타입-스크립트는-문법을-허용할-뿐-아무-책임을-지지-않습니다">타입 스크립트는 문법을 허용할 뿐 아무 책임을 지지 않습니다!&lt;/h2>
&lt;p>릴리즈 노트에서도 설명하고 있지만, 타입스크립트는 그저 이 구문을 사용할 수 있게 지원하는 것 뿐이며 어떤 해석도 하지 않습니다. 왜냐면 이 구문을 어떻게 처리할지는 각 브라우저 및 런타임에 달려있기 때문이죠. 앞서 보신것 처럼 Node.js는 엄격하게 이 구문을 통해 json 파일의 타입을 체크하고 있습니다.&lt;/p>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#import-attributes">Import Attributes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/tsconfig/#resolveJsonModule">resolveJsonModule&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/tsconfig/#allowSyntheticDefaultImports">allowSyntheticDefaultImports&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/tc39/proposal-import-attributes">tc39/proposal-import-attributes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/tc39/proposal-json-modules">tc39/proposal-json-modules&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Typescript: Decorator Metadata</title><link>https://netscout.github.io/posts/typescript-decorator-metadata/</link><pubDate>Sat, 06 Sep 2025 23:42:51 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-decorator-metadata/</guid><description>Typescript는 5.0 부터 ECMAScript의 표준에 발맞춘 Decorator를 지원하고 있습니다. 얼마전에 이와 관련된 글을 쓴적이 있었죠! 그런데 ECMAScript의 표준에는 이 데코레이터를 좀 더 강력하게 사용할 수 있는 방법도 같이 제시되고 …</description><content>&lt;h2 id="개요">개요&lt;/h2>
&lt;p>Typescript는 5.0 부터 ECMAScript의 표준에 발맞춘 Decorator를 지원하고 있습니다. 얼마전에 &lt;a href="https://raw.githubusercontent.com/netscout/typescript-studies/main/src/decorator/README.md">이와 관련된 글&lt;/a>을 쓴적이 있었죠! 그런데 ECMAScript의 표준에는 이 &lt;a href="https://github.com/tc39/proposal-decorator-metadata">데코레이터를 좀 더 강력하게 사용할 수 있는 방법&lt;/a>도 같이 제시되고 있습니다. 데코레이터의 메타데이터를 이용해서 데코레이터가 감싸고 있는 클래스의 정보를 추출하는 메타 프로그래밍을 지원합니다. Typescript 역시 5.2 버전 부터 이 기능을 지원하고 있습니다.&lt;/p>
&lt;h2 id="아주-간단한-예제">아주 간단한 예제&lt;/h2>
&lt;p>그럼 이 데코레이터 메타데이터가 뭔지 간단한 예제를 통해서 확인해보겠습니다. 우선 데코레이터 메타데이터를 사용하기 위해서는 다음과 같이 &lt;code>tsconfig.json&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;es2022&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;lib&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;es2022&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;esnext.decorators&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;dom&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;experimentalDecorators&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;useDefineForClassFields&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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가 데코레이터 메타데이터를 지원하지 않기 때문에 다음과 같이 코드에 polyfill을 추가해야 합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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 class="nx">Symbol&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">metadata&lt;/span> &lt;span class="o">??=&lt;/span> &lt;span class="nx">Symbol&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Symbol.metadata&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;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/decorator-metadata/src/simple-decorator-metadata.ts
&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">// Symbol.metadata polyfill
&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 class="nx">Symbol&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">metadata&lt;/span> &lt;span class="o">??=&lt;/span> &lt;span class="nx">Symbol&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Symbol.metadata&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">// 데코레이터 Context 인터페이스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">interface&lt;/span> &lt;span class="nx">Context&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&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="nx">metadata&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">PropertyKey&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">unknown&lt;/span>&lt;span class="p">&amp;gt;;&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 _target 데코레이터가 적용된 클래스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param context 데코레이터 컨텍스트
&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">setMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">_target&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">Context&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">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">]&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">class&lt;/span> &lt;span class="nx">SomeClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@setMetadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">foo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">123&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">@setMetadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">accessor&lt;/span> &lt;span class="nx">bar&lt;/span> &lt;span class="o">=&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@setMetadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">baz() {&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">ourMetadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">SomeClass&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ourMetadata&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 출력: { &amp;#34;bar&amp;#34;: true, &amp;#34;baz&amp;#34;: true, &amp;#34;foo&amp;#34;: true }
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 코드는 말이죠:&lt;/p>
&lt;ul>
&lt;li>코드 최상단에 &lt;code>Symbol.metadata&lt;/code>를 지원하기 위한 polyfill을 추가했습니다.&lt;/li>
&lt;li>setMetadata 데코레이터 함수를 정의했습니다.&lt;/li>
&lt;li>setMetadata를 호출하면 데코레이터 메타데이터에 &amp;lsquo;해당 필드의 이름&amp;rsquo; = true 를 추가합니다.&lt;/li>
&lt;li>데코레이터 메타데이터는 &lt;code>각 클래스 별로 고유한 객체&lt;/code>이며, &lt;code>Symbol.metadata&lt;/code> 을 통해 접근할 수 있습니다.&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; npx ts-node ./src/simple-decorator-metadata.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;bar&amp;#34;&lt;/span>:true,&lt;span class="s2">&amp;#34;baz&amp;#34;&lt;/span>:true,&lt;span class="s2">&amp;#34;foo&amp;#34;&lt;/span>:true&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SomeClass의 foo, bar, baz 필드에 setMetadata 데코레이터가 적용되었기 때문에 데코레이터 메타데이터에 foo, bar, baz 필드의 이름이 추가된 걸 확인할 수 있습니다.&lt;/p>
&lt;p>이제 조금 더 실용적인 예제를 살펴볼까요?&lt;/p>
&lt;h2 id="의존성-주입dependency-injection-예제">의존성 주입(Dependency Injection) 예제&lt;/h2>
&lt;p>일단 데코레이터를 사용해서 의존성 주입(Dependency Injection)을 구현해보겠습니다.&lt;/p>
&lt;blockquote>
&lt;p>이 예제는 테스트를 위한 구현이기 때문에 싱글턴 패턴, 순환 참조 탐지 등의 기능을 구현하지 않습니다. 실제 서비스에서는 &lt;a href="https://github.com/inversify/InversifyJS">InversifyJS&lt;/a> 같은 의존성 주입 라이브러리를 사용하는 것이 좋습니다.&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/decorator-metadata/src/dependency-injection.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * Injectable 데코레이터
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 클래스를 DI 컨테이너에 등록합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param token 클래스 이름
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 데코레이터 함수
&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">Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">token?&lt;/span>: &lt;span class="kt">string&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">target&lt;/span>: &lt;span class="kt">any&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">registry&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">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// token: Logger, target.name: LoggerService, target: LoggerService 클래스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">registry&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">token&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">target&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">// 환경에 상관없이 사용할 수 있도록 globalThis에 글로벌 컨테이너를 설정합니다.
&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 class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&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">();&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">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">registry&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="p">(&lt;/span>&lt;span class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&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">key&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 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"> * Inject 데코레이터
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 클래스 필드에 DI로 객체의 인스턴스를 주입합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param token 클래스 이름
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 데코레이터 함수
&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">Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">token&lt;/span>: &lt;span class="kt">string&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">target&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassFieldDecoratorContext&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">initialValue&lt;/span>: &lt;span class="kt">any&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">// Inject 데코레이터를 전달되는 token(ex: Logger)에 해당하는 서비스 클래스를 찾습니다.
&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">container&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&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">serviceClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">container&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">token&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="o">!&lt;/span>&lt;span class="nx">serviceClass&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`컨테이너에서 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">token&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="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">serviceClass&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * LoggerService 클래스
&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">@Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Logger&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">class&lt;/span> &lt;span class="nx">LoggerService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&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;LoggerService constructor&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">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">message&lt;/span>: &lt;span class="kt">string&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">`[LOG] &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toISOString&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">message&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * DatabaseService 클래스
&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">@Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Database&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">class&lt;/span> &lt;span class="nx">DatabaseService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Logger&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">private&lt;/span> &lt;span class="nx">logger&lt;/span>&lt;span class="o">!:&lt;/span> &lt;span class="nx">LoggerService&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">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sql&lt;/span>: &lt;span class="kt">string&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">logger&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">`Executing: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">sql&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="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">rows&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="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"> * UserService 클래스
&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">@Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;UserService&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">class&lt;/span> &lt;span class="nx">UserService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Database&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">private&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="o">!:&lt;/span> &lt;span class="nx">DatabaseService&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">@Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Logger&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">private&lt;/span> &lt;span class="nx">logger&lt;/span>&lt;span class="o">!:&lt;/span> &lt;span class="nx">LoggerService&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">getUser&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">logger&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">`Getting user &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="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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`SELECT * FROM users WHERE id = &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="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">const&lt;/span> &lt;span class="nx">userService&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">UserService&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">userService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>조금 복잡하게 느껴집니다&amp;hellip; 우선 실행 순서를 정리해보면 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">graph TB
subgraph ProcessFlow[&amp;#34;🔄 처리 흐름&amp;#34;]
Step1[&amp;#34;1️⃣ 클래스 정의 시&amp;lt;br/&amp;gt;데코레이터 실행&amp;#34;]
Step2[&amp;#34;2️⃣ 컨테이너에&amp;lt;br/&amp;gt;토큰-클래스 매핑 저장&amp;#34;]
Step3[&amp;#34;3️⃣ 인스턴스 생성 시&amp;lt;br/&amp;gt;의존성 자동 해결&amp;#34;]
Step4[&amp;#34;4️⃣ 재귀적으로&amp;lt;br/&amp;gt;모든 의존성 주입&amp;#34;]
end
%% 처리 흐름
Step1 --&amp;gt; Step2
Step2 --&amp;gt; Step3
Step3 --&amp;gt; Step4&lt;/pre>&lt;ol>
&lt;li>클래스에 정의된 Injectable 데코레이터에 token(ex: Logger)과 클래스(ex: LoggerService)가 전달됩니다.&lt;/li>
&lt;li>Injectable 데코레이터는 글로벌 DI 컨테이너에 token-클래스 매핑을 저장합니다.&lt;/li>
&lt;li>그 다음 Inject 데코레이터에 token(ex: Logger)이 전달되면, 글로벌 DI 컨테이너에서 token에 해당하는 클래스를 찾아서 인스턴스를 생성하여 주입합니다.&lt;/li>
&lt;li>의존성 주입은 제귀적으로 발생합니다.(UserService -&amp;gt; DatabaseService -&amp;gt; LoggerService)&lt;/li>
&lt;/ol>
&lt;p>전체 구조를 도식화 해보면 다음과 같습니다.&lt;/p>
&lt;pre class="mermaid">graph TB
subgraph GlobalContainer[&amp;#34;🌐 글로벌 DI 컨테이너&amp;#34;]
DIMap[&amp;#34;globalThis.diContainer&amp;lt;br/&amp;gt;Map&amp;amp;lt;string, Constructor&amp;amp;gt;&amp;#34;]
TokenLogger[&amp;#34;&amp;#39;Logger&amp;#39; ➜ LoggerService&amp;#34;]
TokenDatabase[&amp;#34;&amp;#39;Database&amp;#39; ➜ DatabaseService&amp;#34;]
TokenUser[&amp;#34;&amp;#39;UserService&amp;#39; ➜ UserService&amp;#34;]
end
subgraph DecoratorSystem[&amp;#34;🏷️ 데코레이터&amp;#34;]
InjectableDecorator[&amp;#34;@Injectable(token?)&amp;lt;br/&amp;gt;📝 클래스 등록 데코레이터&amp;lt;br/&amp;gt;• 토큰 생성&amp;lt;br/&amp;gt;• 컨테이너 저장&amp;#34;]
InjectDecorator[&amp;#34;@Inject(token)&amp;lt;br/&amp;gt;💉 의존성 주입 데코레이터&amp;lt;br/&amp;gt;• 컨테이너 조회&amp;lt;br/&amp;gt;• 인스턴스 생성&amp;#34;]
end
subgraph ServiceLayer[&amp;#34;🔧 서비스 계층&amp;#34;]
Logger[&amp;#34;🟢 LoggerService&amp;lt;br/&amp;gt;@Injectable(&amp;#39;Logger&amp;#39;)&amp;lt;br/&amp;gt;────────────&amp;lt;br/&amp;gt;+ constructor()&amp;lt;br/&amp;gt;+ log(message: string)&amp;#34;]
Database[&amp;#34;🟡 DatabaseService&amp;lt;br/&amp;gt;@Injectable(&amp;#39;Database&amp;#39;)&amp;lt;br/&amp;gt;@Inject(&amp;#39;Logger&amp;#39;) logger&amp;lt;br/&amp;gt;────────────&amp;lt;br/&amp;gt;+ query(sql: string)&amp;#34;]
User[&amp;#34;🔴 UserService&amp;lt;br/&amp;gt;@Injectable(&amp;#39;UserService&amp;#39;)&amp;lt;br/&amp;gt;@Inject(&amp;#39;Database&amp;#39;) db&amp;lt;br/&amp;gt;@Inject(&amp;#39;Logger&amp;#39;) logger&amp;lt;br/&amp;gt;────────────&amp;lt;br/&amp;gt;+ getUser(id: number)&amp;#34;]
end
subgraph RuntimeExecution[&amp;#34;⚡ 실행 시점&amp;#34;]
ClientCode[&amp;#34;📱 클라이언트 코드&amp;lt;br/&amp;gt;const userService = new UserService()&amp;lt;br/&amp;gt;userService.getUser(123)&amp;#34;]
end
%% 등록 과정
InjectableDecorator -.-&amp;gt;|&amp;#34;🏷️ 클래스에 데코레이터 추가&amp;lt;br/&amp;gt;토큰 생성 및 등록 준비&amp;#34;| Logger
InjectableDecorator -.-&amp;gt;|&amp;#34;🏷️ 클래스에 데코레이터 추가&amp;lt;br/&amp;gt;토큰 생성 및 등록 준비&amp;#34;| Database
InjectableDecorator -.-&amp;gt;|&amp;#34;🏷️ 클래스에 데코레이터 추가&amp;lt;br/&amp;gt;토큰 생성 및 등록 준비&amp;#34;| User
%% 컨테이너 저장
Logger -.-&amp;gt;|&amp;#34;💾 &amp;#39;Logger&amp;#39; 토큰으로&amp;lt;br/&amp;gt;LoggerService 클래스 저장&amp;#34;| TokenLogger
Database -.-&amp;gt;|&amp;#34;💾 &amp;#39;Database&amp;#39; 토큰으로&amp;lt;br/&amp;gt;DatabaseService 클래스 저장&amp;#34;| TokenDatabase
User -.-&amp;gt;|&amp;#34;💾 &amp;#39;UserService&amp;#39; 토큰으로&amp;lt;br/&amp;gt;UserService 클래스 저장&amp;#34;| TokenUser
%% 의존성 관계
Database --&amp;gt;|&amp;#34;🔗 DatabaseService.logger 필드&amp;lt;br/&amp;gt;LoggerService 인스턴스 필요&amp;#34;| Logger
User --&amp;gt;|&amp;#34;🔗 UserService.db 필드&amp;lt;br/&amp;gt;DatabaseService 인스턴스 필요&amp;#34;| Database
User --&amp;gt;|&amp;#34;🔗 UserService.logger 필드&amp;lt;br/&amp;gt;LoggerService 인스턴스 필요&amp;#34;| Logger
%% 주입 과정
InjectDecorator -.-&amp;gt;|&amp;#34;🔍 토큰으로 컨테이너 검색&amp;lt;br/&amp;gt;클래스 찾기 및 인스턴스 생성&amp;#34;| DIMap
%% 실행 과정
ClientCode --&amp;gt;|&amp;#34;🚀 new UserService() 호출&amp;lt;br/&amp;gt;의존성 주입 트리거&amp;#34;| User
%% 스타일링
classDef container fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef decorator fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef service fill:#e8f5e8,stroke:#4caf50,stroke-width:2px,color:#000
classDef client fill:#fce4ec,stroke:#e91e63,stroke-width:2px,color:#000
classDef process fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#000
class DIMap,TokenLogger,TokenDatabase,TokenUser container
class InjectableDecorator,InjectDecorator decorator
class Logger,Database,User service
class ClientCode client
class Step1,Step2,Step3,Step4 process&lt;/pre>&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; npx ts-node ./src/dependency-injection.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LoggerService constructor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LoggerService constructor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>LOG&lt;span class="o">]&lt;/span> 2025-09-06T00:30:24.749Z: Getting user &lt;span class="m">123&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>LOG&lt;span class="o">]&lt;/span> 2025-09-06T00:30:24.750Z: Executing: SELECT * FROM users WHERE &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">123&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>의존성 주입이 잘 되고 있군요! 그리고 &lt;code>LoggerService constructor&lt;/code>가 두번 출력되는 것에서 매번 의존성 주입이 발생할 때 마다 새로운 객체가 생성된 다는 걸 확인할 수 있습니다. 그런데 아직 데코레이터 메타데이터는 보이지 않습니다&amp;hellip; 이제 데코레이터 메타데이터를 이용해서 의존성 주입 정보를 더 자세히 확인할 수 있도록 해보겠습니다.&lt;/p>
&lt;h2 id="데코레이터-메타데이터를-이용해서-의존성-주입-정보를-확인해보자">데코레이터 메타데이터를 이용해서 의존성 주입 정보를 확인해보자&lt;/h2>
&lt;p>앞선 예제에 데코레이터 메타데이터를 추가하여 다음과 같이 작성해보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/decorator-metadata/src/di-with-metadata.ts
&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">// Symbol.metadata 를 지원하기 위한 polyfill
&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 class="nx">Symbol&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">metadata&lt;/span> &lt;span class="o">??=&lt;/span> &lt;span class="nx">Symbol&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Symbol.metadata&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">INJECTION_TOKENS_KEY&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Symbol&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;injection-tokens&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * Injectable 데코레이터
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 클래스를 DI 컨테이너에 등록합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param token 클래스 이름
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 데코레이터 함수
&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">Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">token?&lt;/span>: &lt;span class="kt">string&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">target&lt;/span>: &lt;span class="kt">any&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">registry&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">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// token: Logger, target.name: LoggerService, target: LoggerService 클래스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">registry&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">token&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">target&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">// 환경에 상관없이 사용할 수 있도록 globalThis에 글로벌 컨테이너를 설정합니다.
&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 class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&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">();&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">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">registry&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="p">(&lt;/span>&lt;span class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&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">key&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 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"> * Inject 데코레이터
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 클래스 필드에 DI로 객체의 인스턴스를 주입합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param token 클래스 이름
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 데코레이터 함수
&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">Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">token&lt;/span>: &lt;span class="kt">string&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">target&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassFieldDecoratorContext&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">fieldName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="kr">as&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="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=== &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">fieldName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39; 필드에 @Inject(&amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">token&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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">// 메타데이터 객체에 injection tokens 정보가 없으면 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">metadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">INJECTION_TOKENS_KEY&lt;/span>&lt;span class="p">]&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">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&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">// TypeScript의 메타데이터에 필드명과 토큰 매핑 저장
&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">injectionTokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">metadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">INJECTION_TOKENS_KEY&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">Map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">injectionTokens&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">fieldName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">token&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 class="nx">injectionTokens&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">return&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">initialValue&lt;/span>: &lt;span class="kt">any&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">container&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">globalThis&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">diContainer&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">serviceClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">container&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">token&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="o">!&lt;/span>&lt;span class="nx">serviceClass&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`컨테이너에서 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">token&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="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">serviceClass&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">// 메타데이터를 확인하기 위한 유틸리티 함수들
&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"> * 클래스 인스턴스에서 TypeScript 메타데이터로부터 DI 정보를 확인합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param instance 클래스 인스턴스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns DI로 주입된 정보
&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">getInjectionInfo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">instance&lt;/span>: &lt;span class="kt">object&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Map&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">undefined&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="kr">constructor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">instance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">constructor&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">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">constructor&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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="o">!&lt;/span>&lt;span class="nx">metadata&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="kc">undefined&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="nx">metadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">INJECTION_TOKENS_KEY&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">Map&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="p">&amp;gt;;&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"> * 클래스의 생성자를 통해 TypeScript 메타데이터로부터 DI 정보를 확인합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param constructor 클래스 생성자
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns DI로 주입된 정보
&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">getClassInjectionInfo&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">Function&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Map&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">undefined&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">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">constructor&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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="o">!&lt;/span>&lt;span class="nx">metadata&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="kc">undefined&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="nx">metadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">INJECTION_TOKENS_KEY&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">Map&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">string&lt;/span>&lt;span class="p">&amp;gt;;&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"> * 클래스 인스턴스에서 TypeScript 메타데이터의 DI 정보를 디버깅합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param instance 클래스 인스턴스
&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">debugInstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">instance&lt;/span>: &lt;span class="kt">object&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">className&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">instance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">constructor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">injections&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">getInjectionInfo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">instance&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">className&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">injections&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">injections&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">size&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&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">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">field&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">token&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">of&lt;/span> &lt;span class="nx">injections&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">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> &amp;lt;- &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">token&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 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>&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"> * 클래스의 전체 TypeScript 메타데이터 구조를 확인합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @param constructor 클래스 생성자
&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">inspectMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">constructor&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">Function&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">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kr">constructor&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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">className&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">className&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="o">!&lt;/span>&lt;span class="nx">metadata&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; 메타데이터가 없습니다.&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>&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">const&lt;/span> &lt;span class="nx">injectionTokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">metadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">INJECTION_TOKENS_KEY&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">injectionTokens&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; 의존성 주입 토큰:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">injectionTokens&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>&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"> * LoggerService 클래스
&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">@Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Logger&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">class&lt;/span> &lt;span class="nx">LoggerService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">message&lt;/span>: &lt;span class="kt">string&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">`[LOG] &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">toISOString&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">message&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * DatabaseService 클래스
&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">@Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Database&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">class&lt;/span> &lt;span class="nx">DatabaseService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Logger&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">private&lt;/span> &lt;span class="nx">logger&lt;/span>&lt;span class="o">!:&lt;/span> &lt;span class="nx">LoggerService&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">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sql&lt;/span>: &lt;span class="kt">string&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">logger&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">`Executing: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">sql&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="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">rows&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="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"> * UserService 클래스
&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">@Injectable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;UserService&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">class&lt;/span> &lt;span class="nx">UserService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Database&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">private&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="o">!:&lt;/span> &lt;span class="nx">DatabaseService&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">@Inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Logger&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">private&lt;/span> &lt;span class="nx">logger&lt;/span>&lt;span class="o">!:&lt;/span> &lt;span class="nx">LoggerService&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">getUser&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">logger&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">`Getting user &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="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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`SELECT * FROM users WHERE id = &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="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">// DEMO: 데코레이터 메타데이터를 통한 DI 정보 확인
&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;=== 데코레이터 메타데이터를 통한 DI 정보 확인 ===&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="nx">inspectMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">LoggerService&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">inspectMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">DatabaseService&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">inspectMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">UserService&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">// 생성된 클래스에서 TypeScript 메타데이터의 DI 정보 확인
&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">userService&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">UserService&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">debugInstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">userService&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">debugInstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">DatabaseService&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">debugInstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">LoggerService&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">// 클래스를 통해 TypeScript 메타데이터의 DI 정보 확인
&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">userInjections&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">getClassInjectionInfo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">UserService&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;\nUserService 메타데이터의 의존성 주입 정보:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">userInjections&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 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">userMetadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">UserService&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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;UserService 메타데이터 객체:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">userMetadata&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;UserService 의존성 주입 토큰:&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">userMetadata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">INJECTION_TOKENS_KEY&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="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">userService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>코드의 최상단에 Symbol.metadata를 지원하기 위한 polyfill이 추가되었습니다. 그리고 각 클래스의 데코레이터 메타데이터에 의존성 주입 정보를 저장하는 WeakMap을 선언했습니다.&lt;/p>
&lt;p>그리고 Inject 데코레이터에 메타데이터 객체를 이용하여 특정 클래스에서 발생한 의존성 주입 정보를 저장하도록 했습니다. 그리고 여러 헬퍼 함수를 통해 메타데이터의 정보를 확인할 수 있도록 했습니다. 해당 코드를 보시면 다음과 같이 특정 클래스의 데코레이터 메타데이터를 불러오고 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">UserService&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx ts-node ./src/di-with-metadata.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 class="o">===&lt;/span> &lt;span class="s1">&amp;#39;logger&amp;#39;&lt;/span> 필드에 @Inject&lt;span class="o">(&lt;/span>&lt;span class="s1">&amp;#39;Logger&amp;#39;&lt;/span>&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">→ 메타데이터에 저장된 의존성 주입 정보: Map&lt;span class="o">(&lt;/span>1&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s1">&amp;#39;logger&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Logger&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 class="s1">&amp;#39;db&amp;#39;&lt;/span> 필드에 @Inject&lt;span class="o">(&lt;/span>&lt;span class="s1">&amp;#39;Database&amp;#39;&lt;/span>&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">→ 메타데이터에 저장된 의존성 주입 정보: Map&lt;span class="o">(&lt;/span>1&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s1">&amp;#39;db&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Database&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 class="s1">&amp;#39;logger&amp;#39;&lt;/span> 필드에 @Inject&lt;span class="o">(&lt;/span>&lt;span class="s1">&amp;#39;Logger&amp;#39;&lt;/span>&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">→ 메타데이터에 저장된 의존성 주입 정보: Map&lt;span class="o">(&lt;/span>2&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s1">&amp;#39;db&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Database&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;logger&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Logger&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="o">===&lt;/span> 데코레이터 메타데이터를 통한 DI 정보 &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">&lt;span class="o">===&lt;/span> LoggerService 메타데이터 &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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> DatabaseService 메타데이터 &lt;span class="nv">구조&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 의존성 주입 토큰: Map&lt;span class="o">(&lt;/span>1&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s1">&amp;#39;logger&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Logger&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> UserService 메타데이터 &lt;span class="nv">구조&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 의존성 주입 토큰: Map&lt;span class="o">(&lt;/span>2&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="s1">&amp;#39;db&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Database&amp;#39;&lt;/span>, &lt;span class="s1">&amp;#39;logger&amp;#39;&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s1">&amp;#39;Logger&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> UserService 메타데이터의 의존성 주입 &lt;span class="nv">정보&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> db &amp;lt;- Database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> logger &amp;lt;- &lt;span class="nv">Logger&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> DatabaseService 메타데이터의 의존성 주입 &lt;span class="nv">정보&lt;/span> &lt;span class="o">===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> logger &amp;lt;- &lt;span class="nv">Logger&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> LoggerService 메타데이터의 의존성 주입 &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">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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">&lt;span class="o">[&lt;/span>LOG&lt;span class="o">]&lt;/span> 2025-09-06T01:13:29.104Z: Getting user &lt;span class="m">123&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>LOG&lt;span class="o">]&lt;/span> 2025-09-06T01:13:29.105Z: Executing: SELECT * FROM users WHERE &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">123&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>처음 세 줄은 Inject 데코레이터가 실행될 때 출력되는 로그입니다. 가장먼저 DatabaseService의 logger 필드에 의존성이 주입되고, 이어서 UserService의 db, logger 필드에 순서대로 의존성이 주입되고 있습니다.&lt;/p>
&lt;p>그 다음 세줄은 각 클래스의 데코레이터 메타데이터를 살펴보고 있습니다.&lt;/p>
&lt;p>그리고 그 다음 세 줄은 각 클래스의 데코레이터 메타데이터를 통해 의존성 주입 정보를 확인하고 있구요, 마지막 2줄은 서비스 실행 결과입니다.&lt;/p>
&lt;h2 id="reflect-metadata-와의-비교">reflect-metadata 와의 비교&lt;/h2>
&lt;p>&lt;a href="https://github.com/microsoft/reflect-metadata">reflect-metadata&lt;/a>는 데코레이터의 메타데이터를 활용하기 위해 그동안 활용되어 왔습니다. 하지만, 새로운 표준의 등장으로 신규 프로젝트에서는 표준 데코레이터 메타데이터를 사용하는 게 좋습니다.&lt;/p>
&lt;ul>
&lt;li>번들링 사이즈를 줄일 수 있습니다. 라이브러리를 polyfill로 대체할 수 있죠.&lt;/li>
&lt;li>데코레이터 메타데이터는 Stage 3 단계에 있습니다. Stage 3는 Candidate 상태로서 대부분 완성 되어 안정적이라고 판단할 수 있는 상태입니다. 따라서 향후 표준이 진행되는 방향을 따라가는 게 자연스럽습니다. 그리고 reflect-metadata 리포지토리에서도 이 점을 분명히 하고 있습니다.&lt;/li>
&lt;li>reflect-metadata는 메타데이터를 가져오기 위해 Prototype 체인을 순회합니다. 따라서 메타데이터 처리를 위해 더 많은 작업이 필요하고, 성능 측면에서 불리합니다.&lt;/li>
&lt;/ul>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata">TypeScript 5.2/Decorator Metadata&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/ko/docs/handbook/decorators.html#%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0-metadata">TypeScript Decorators&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/tc39/proposal-decorator-metadata">TC39/proposal-decorator-metadata&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.bitsrc.io/typescripts-reflect-metadata-what-it-is-and-how-to-use-it-fb7b19cfc7e2">TypeScript’s Reflect Metadata: What it is and How to Use it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/microsoft/reflect-metadata">reflect-metadata&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Typescript: using 문으로 리소스 관리</title><link>https://netscout.github.io/posts/typescript-using-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B4%80%EB%A6%AC/</link><pubDate>Sun, 31 Aug 2025 01:18:53 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-using-%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B4%80%EB%A6%AC/</guid><description>어떤 종류의 앱이든 만들다 보면 리소스 할당과 해제는 매우 중요한 문제입니다. 메모리를 자동으로 관리해주는 GC의 동작 방식을 보면, 특정한 절차를 통해 더 이상 참조되지 않는 객체를 찾아서 삭제해주는 작업을 반복합니다. 리소스를 할당만 하고 제대로 해제하지 않는다면 …</description><content>&lt;h2 id="이-글에서-사용된-환경-설정">이 글에서 사용된 환경 설정&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Node.js&lt;/strong>: 22.14.0&lt;/li>
&lt;li>&lt;strong>TypeScript&lt;/strong>: 5.5.3&lt;/li>
&lt;/ul>
&lt;h2 id="개요">개요&lt;/h2>
&lt;p>어떤 종류의 앱이든 만들다 보면 리소스 할당과 해제는 매우 중요한 문제입니다. 메모리를 자동으로 관리해주는 GC의 동작 방식을 보면, 특정한 절차를 통해 더 이상 참조되지 않는 객체를 찾아서 삭제해주는 작업을 반복합니다. 리소스를 할당만 하고 제대로 해제하지 않는다면 이미 사용이 끝난 리소스가 계속해서 메모리와 CPU 리소스를 점유하게 되고 이런 문제가 계속 발생한다면 심각한 앱은 심각한 메모리 누수 문제를 겪게 되겠죠.&lt;/p>
&lt;p>C#과 같은 언어에서는 초장기 부터 이런 문제를 해결하기 위해서(당시에는 C, C++의 포인터를 사용할 때 메모리 관리가 어려웠습니다.) 할당한 리소스를 자동으로 해제할 수 있는 방법을 제공했습니다.&lt;/p>
&lt;h2 id="c의-using-문">C#의 using 문&lt;/h2>
&lt;p>C#에서는 &lt;code>IDisposable&lt;/code> 또는 &lt;code>IAsyncDisposable&lt;/code> 인터페이스를 구현하여 리소스를 할당한 후 사용이 끝나면 자동으로 해제되는 방법을 제공합니다.&lt;/p>
&lt;p>다음과 같은 텍스트 파일이 있고, 이 파일을 조금씩 읽어서 콘솔에 출력하는 코드를 예시로 들어볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">// csharp-using/text.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla consectetur enim lorem, vestibulum egestas nibh consectetur quis. Nam a fringilla est, eu fringilla dui. Vestibulum neque quam, dignissim ut blandit in, suscipit laoreet dolor. Cras hendrerit urna at elit pellentesque, in bibendum libero laoreet. Nam interdum sapien id metus scelerisque facilisis. Praesent venenatis viverra lacus, nec varius mi mattis at. Sed malesuada velit risus, et pharetra arcu rhoncus sed. Duis sit amet tincidunt nisl, vel gravida ante.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Morbi egestas eu nisl et bibendum. Duis placerat sodales nulla, sed dictum libero mattis vel. Suspendisse velit sapien, scelerisque vel massa quis, ultrices vestibulum mauris. Vestibulum sit amet viverra leo, quis euismod sem. Aliquam dignissim arcu nec lectus lacinia, at pretium urna aliquam. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis lectus nunc, hendrerit eget lobortis nec, rutrum ut risus. Morbi porta orci velit, et viverra magna ullamcorper ac. Praesent commodo, turpis ut facilisis scelerisque, felis enim efficitur risus, et elementum sapien arcu vel tellus. Praesent feugiat aliquet consequat. Mauris in leo suscipit, molestie erat ut, cursus nibh. In luctus dui et nulla luctus, a elementum libero aliquet. Mauris finibus bibendum vestibulum. Quisque rhoncus purus at turpis hendrerit accumsan. Praesent posuere sapien dolor, vel feugiat sem fermentum vel. Donec massa dui, pulvinar eget quam a, aliquet malesuada arcu.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Curabitur non lorem ut justo luctus luctus. Vivamus ac congue nunc, ac tincidunt velit. Praesent nulla nisl, sollicitudin sed venenatis id, pharetra id neque. Donec tellus magna, dignissim nec faucibus vel, facilisis quis ligula. Nullam eget aliquam nisi. Mauris imperdiet nunc sed augue iaculis euismod. Duis sollicitudin posuere mauris eget accumsan. Etiam vestibulum est at sagittis maximus. Pellentesque mollis diam quis purus venenatis, vel cursus risus dictum. Vivamus elementum, magna ac eleifend rutrum, velit tellus mollis odio, sed volutpat quam felis quis tellus. Morbi a feugiat metus. Nullam sodales sem eu ligula pulvinar, a pellentesque tellus vehicula. Curabitur nec mi ornare, laoreet mi eu, molestie arcu. Curabitur ex lacus, ultricies a dui nec, placerat finibus enim. Vestibulum euismod a dolor eu vulputate.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Donec ac malesuada nisl, ut tincidunt neque. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean rhoncus eros non maximus fringilla. Curabitur lobortis ac lacus in aliquam. Morbi id arcu dui. Cras quis erat cursus, aliquam dolor et, vestibulum risus. Sed eleifend hendrerit nulla, sed maximus risus pellentesque at. Phasellus a placerat metus, vel maximus justo. Sed semper gravida risus, vel pulvinar diam pharetra at. Sed pellentesque libero mi, vitae semper ligula porta in. Suspendisse ornare, arcu nec euismod suscipit, lectus sem placerat nulla, id eleifend nisl mauris et purus. Duis in eros a nibh venenatis posuere.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Morbi rhoncus orci tortor. Vivamus cursus dolor in magna efficitur, a iaculis ligula imperdiet. Curabitur nec consectetur lectus, non bibendum quam. Nullam mollis posuere auctor. Quisque scelerisque luctus ante, vel auctor justo finibus quis. Aenean efficitur massa non nisi blandit, nec ullamcorper lectus egestas. In sed sodales dolor, sed accumsan mi. Nunc tincidunt porttitor arcu in semper. Phasellus rhoncus felis elementum justo tempor, sed congue turpis condimentum. Nam aliquet felis in pharetra convallis. Ut vestibulum hendrerit odio, et aliquet massa fringilla vitae. Fusce est risus, varius nec turpis sit amet, varius consectetur elit. Nam tristique nulla nisl, sed venenatis nunc pretium a.
&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-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// csharp-using/main.cs&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.IO&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="kt">var&lt;/span> &lt;span class="n">buffer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">reader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StreamReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;text.txt&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="kt">int&lt;/span> &lt;span class="n">charsRead&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">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">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Peek&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">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 class="n">charsRead&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buffer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buffer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">charsRead&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>이 코드를 실행하려면, cs 파일을 스크립트로 실행할 수 있게 해주는 &lt;code>dotnet-script&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; dotnet tool install -g dotnet-script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">You can invoke the tool using the following command: dotnet-script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Tool &lt;span class="s1">&amp;#39;dotnet-script&amp;#39;&lt;/span> &lt;span class="o">(&lt;/span>version &lt;span class="s1">&amp;#39;1.6.0&amp;#39;&lt;/span>&lt;span class="o">)&lt;/span> was successfully installed.
&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; dotnet script main.cs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...Nam tristique nulla nisl, sed venenatis nunc pretium a.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>코드가 중간 언어(IL)로 변환될 때 using 블록으로 감싸진 리소스를 try-finally 블록으로 변환하고 finally 블록 안에서 &lt;code>IDisposable&lt;/code> 인터페이스의 &lt;code>Dispose()&lt;/code> 메서드를 호출합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">finally&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="n">streamReader&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Dispose&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>리소스를 해제하는 걸 일일이 기억할 필요 없이 using 문을 사용하면 리소스의 해제가 자동으로 일어나게 되는 거죠. 정신 없이 코딩하다보면 리소스 해제를 놓치는 경우는 생각보다 자주 일어납니다&amp;hellip;그게 개발이죠!&lt;/p>
&lt;h2 id="javascript는">Javascript는?&lt;/h2>
&lt;p>Javascript는&amp;hellip; 이런 기능을 제공하진 않았습니다.&lt;/p>
&lt;p>텍스트 파일에 내용을 추가하는 간단한 예제를 볼까요?&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="c1">// typescript-using/append-file.ts
&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="nx">fs&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;fs&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">appendToFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">content&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&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">fd&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="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">// Open file for appending
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">fd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">openSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;a&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">const&lt;/span> &lt;span class="nx">buffer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;utf8&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">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">writeSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">buffer&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">filePath&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">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">error&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">finally&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">fd&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">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">closeSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fd&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="nx">appendToFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test.txt&amp;#34;&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">appendToFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test.txt&amp;#34;&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;/code>&lt;/pre>&lt;/div>&lt;p>보시는 것 처럼 finally 블록에서 직접 리소스를 해제 하도록 하는 코드를 작성해줘야 합니다. 만약 해제를 까먹는다면 어떻게 될까요?&lt;/p>
&lt;p>위 코드에서 fs.closeSync(fd)를 주석 처리하고 실행해봐도 실행에는 문제가 없지만, 메모리 누수가 생기겠죠? 그리고 OS의 File Descriptor 또한 계속 차지하다가 &lt;code>Too many open files&lt;/code> 에러도 발생할 수 있습니다.&lt;/p>
&lt;p>이런 문제가 있다보니 Javascript에서도 리소스 관리를 좀 더 스마트하게 할 수 있는 방법을 제공하기 위한 논의가 진행되었습니다. &lt;a href="https://github.com/tc39/proposal-explicit-resource-management">tc39&lt;/a> 에 의해 이미 스펙은 정의되었고, 각 브라우저/런타임 등에서 구현이 시작된 상태입니다.&lt;/p>
&lt;h2 id="typescript의-using-문">Typescript의 using 문&lt;/h2>
&lt;p>Typescript 5.2 버전에서는 Javascript의 스펙에 발 맞춰 &lt;code>using&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="c1">// typescript-using/using-append-file.ts
&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="nx">fs&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;fs&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">class&lt;/span> &lt;span class="nx">FileDescriptor&lt;/span> &lt;span class="kr">implements&lt;/span> &lt;span class="nx">Disposable&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">private&lt;/span> &lt;span class="nx">fd&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mode&lt;/span>: &lt;span class="kt">string&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">openSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mode&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">get&lt;/span> &lt;span class="nx">descriptor&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&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="k">return&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fd&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="p">[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dispose&lt;/span>&lt;span class="p">]()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fd&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">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">closeSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fd&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;File descriptor closed automatically&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">appendToFileWithUsing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">content&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&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="c1">// using 카워드로 선언된 객체는 선언된 스코프를 벗어날 때 자동으로 Symbol.dispose를 호출합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">using&lt;/span> &lt;span class="nx">file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;a&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">const&lt;/span> &lt;span class="nx">buffer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;utf8&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">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">writeSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">descriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">buffer&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">`Successfully appended to &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">filePath&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">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">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Error appending to file:&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>&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">appendToFileWithUsing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test.txt&amp;#34;&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">appendToFileWithUsing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test.txt&amp;#34;&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;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>FileDescriptor&lt;/code> 클래스는 &lt;code>Disposable&lt;/code> 인터페이스를 통해 &lt;code>Symbol.dispose&lt;/code> 메서드를 구현하고 있습니다. &lt;code>Disposable&lt;/code> 인터페이스는 파일 디스크립터가 선언된 스코프를 벗어날 때 자동으로 파일 디스크립터를 해제합니다.&lt;/p>
&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; npx ts-node ./using-append-file.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Successfully appended to test.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">File descriptor closed automatically
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Successfully appended to test.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">File descriptor closed automatically
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>npx tsc&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="kd">function&lt;/span> &lt;span class="nx">appendToFileWithUsing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">content&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">env_1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">stack&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[],&lt;/span> &lt;span class="nx">error&lt;/span>: &lt;span class="kt">void&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">hasError&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="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">// using 카워드로 선언된 객체는 선언된 스코프를 벗어날 때 자동으로 Symbol.dispose를 호출합니다.
&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">file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">__addDisposableResource&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">env_1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;a&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="kc">false&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">buffer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;utf8&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">fs_1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">writeSync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">descriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">buffer&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">`Successfully appended to &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">filePath&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">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e_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">env_1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">e_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">env_1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hasError&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 class="k">finally&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">__disposeResources&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">env_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 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">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Error appending to file:&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>&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;/code>&lt;/pre>&lt;/div>&lt;p>finally 블록을 통해 리소스를 해제하는 코드로 변환되었습니다. 앞서 보신 코드들과 비슷하죠?&lt;/p>
&lt;h2 id="리소스-해제-순서">리소스 해제 순서&lt;/h2>
&lt;p>using 문을 통해 리소스를 사용하면, 해당 using 문이 포함된 스코프를 벗어날 때 리소스가 해제됩니다. 그 순서를 확인해볼까요? 다음은 Typescript의 공식 문서에 포함된 예제입니다.&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">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&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">Disposable&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">`Creating &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="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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dispose&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">`Disposing &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="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="kd">function&lt;/span> &lt;span class="nx">syncFunc() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">using&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;a&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">using&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;b&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">using&lt;/span> &lt;span class="nx">c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;c&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">using&lt;/span> &lt;span class="nx">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;d&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">using&lt;/span> &lt;span class="nx">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;e&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// return 문 이후의 코드는 실행되지 않습니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">using&lt;/span> &lt;span class="nx">f&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">syncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;f&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">syncFunc&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx ts-node ./dispose-order.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>생성된 리소스는 역순으로 해제되며, 가장 안쪽 스코프에 포함된 c, d가 먼저 해제되는 걸 볼 수 있습니다.&lt;/p>
&lt;h3 id="비동기-호출이-필요한-경우">비동기 호출이 필요한 경우&lt;/h3>
&lt;p>리소스를 해제할 때 비동기 호출이 필요한 경우는 &lt;code>AsyncDisposable&lt;/code> 인터페이스를 통해 &lt;code>Symbol.asyncDispose&lt;/code> 메서드를 구현하면 됩니다. 역시 Typescript의 공식 문서에 포함된 예제를 통해 확인해볼까요?&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">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">doWork() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 0.5초 동안 대기합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resolve&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resolve&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;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">asyncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&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">AsyncDisposable&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">`Constructing &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="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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">Symbol&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">asyncDispose&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">`Disposing (async) &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="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">doWork&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="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">asyncFunc() {&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">using&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">asyncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;a&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">await&lt;/span> &lt;span class="nx">using&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">asyncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;b&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="k">await&lt;/span> &lt;span class="nx">using&lt;/span> &lt;span class="nx">c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">asyncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;c&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">await&lt;/span> &lt;span class="nx">using&lt;/span> &lt;span class="nx">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">asyncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;d&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="k">await&lt;/span> &lt;span class="nx">using&lt;/span> &lt;span class="nx">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">asyncLoggy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;e&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>&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">asyncFunc&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx ts-node ./async-dispose.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Constructing a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Constructing b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Constructing c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Constructing d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing &lt;span class="o">(&lt;/span>async&lt;span class="o">)&lt;/span> d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing &lt;span class="o">(&lt;/span>async&lt;span class="o">)&lt;/span> c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Constructing e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing &lt;span class="o">(&lt;/span>async&lt;span class="o">)&lt;/span> e
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing &lt;span class="o">(&lt;/span>async&lt;span class="o">)&lt;/span> b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Disposing &lt;span class="o">(&lt;/span>async&lt;span class="o">)&lt;/span> a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="매번-인터페이스로-구현해야-한다고">매번 인터페이스로 구현해야 한다고?&lt;/h2>
&lt;p>그런데 말입니다&amp;hellip; 뭔 코드를 작성할 때 마다 이렇게 클래스를 정의해서 &lt;code>Disposable&lt;/code> 인터페이스를 구현해야만 하는 걸까요? 그래서 &lt;code>DisposableStack&lt;/code>, &lt;code>AsyncDisposableStack&lt;/code> 역시 제공됩니다. 이 객체를 이용하면, 별도의 클래스를 정의하지 않고 해제되어야 할 리소스를 명시할 수 있습니다.&lt;/p>
&lt;blockquote>
&lt;p>2025년 8월 31일 현재 &lt;code>DisposableStack&lt;/code>은 Node.js 에서 구현되지 않았기 때문에 &lt;code>@whatwg-node/disposablestack&lt;/code> 패키지를 사용합니다. &lt;code>npm i @whatwg-node/disposablestack&lt;/code> 명령으로 설치해주세요.&lt;/p>&lt;/blockquote>
&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="c1">// typescript-using/disposableTimer.ts
&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">DisposableStack&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@whatwg-node/disposablestack&amp;#34;&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">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">disposableTimerFunc() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">using&lt;/span> &lt;span class="nx">disposer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DisposableStack&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// disposer.use(disposableTimer(() =&amp;gt; console.log(&amp;#34;timer tick&amp;#34;), 1000));
&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">timer&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="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;timer tick&amp;#34;&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="nx">disposer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">defer&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">clearInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">timer&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;timer disposed&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;5초 동안 타이머를 기다립니다...&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">await&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resolve&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5000&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">disposableTimerFunc&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npx ts-node ./disposableTimer.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5초 동안 타이머를 기다립니다...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timer tick
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timer tick
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timer tick
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timer tick
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timer disposed
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>별도로 인터페이스를 구현하지 않더라도, defer 메서드로 전달한 리소스 해제 코드를 통해 리소스를 해제할 수 있습니다. 단, 주의해야 할 점이 있습니다. &lt;code>반드시 리소스를 선언한 직후에 defer 메서드로 리소스 해제 코드를 전달해야 합니다.&lt;/code> &lt;code>DisposableStack&lt;/code>은 stack과 유사한 형태로 &lt;code>마지막에 들어온 게 가장 먼저 나간다&lt;/code> 는 원리로 작동합니다. 그래서 불분명한 리소스 해제로 인해서 문제가 발생하지 않으려면 꼭 선언된 직후에 defer 메서드로 리소스 해제 코드를 전달해야 합니다.&lt;/p>
&lt;h2 id="핵심-개념-요약">핵심 개념 요약&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>개념&lt;/th>
&lt;th>설명&lt;/th>
&lt;th>예시&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>using 선언&lt;/strong>&lt;/td>
&lt;td>스코프를 벗어날 때 자동으로 리소스 해제&lt;/td>
&lt;td>&lt;code>using file = new FileDescriptor(...)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Disposable&lt;/strong>&lt;/td>
&lt;td>동기 리소스 해제 인터페이스&lt;/td>
&lt;td>&lt;code>[Symbol.dispose](): void&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AsyncDisposable&lt;/strong>&lt;/td>
&lt;td>비동기 리소스 해제 인터페이스&lt;/td>
&lt;td>&lt;code>[Symbol.asyncDispose](): Promise&amp;lt;void&amp;gt;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>DisposableStack&lt;/strong>&lt;/td>
&lt;td>여러 리소스를 스택으로 관리&lt;/td>
&lt;td>&lt;code>disposer.defer(() =&amp;gt; cleanup())&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>해제 순서&lt;/strong>&lt;/td>
&lt;td>LIFO (Last In, First Out) 방식&lt;/td>
&lt;td>마지막에 생성된 것부터 해제&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management">TypeScript 5.2&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management">JavaScript resource management&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/tc39/proposal-explicit-resource-management">tc39/proposal-explicit-resource-management&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/whatwg-node/disposablestack">https://www.npmjs.com/package/@whatwg-node/disposablestack&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/ko-kr/dotnet/standard/garbage-collection/using-objects">IDisposable을 구현하는 개체 사용&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Typescript: Decorator</title><link>https://netscout.github.io/posts/typescript-decorator/</link><pubDate>Sat, 02 Aug 2025 23:28:08 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-decorator/</guid><description>&lt;ol>
&lt;li>개요 2. 기존에 작성했던 데코레이터 (Legacy) 3. TypeScript 5.0+의 데코레이터 (Modern) 4. 좀 더 확장해보기 5. 참고자료&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/typescript-decorator/#%ea%b0%9c%ec%9a%94">개요&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/typescript-decorator/#%ea%b8%b0%ec%a1%b4%ec%97%90-%ec%9e%91%ec%84%b1%ed%96%88%eb%8d%98-%eb%8d%b0%ec%bd%94%eb%a0%88%ec%9d%b4%ed%84%b0">기존에 작성했던 데코레이터 (Legacy)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/typescript-decorator/#typescript-50%ec%9d%98-%eb%8d%b0%ec%bd%94%eb%a0%88%ec%9d%b4%ed%84%b0">TypeScript 5.0+의 데코레이터 (Modern)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/typescript-decorator/#%ec%a2%80-%eb%8d%94-%ed%99%95%ec%9e%a5%ed%95%b4%eb%b3%b4%ea%b8%b0">좀 더 확장해보기&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://netscout.github.io/posts/typescript-decorator/#%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>예전에 퇴사했던 직원이 작성했던 프로젝트를 유지보수하기 위해 코드를 들여다 본적이 있었습니다. 그 프로젝트를 A라고 해보죠. 4-5개의 프로젝트를 동시에 띄워놓고 동작을 확인해야 했어서 정신없이 프로젝트를 실행하고 동작을 확인하던 중 A에서 뭔가 제대로 동작하지 않는다는 걸 확인했습니다. 그래서 로그를 확인해보다 놀랐습니다! A에는 로그를 남기는 코드가 단 한 줄도 없었거든요&amp;hellip;&lt;/p>
&lt;p>그래서 일단 요청을 처리하는 각 메서드 마다 시작과 끝을 로그로 남겨야겠다는 생각에 로그를 추가하다가 문득 얼마전 지나가듯이 봤던 Typescript의 데코레이터(Decorator)가 기억났습니다. 그래서 무작정 찾아보며 코드를 작성하기 시작했습니다.&lt;/p>
&lt;h2 id="기존에-작성했던-데코레이터">기존에 작성했던 데코레이터&lt;/h2>
&lt;p>우선 tsconfig.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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;experimentalDecorators&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/legacy_decorator.ts
&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"> */&lt;/span>
&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">loggedMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">headMessage&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="o">:&lt;/span> &lt;span class="nx">MethodDecorator&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="kd">function&lt;/span> &lt;span class="nx">actualDecorator&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">T&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">target&lt;/span>: &lt;span class="kt">object&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">propertyKey&lt;/span>: &lt;span class="kt">string&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">symbol&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">descriptor&lt;/span>: &lt;span class="kt">TypedPropertyDescriptor&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="nx">TypedPropertyDescriptor&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">T&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="kr">const&lt;/span> &lt;span class="nx">originalMethod&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">descriptor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">unknown&lt;/span> &lt;span class="kr">as&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">args&lt;/span>: &lt;span class="kt">any&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="o">=&amp;gt;&lt;/span> &lt;span class="kt">any&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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">propertyKey&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">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Entering method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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="nx">descriptor&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">replacementMethod&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">unknown&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">T&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">descriptor&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="kr">class&lt;/span> &lt;span class="nx">MyClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// @ts-ignore - Typescript 5.8의 데코레이터 타입 체크를 일단 해제
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kd">@loggedMethod&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">myMethod() {&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;myMethod called&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="kr">const&lt;/span> &lt;span class="nx">myClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myMethod&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>참고로 해당 코드는 Typescript 5.1 에서 작성되었으나, 글을 작성하는 현재 최신 버전인 5.8에서의 실행을 위해 일단 @ts-ignore를 적용했습니다.&lt;/p>&lt;/blockquote>
&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">❯ npx ts-node legacy_decorator.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myMethod called
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>메서드가 호출될 때마다 로그가 출력되는걸 볼 수 있습니다.&lt;/p>
&lt;p>그런데 이 데코레이터는 몇가지 문제점이 있습니다. 현재 이 데코레이터는 비동기 함수에 대해서는 제대로 동작하지 않습니다. 비동기 함수 역시 지원하려면 좀 복잡한 로직을 작성해야만 합니다. 그리고 이게 더 큰 문제인데요, 이 데코레이터는 ECMAscript의 &lt;a href="https://github.com/tc39/proposal-decorators">TC39&lt;/a>에서 제시된 데코레이터 시그니처와 맞지 않습니다. 언젠가는 사라질 운명이라는 거죠.&lt;/p>
&lt;h2 id="typescript-50의-데코레이터">Typescript 5.0+의 데코레이터&lt;/h2>
&lt;p>그래서 Typescript 5.0+에서는 표준과 발맞추도록 데코레이터를 개선했습니다. 앞서 보셨던 코드와 동일한 작업을 하는 코드입니다. 이 코드를 실행하기 위해서 tsconfig.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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;es2022&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;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/new_decorator.ts
&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">function&lt;/span> &lt;span class="nx">loggedMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">headMessage&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="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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalMethod&lt;/span>: &lt;span class="kt">Function&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassMethodDecoratorContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Entering method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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="k">return&lt;/span> &lt;span class="nx">replacementMethod&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="kr">class&lt;/span> &lt;span class="nx">MyClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">myMethod() {&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;myMethod called&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="kr">const&lt;/span> &lt;span class="nx">myClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myMethod&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">❯ npx ts-node new_decorator.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myMethod called
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&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-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/new_decorator.ts
&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">function&lt;/span> &lt;span class="nx">loggedMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">headMessage&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="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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalMethod&lt;/span>: &lt;span class="kt">Function&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassMethodDecoratorContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Entering method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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="k">return&lt;/span> &lt;span class="nx">replacementMethod&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="kr">class&lt;/span> &lt;span class="nx">MyClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">myMethod() {&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;myMethod called&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="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="nx">myAsyncMethod() {&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;async method start&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">await&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&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">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&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="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 method end&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="kr">const&lt;/span> &lt;span class="nx">myClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myMethod&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myAsyncMethod&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">❯ npx ts-node new_decorator.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myMethod called
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myAsyncMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">async method start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myAsyncMethod&amp;#39;&lt;/span>. &amp;lt;---- 비동기 함수가 끝나기 전에 로그가 출력되었습니다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">async method end
&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-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/new_decorator.ts
&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">function&lt;/span> &lt;span class="nx">loggedMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">headMessage&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="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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalMethod&lt;/span>: &lt;span class="kt">Function&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassMethodDecoratorContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Entering method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">// 비동기 메서드인 경우, Promise가 완료된 후 로그를 출력
&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">result&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;function&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">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">finally&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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="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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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="k">return&lt;/span> &lt;span class="nx">replacementMethod&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="kr">class&lt;/span> &lt;span class="nx">MyClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">myMethod() {&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;myMethod called&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="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="nx">myAsyncMethod() {&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;async method start&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">await&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&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">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&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="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 method end&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="kr">const&lt;/span> &lt;span class="nx">myClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myMethod&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myAsyncMethod&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>원본 메서드릐 실행 결과를 받아서 Promise 인지 여부를 확인하는 코드를 추가했습니다. 결과를 볼까요?&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">🚩 Entering method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myMethod called
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myAsyncMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">async method start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">async method end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myAsyncMethod&amp;#39;&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-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">loggedMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">headMessage&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="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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalMethod&lt;/span>: &lt;span class="kt">Function&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassMethodDecoratorContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Entering method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">// 비동기 메서드인 경우, Promise가 완료된 후 로그를 출력
&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">result&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;function&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">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">finally&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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="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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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="k">return&lt;/span> &lt;span class="nx">replacementMethod&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="kr">class&lt;/span> &lt;span class="nx">MyClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">myMethod() {&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;myMethod called&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&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="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">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="nx">myAsyncMethod() {&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;async method start&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">await&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rej&lt;/span>&lt;span class="p">)&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">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">rej&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&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 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;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;async method end&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="kr">const&lt;/span> &lt;span class="nx">myClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myMethod&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myAsyncMethod&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">❯ npx ts-node new_decorator.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myMethod called
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:31
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> throw new Error&lt;span class="o">(&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&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">Error: &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at MyClass.myMethod &lt;span class="o">(&lt;/span>/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:31:11&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at MyClass.replacementMethod &lt;span class="o">(&lt;/span>/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:10:37&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>/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:48:9&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Module._compile &lt;span class="o">(&lt;/span>node:internal/modules/cjs/loader:1554:14&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Module.m._compile &lt;span class="o">(&lt;/span>/Users/onlifecoding/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1618:23&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at node:internal/modules/cjs/loader:1706:10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Object.require.extensions.&amp;lt;computed&amp;gt; &lt;span class="o">[&lt;/span>as .ts&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>/Users/onlifecoding/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1621:12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Module.load &lt;span class="o">(&lt;/span>node:internal/modules/cjs/loader:1289:32&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Function._load &lt;span class="o">(&lt;/span>node:internal/modules/cjs/loader:1108:12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at TracingChannel.traceSync &lt;span class="o">(&lt;/span>node:diagnostics_channel:322:14&lt;span class="o">)&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-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">loggedMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">headMessage&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="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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalMethod&lt;/span>: &lt;span class="kt">Function&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassMethodDecoratorContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Entering method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">// 비동기 메서드인 경우, Promise가 완료된 후 로그를 출력
&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">result&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;function&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">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">finally&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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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="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">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">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 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="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">headMessage&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> Exiting method &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39; with error.`&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">throw&lt;/span> &lt;span class="nx">error&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">return&lt;/span> &lt;span class="nx">replacementMethod&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="kr">class&lt;/span> &lt;span class="nx">MyClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">myMethod() {&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;myMethod called&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test&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="kd">@loggedMethod&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="nx">myAsyncMethod() {&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;async method start&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">await&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rej&lt;/span>&lt;span class="p">)&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">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">rej&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test&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="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;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;async method end&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="kr">const&lt;/span> &lt;span class="nx">myClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myMethod&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">myClass&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">myAsyncMethod&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">❯ npx ts-node new_decorator.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Entering method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myMethod called
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🚩 Exiting method &lt;span class="s1">&amp;#39;myMethod&amp;#39;&lt;/span> with error.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:39
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> throw new Error&lt;span class="o">(&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&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">Error: &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at MyClass.myMethod &lt;span class="o">(&lt;/span>/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:39:11&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at MyClass.replacementMethod &lt;span class="o">(&lt;/span>/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:12:39&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>/Users/onlifecoding/src/test/typescript-studies/src/decorator/src/new_decorator.ts:56:9&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Module._compile &lt;span class="o">(&lt;/span>node:internal/modules/cjs/loader:1554:14&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Module.m._compile &lt;span class="o">(&lt;/span>/Users/onlifecoding/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1618:23&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at node:internal/modules/cjs/loader:1706:10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Object.require.extensions.&amp;lt;computed&amp;gt; &lt;span class="o">[&lt;/span>as .ts&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>/Users/onlifecoding/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1621:12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Module.load &lt;span class="o">(&lt;/span>node:internal/modules/cjs/loader:1289:32&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at Function._load &lt;span class="o">(&lt;/span>node:internal/modules/cjs/loader:1108:12&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at TracingChannel.traceSync &lt;span class="o">(&lt;/span>node:diagnostics_channel:322:14&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>함수가 오류 때문에 종료되었다고 명확하게 표시되는 걸 확인할 수 있습니다.&lt;/p>
&lt;h2 id="좀-더-확장해보기">좀 더 확장해보기&lt;/h2>
&lt;p>데코레이터는 더 다양한 상황에서 유용하게 사용할 수 있는데요, 그 중에서 API 호출이 실패할 때 지정된 횟수만큼 재시도하도록 하는 데코레이터를 구현해보겠습니다.&lt;/p>
&lt;h3 id="-exponential-backoff-재시도-데코레이터">📊 Exponential Backoff 재시도 데코레이터&lt;/h3>
&lt;p>&lt;code>Exponential Backoff&lt;/code> 는 API 호출 등이 실패할 때, 지정된 횟수만큼 재시도하되, 재시도 사이에 시간 간격을 지수적으로 증가시키는 방법입니다. 뭔가 서비스가 일시적으로 오류가 발생했을 때, 복구하는 데 시간이 좀 걸릴 수 있으므로 그걸 대비해서 시간을 늘려가면서 재시도하는 거죠.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/retry_decorator.ts
&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"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">RetryOptions&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">maxRetries?&lt;/span>: &lt;span class="kt">number&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">initialDelay?&lt;/span>: &lt;span class="kt">number&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 ms - 대기 시간 (밀리초)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 대기 완료 후 프로미스 반환
&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">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&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">void&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">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">ms&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 options - 재시도 옵션
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 재시도 데코레이터 함수
&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">retry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">options&lt;/span>: &lt;span class="kt">RetryOptions&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="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">maxRetries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">initialDelay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1000&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">options&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">return&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalMethod&lt;/span>: &lt;span class="kt">Function&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">ClassMethodDecoratorContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">methodName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&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">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">replacementMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">...&lt;/span>&lt;span class="nx">args&lt;/span>: &lt;span class="kt">any&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="kd">let&lt;/span> &lt;span class="nx">lastError&lt;/span>: &lt;span class="kt">any&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="kd">let&lt;/span> &lt;span class="nx">attempt&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">attempt&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="nx">maxRetries&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">attempt&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="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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">originalMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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 키워드를 사용하여 비동기 작업 완료를 기다림
&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">result&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;function&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="k">await&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="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 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">lastError&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">error&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">attempt&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">maxRetries&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">// Exponential backoff: 대기 시간 = initialDelay * 2^attempt
&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="nx">initialDelay&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">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">attempt&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">errorMessage&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error&lt;/span> &lt;span class="k">instanceof&lt;/span> &lt;span class="nb">Error&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">error.message&lt;/span> : &lt;span class="kt">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">error&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>&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">delay&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">ms 후 재시도 &lt;/span>&lt;span class="si">${&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">attempt&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="si">}&lt;/span>&lt;span class="sb">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">maxRetries&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> - 오류: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">errorMessage&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="k">await&lt;/span> &lt;span class="nx">sleep&lt;/span>&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 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="sb">`❌ All retries failed for &amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">methodName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&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">throw&lt;/span> &lt;span class="nx">lastError&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="nx">replacementMethod&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">// 예제 사용: Exponential Backoff 재시도 데코레이터
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">class&lt;/span> &lt;span class="nx">TestService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">private&lt;/span> &lt;span class="nx">asyncAttemptCount&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="kr">private&lt;/span> &lt;span class="nx">syncAttemptCount&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">// 비동기 메서드에 적용(마지막에 성공하는 케이스)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kd">@retry&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">maxRetries&lt;/span>: &lt;span class="kt">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">initialDelay&lt;/span>: &lt;span class="kt">500&lt;/span> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="nx">unreliableAsyncMethod() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">asyncAttemptCount&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">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">`📡 비동기 메서드 테스트 (2번 실패 후 성공) - 시도 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">asyncAttemptCount&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>&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">asyncAttemptCount&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">3&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`비동기 메서드 테스트 (2번 실패 후 성공) - 시도 &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">asyncAttemptCount&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>&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;✅ 비동기 메서드 테스트 (2번 실패 후 성공) - 성공!&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="k">return&lt;/span> &lt;span class="s2">&amp;#34;비동기 메서드 테스트 (2번 실패 후 성공) - 성공!&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="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 class="kr">async&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">service&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">TestService&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;=== 비동기 메서드 테스트 (2번 실패 후 성공) ===&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">unreliableAsyncMethod&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;Result:&amp;#34;&lt;/span>&lt;span class="p">,&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 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="kr">const&lt;/span> &lt;span class="nx">errorMessage&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">error&lt;/span> &lt;span class="k">instanceof&lt;/span> &lt;span class="nb">Error&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">error.message&lt;/span> : &lt;span class="kt">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">error&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;Final error:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">errorMessage&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;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">❯ npx ts-node retry_decorator.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===&lt;/span> 비동기 메서드 테스트 &lt;span class="o">(&lt;/span>2번 실패 후 성공&lt;span class="o">)&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">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🔄 500ms 후 재시도 1/3 - 오류: 비동기 메서드 테스트 &lt;span class="o">(&lt;/span>2번 실패 후 성공&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 class="o">(&lt;/span>2번 실패 후 성공&lt;span class="o">)&lt;/span> - 시도 &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">🔄 1000ms 후 재시도 2/3 - 오류: 비동기 메서드 테스트 &lt;span class="o">(&lt;/span>2번 실패 후 성공&lt;span class="o">)&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> - 시도 &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>2번 실패 후 성공&lt;span class="o">)&lt;/span> - 성공!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Result: 비동기 메서드 테스트 &lt;span class="o">(&lt;/span>2번 실패 후 성공&lt;span class="o">)&lt;/span> - 성공!
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="추가-확장-아이디어">추가 확장 아이디어&lt;/h3>
&lt;p>이 패턴을 기반으로 다음과 같이 확장해볼 수도 있을 겁니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>데코레이터&lt;/th>
&lt;th>설명&lt;/th>
&lt;th>적용 예시&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>@Cache()&lt;/strong>&lt;/td>
&lt;td>메서드 결과 캐싱&lt;/td>
&lt;td>&lt;code>@Cache({ ttl: 5000 })&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>@RateLimit()&lt;/strong>&lt;/td>
&lt;td>호출 빈도 제한&lt;/td>
&lt;td>&lt;code>@RateLimit({ maxCalls: 10, windowMs: 60000 })&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>@Timeout()&lt;/strong>&lt;/td>
&lt;td>실행 시간 제한&lt;/td>
&lt;td>&lt;code>@Timeout(5000)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>@Measure()&lt;/strong>&lt;/td>
&lt;td>성능 측정&lt;/td>
&lt;td>&lt;code>@Measure({ threshold: 1000 })&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>@Authorize()&lt;/strong>&lt;/td>
&lt;td>권한 검증&lt;/td>
&lt;td>&lt;code>@Authorize(['admin', 'user'])&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://dev.to/rajrathod/exploring-the-power-of-typescript-decorators-extending-and-modifying-code-with-ease-424l">Exploring the Power of TypeScript Decorators: Extending and Modifying Code with Ease&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.bitsrc.io/using-typescript-decorators-in-practise-d09cc1c0f8f4">Using TypeScript Decorators in Practise&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html">TypeScript 5.0&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/tc39/proposal-decorators">tc39/proposal-decorators&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/decorators.html">TypeScript Handbook - Decorators&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>TypeScript: 인덱스 시그니처와 인덱스 액세스 타입</title><link>https://netscout.github.io/posts/typescript-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98%EC%99%80-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%95%A1%EC%84%B8%EC%8A%A4-%ED%83%80%EC%9E%85/</link><pubDate>Thu, 24 Jul 2025 01:09:56 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98%EC%99%80-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%95%A1%EC%84%B8%EC%8A%A4-%ED%83%80%EC%9E%85/</guid><description>이전에 작성했던 글에서 enum 타입 대신에 사용할 수 있는 방법 중 이런 코드가 있었습니다.</description><content>&lt;hr>
&lt;h2 id="개요">개요&lt;/h2>
&lt;p>&lt;a href="https://raw.githubusercontent.com/netscout/typescript-studies/main/src/erasableSyntaxOnly/README.md">이전에 작성했던 글&lt;/a>에서 enum 타입 대신에 사용할 수 있는 방법 중 이런 코드가 있었습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">Direction&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">Up&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;up&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">Down&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;down&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">Left&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;left&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">Right&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;right&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">as&lt;/span> &lt;span class="kr">const&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">type&lt;/span> &lt;span class="nx">DirectionType&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>저는 이 코드를 처음 봤을 때 &lt;code>이게 뭐지?&lt;/code> 싶었습니다. 좀 찾아본 뒤에 이해한대로 해석을 적긴했지만 완전히 이해되진 않았었습니다. 그래서 이 글은 제가 이해하고 싶어서 정리해본 내용을 적었는데요. TypeScript의 &lt;code>인덱스 시그니처(Index Signatures)&lt;/code>와 &lt;code>인덱스 액세스 타입(Index Access Types)&lt;/code>에 대해서 좀 더 자세히 알아보겠습니다.&lt;/p>
&lt;h2 id="용어-정리">용어 정리&lt;/h2>
&lt;p>글을 쓰면서 저도 용어가 좀 헷갈리기 시작했습니다. 우선 혼동을 방지하기 위해 먼저 핵심 용어들을 정리하겠습니다:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&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;code>[key: string]: any&lt;/code>&lt;/td>
&lt;td>동적 키를 가진 객체의 타입 정의&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>인덱스 액세스 타입&lt;/td>
&lt;td>&lt;code>Type[Key]&lt;/code>&lt;/td>
&lt;td>기존 타입에서 특정 속성 타입 추출&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>typeof 연산자&lt;/td>
&lt;td>&lt;code>typeof value&lt;/code>&lt;/td>
&lt;td>값의 타입을 추론&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>keyof 연산자&lt;/td>
&lt;td>&lt;code>keyof Type&lt;/code>&lt;/td>
&lt;td>타입의 모든 키를 유니온으로 추출&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="인덱스-시그니처-index-signatures">인덱스 시그니처 (Index Signatures)&lt;/h2>
&lt;h3 id="기본-개념과-문법">기본 개념과 문법&lt;/h3>
&lt;p>인덱스 시그니처는 &lt;strong>객체의 키가 미리 정해지지 않았지만, 키와 값의 타입을 알고 있을 때&lt;/strong> 사용하는 타입 정의 방법입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">interface&lt;/span> &lt;span class="nx">Dictionary&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">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&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">dict&lt;/span>: &lt;span class="kt">Dictionary&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">john&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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">jane&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">25&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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="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="nx">dict&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">john&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// { name: &amp;#34;John&amp;#34;, age: 30, isStudent: true }
&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="nx">dict&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">jane&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// { name: &amp;#34;Jane&amp;#34;, age: 25, isStudent: false }
&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-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">PersonDict&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">key&lt;/span>: &lt;span class="kt">string&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="nx">name&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="nx">age&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">isStudent&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 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">people&lt;/span>: &lt;span class="kt">PersonDict&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">john&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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">jane&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">25&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="사용-예시">사용 예시&lt;/h3>
&lt;p>인덱스 시그니처는 다음과 같은 경우에 유용하게 사용할 수 있습니다.&lt;/p>
&lt;p>&lt;strong>API 응답 데이터 처리:&lt;/strong>&lt;/p>
&lt;p>예를 들어 API 응답 데이터가 여러 사용자의 데이터를 객체로 반환하는 경우, 각 사용자 속성의 타입을 인덱스 시그니처로 정의할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">ApiResponseData&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">userId&lt;/span>: &lt;span class="kt">string&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="nx">name&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="nx">email&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="nx">lastLogin&lt;/span>: &lt;span class="kt">Date&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="kr">const&lt;/span> &lt;span class="nx">userData&lt;/span>: &lt;span class="kt">ApiResponseData&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">user_123&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Alice&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">email&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;alice@example.com&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">lastLogin&lt;/span>: &lt;span class="kt">new&lt;/span> &lt;span class="nb">Date&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">user_456&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Bob&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">email&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;bob@example.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">lastLogin&lt;/span>: &lt;span class="kt">new&lt;/span> &lt;span class="nb">Date&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="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>설정 객체:&lt;/strong>&lt;/p>
&lt;p>설정 값이 너무 많아서 일일이 타입으로 선언하기 어려운 경우, 대략적으로 허용되는 값들을 인덱스 시그니처로 정의할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">AppConfig&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">feature&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kr">boolean&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="o">|&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">theme&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;light&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;dark&amp;#34;&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">version&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">const&lt;/span> &lt;span class="nx">config&lt;/span>: &lt;span class="kt">AppConfig&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">theme&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;dark&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">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;span class="line">&lt;span class="cl"> &lt;span class="nx">enableNotifications&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">maxRetries&lt;/span>: &lt;span class="kt">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">apiEndpoint&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;https://api.example.com&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;h3 id="제약사항과-주의점">제약사항과 주의점&lt;/h3>
&lt;p>&lt;strong>1. Record 타입 사용 권장:&lt;/strong>&lt;/p>
&lt;p>객체의 타입을 정의할 때는 인덱스 시그니처 보다 Record 타입을 사용하는 것이 더 간결하고 명확합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 인덱스 시그니처 대신 Record 타입 사용
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">interface&lt;/span> &lt;span class="nx">Person&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&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="nx">age&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">isStudent&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="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">people&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">Person&lt;/span>&lt;span class="p">&amp;gt;&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">john&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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">jane&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">25&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 타입 안전성 한계:&lt;/strong>&lt;/p>
&lt;p>런타임에 실제로 존재하지 않는 속성도 컴파일 타입에는 오류 없이 접근할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">FlexibleObject&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">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="o">|&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">const&lt;/span> &lt;span class="nx">obj&lt;/span>: &lt;span class="kt">FlexibleObject&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">30&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">// 컴파일 타임에는 에러가 없지만 런타임에 undefined일 수 있음
&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="nx">obj&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nonExistentKey&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// undefined (타입상으로는 string | number)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="인덱스-액세스-타입-index-access-types">인덱스 액세스 타입 (Index Access Types)&lt;/h2>
&lt;h3 id="기본-문법과-사용법">기본 문법과 사용법&lt;/h3>
&lt;p>인덱스 액세스 타입은 &lt;strong>기존 타입에서 특정 속성의 타입을 추출&lt;/strong>할 때 사용합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">Car&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">make&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="nx">model&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="nx">year&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">color&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">red&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="nx">green&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="nx">blue&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 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">type&lt;/span> &lt;span class="nx">CarMake&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Car&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;make&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">CarColor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Car&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// { red: string; green: string; blue: string; }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">CarRedValue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Car&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;red&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// string
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">CarBasicInfo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Car&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;make&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// string | string = string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">CarMixedInfo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Car&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;make&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;year&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// string | number
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="typeof와-keyof-연산자">typeof와 keyof 연산자&lt;/h3>
&lt;p>이제 처음에 보여드렸던 복잡한 코드를 단계별로 분석해보겠습니다:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">Direction&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">Up&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;up&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">Down&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;down&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">Left&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;left&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">Right&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;right&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">as&lt;/span> &lt;span class="kr">const&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">DirectionType&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 코드를 이해하기 위해 IDE의 도움을 받으면서 하나씩 분석해보겠습니다.&lt;/p>
&lt;p>&lt;strong>1단계: typeof 연산자&lt;/strong>&lt;/p>
&lt;p>&lt;code>typeof&lt;/code> 연산자는 객체 등의 값의 타입을 추론해서 반환합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionObject&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&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">// readonly Up: &amp;#34;up&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// readonly Down: &amp;#34;down&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// readonly Left: &amp;#34;left&amp;#34;;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// readonly Right: &amp;#34;right&amp;#34;;
&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;/code>&lt;/pre>&lt;/div>&lt;p>Direction이 const로 선언되었으므로, 각 속성이 readonly로 나타나고 있습니다.&lt;/p>
&lt;p>&lt;strong>2단계: keyof 연산자&lt;/strong>&lt;/p>
&lt;p>&lt;code>keyof&lt;/code> 연산자는 타입의 모든 키를 유니온으로 추출합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionKeys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 결과: &amp;#34;Up&amp;#34; | &amp;#34;Down&amp;#34; | &amp;#34;Left&amp;#34; | &amp;#34;Right&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3단계: 인덱스 액세스 타입으로 값 추출&lt;/strong>&lt;/p>
&lt;p>이제 인덱스 엑세스 타입으로 각 속성의 타입을 추출해보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionTypeUp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="s2">&amp;#34;Up&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// &amp;#34;up&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionTypeX&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="s2">&amp;#34;Left&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;Right&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// &amp;#34;left&amp;#34; | &amp;#34;right&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionTypeAll&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="s2">&amp;#34;Up&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;Down&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;Left&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;Right&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// &amp;#34;up&amp;#34; | &amp;#34;down&amp;#34; | &amp;#34;left&amp;#34; | &amp;#34;right&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>특정 속성만을 지정할 수도 있고, 유니온 타입으로 여러 속성을 지정할 수도 있습니다. 마지막의 &lt;code>DirectionTypeAll&lt;/code>은 모든 속성을 유니온 타입으로 지정한 것인데요, 직관적이긴 하지만 속성 개수가 많아지면 이렇게 작성하긴 어려울 겁니다.&lt;/p>
&lt;p>여기서 &lt;code>keyof&lt;/code> 연산자를 사용해서 타입의 모든 속성을 유니온 타입으로 추출할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">AllKeys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// &amp;#34;Up&amp;#34; | &amp;#34;Down&amp;#34; | &amp;#34;Left&amp;#34; | &amp;#34;Right&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionTypeAll&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="nx">AllKeys&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// &amp;#34;up&amp;#34; | &amp;#34;down&amp;#34; | &amp;#34;left&amp;#34; | &amp;#34;right&amp;#34;
&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-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">type&lt;/span> &lt;span class="nx">DirectionType&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// &amp;#34;up&amp;#34; | &amp;#34;down&amp;#34; | &amp;#34;left&amp;#34; | &amp;#34;right&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="배열과-튜플에서의-사용">배열과 튜플에서의 사용&lt;/h3>
&lt;p>객체 뿐만 아니라 배열과 튜플같이 인덱스로 요소를 접근하는 타입에서도 인덱스 액세스 타입을 사용할 수 있습니다.&lt;/p>
&lt;p>&lt;strong>배열에서의 인덱스 액세스:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">arr&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isStudent&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 class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">25&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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent: boolean;
&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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent?: boolean;
&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">type&lt;/span> &lt;span class="nx">PersonArray&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">arr&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="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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent: boolean;
&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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent?: boolean;
&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">type&lt;/span> &lt;span class="nx">Person&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&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="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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent?: boolean;
&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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent?: boolean;
&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">type&lt;/span> &lt;span class="nx">Person2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">1&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="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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent: boolean;
&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"> * name: string;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * age: number;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * isStudent?: boolean;
&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">type&lt;/span> &lt;span class="nx">PersonAll&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">arr&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="kt">number&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 배열 타입의 모든 요소 타입
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>흥미로운 점은 배열 타입은 모든 요소의 타입이 동일하다는 점입니다. 그래서 특정 인덱스의 요소 타입을 사용하든, 모든 요소의 타입을 사용하든 상관없이 타입이 동일합니다.&lt;/p>
&lt;p>&lt;strong>튜플에서의 인덱스 액세스:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">tuple&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&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 class="kc">true&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kr">const&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">FirstElement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">tuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">SecondElement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">tuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// &amp;#34;hello&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">ThirdElement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">tuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">AllElements&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">tuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="kt">number&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 1 | &amp;#34;hello&amp;#34; | true
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>튜플은 배열과 다르게 각 인덱스마다 서로 다른 타입을 가질 수 있습니다!&lt;/p>
&lt;p>&lt;strong>배열 vs 튜플 비교:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">numbers&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&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">type&lt;/span> &lt;span class="nx">NumberArrayElement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">numbers&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// number
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">NumberArrayAny&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">numbers&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="kt">number&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// number
&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>&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">mixedTuple&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">number&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">boolean&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&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 class="mi">42&lt;/span>&lt;span class="p">,&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="kr">type&lt;/span> &lt;span class="nx">TupleFirst&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">mixedTuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">TupleSecond&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">mixedTuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// number
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">TupleUnion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">mixedTuple&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="kt">number&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// string | number | boolean
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="다양한-활용-방법">다양한 활용 방법&lt;/h2>
&lt;p>이제 실제로 인덱스 엑세스 타입을 사용하는 예시를 살펴보겠습니다.&lt;/p>
&lt;h3 id="api-응답-타입에서-특정-데이터-추출">API 응답 타입에서 특정 데이터 추출&lt;/h3>
&lt;p>API 응답 타입에서 특정 데이터를 추출하는 경우 인덱스 엑세스 타입을 사용할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">ApiResponse&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">users&lt;/span>: &lt;span class="kt">Array&lt;/span>&lt;span class="o">&amp;lt;&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">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&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="nx">email&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="nx">profile&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">avatar&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="nx">bio&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 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">meta&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">total&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">page&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 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">User&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">ApiResponse&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;users&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&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="kr">type&lt;/span> &lt;span class="nx">UserProfile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;profile&amp;#34;&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="kr">type&lt;/span> &lt;span class="nx">MetaInfo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">ApiResponse&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;meta&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 메타 정보 타입
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="설정-객체에서-특정-설정-타입-추출">설정 객체에서 특정 설정 타입 추출&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">appConfig&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">database&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">host&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;localhost&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">port&lt;/span>: &lt;span class="kt">5432&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;myapp&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">cache&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">ttl&lt;/span>: &lt;span class="kt">3600&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">maxSize&lt;/span>: &lt;span class="kt">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="nx">features&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">enableNewUI&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">enableBetaFeatures&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="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="kr">const&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">DatabaseConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">appConfig&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="s2">&amp;#34;database&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">type&lt;/span> &lt;span class="nx">CacheConfig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">appConfig&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="s2">&amp;#34;cache&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">type&lt;/span> &lt;span class="nx">FeatureFlags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">appConfig&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="s2">&amp;#34;features&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="함수-파라미터와-반환-타입-추출">함수 파라미터와 반환 타입 추출&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">processUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">age&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processed&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">user&lt;/span>: &lt;span class="kt">user&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">timestamp&lt;/span>: &lt;span class="kt">new&lt;/span> &lt;span class="nb">Date&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="kr">type&lt;/span> &lt;span class="nx">UserParam&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Parameters&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">typeof&lt;/span> &lt;span class="na">processUser&lt;/span>&lt;span class="p">&amp;gt;[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// { id: string; name: string; age: number }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">ProcessResult&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">ReturnType&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">typeof&lt;/span> &lt;span class="na">processUser&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span> &lt;span class="c1">// { processed: boolean; user: { id: string; name: string; age: number; }; timestamp: Date }
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="조건부-타입과의-활용">조건부 타입과의 활용&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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"> * 함수 타입에서 반환 타입이 Promise인지 확인하고 언래핑
&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"> * Promise 타입인 경우, U의 타입을 추론해서 U를 리턴합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * Promise 타입이 아닌 경우, T를 리턴합니다.
&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">type&lt;/span> &lt;span class="nx">UnwrapPromise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">T&lt;/span> &lt;span class="kr">extends&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">infer&lt;/span> &lt;span class="na">U&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">U&lt;/span> : &lt;span class="kt">T&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">interface&lt;/span> &lt;span class="nx">ServiceMethods&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">getUser&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">name&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="nx">getUserSync&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">name&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="nx">deleteUser&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">id&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="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;;&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">UserData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UnwrapPromise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ReturnType&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ServiceMethods&lt;/span>&lt;span class="err">[&lt;/span>&lt;span class="s2">&amp;#34;getUser&amp;#34;&lt;/span>&lt;span class="err">]&lt;/span>&lt;span class="p">&amp;gt;&amp;gt;;&lt;/span> &lt;span class="c1">// { id: string; name: string }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">UserDataSync&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">UnwrapPromise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ReturnType&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ServiceMethods&lt;/span>&lt;span class="err">[&lt;/span>&lt;span class="s2">&amp;#34;getUserSync&amp;#34;&lt;/span>&lt;span class="err">]&lt;/span>&lt;span class="p">&amp;gt;&amp;gt;;&lt;/span> &lt;span class="c1">// { id: string; name: string }
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 에제에서는 getUser 함수의 반환 타입이 &lt;code>Promise&amp;lt;{ id: string; name: string }&amp;gt;&lt;/code> 이므로, U의 타입으로 &lt;code>{ id: string; name: string }&lt;/code> 을 추론해서 &lt;code>UnwrapPromise&lt;/code> 타입은 &lt;code>{ id: string; name: string }&lt;/code> 을 리턴합니다.&lt;/p>
&lt;hr>
&lt;h2 id="권장-사항">권장 사항&lt;/h2>
&lt;h3 id="명시적-인터페이스-vs-인덱스-시그니처">명시적 인터페이스 vs 인덱스 시그니처&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">interface&lt;/span> &lt;span class="nx">BadConfig&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">key&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">any&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">interface&lt;/span> &lt;span class="nx">GoodConfig&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">apiUrl&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="nx">timeout&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">retries&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 class="nx">feature&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kt">number&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kr">boolean&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="record-타입-활용">Record 타입 활용&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">interface&lt;/span> &lt;span class="nx">UserPermissions&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">userId&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;read&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;write&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;admin&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">// ✅ Record 타입 사용
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">type&lt;/span> &lt;span class="nx">UserPermissions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;read&amp;#34;&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="s2">&amp;#34;write&amp;#34;&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="s2">&amp;#34;admin&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="타입-안전성을-위한-검증">타입 안전성을 위한 검증&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">getProperty&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">T&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">K&lt;/span> &lt;span class="na">extends&lt;/span> &lt;span class="na">keyof&lt;/span> &lt;span class="na">T&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="nx">obj&lt;/span>: &lt;span class="kt">T&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">key&lt;/span>: &lt;span class="kt">K&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">T&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">K&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">obj&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&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">const&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&lt;/span>: &lt;span class="kt">30&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">userName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">getProperty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// string (타입 안전)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// const invalid = getProperty(user, &amp;#39;invalid&amp;#39;); // 컴파일 에러
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="성능-고려사항">성능 고려사항&lt;/h3>
&lt;p>&lt;strong>1. 컴파일 시간:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>복잡한 인덱스 액세스 타입은 컴파일 시간을 증가시킬 수 있습니다.&lt;/li>
&lt;li>필요한 경우에만 사용하고, 가능하면 명시적 타입을 정의해야 합니다.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>2. 런타임 영향 없음:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>모든 타입 정보는 컴파일 시에만 존재하기 때문에 런타임에는 아무런 영향을 주지 않습니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="요약">요약&lt;/h2>
&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;code>[key: string]: ValueType&lt;/code>&lt;/td>
&lt;td>동적 키 객체 정의&lt;/td>
&lt;td>&lt;code>{ [userId: string]: User }&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>기본 인덱스 액세스&lt;/td>
&lt;td>&lt;code>Type[Key]&lt;/code>&lt;/td>
&lt;td>속성 타입 추출&lt;/td>
&lt;td>&lt;code>User['name']&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>typeof 인덱스 액세스&lt;/td>
&lt;td>&lt;code>(typeof value)[Key]&lt;/code>&lt;/td>
&lt;td>값에서 속성 타입 추출&lt;/td>
&lt;td>&lt;code>(typeof config)['api']&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>keyof와 조합&lt;/td>
&lt;td>&lt;code>Type[keyof Type]&lt;/code>&lt;/td>
&lt;td>모든 속성값의 유니온&lt;/td>
&lt;td>&lt;code>User[keyof User]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>배열 요소 타입&lt;/td>
&lt;td>&lt;code>ArrayType[number]&lt;/code>&lt;/td>
&lt;td>배열 요소 타입 추출&lt;/td>
&lt;td>&lt;code>Users[number]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>튜플 특정 요소&lt;/td>
&lt;td>&lt;code>TupleType[Index]&lt;/code>&lt;/td>
&lt;td>튜플 특정 위치 타입&lt;/td>
&lt;td>&lt;code>[string, number][0]&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&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;code>typeof&lt;/code>&lt;/td>
&lt;td>값 (value)&lt;/td>
&lt;td>타입 (type)&lt;/td>
&lt;td>&lt;code>typeof user&lt;/code> → &lt;code>{ name: string; age: number }&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>keyof&lt;/code>&lt;/td>
&lt;td>타입 (type)&lt;/td>
&lt;td>키 유니온 (key union)&lt;/td>
&lt;td>&lt;code>keyof User&lt;/code> → &lt;code>&amp;quot;name&amp;quot; | &amp;quot;age&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Type[Key]&lt;/code>&lt;/td>
&lt;td>타입, 키&lt;/td>
&lt;td>속성 타입&lt;/td>
&lt;td>&lt;code>User[&amp;quot;name&amp;quot;]&lt;/code> → &lt;code>string&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="사용-시나리오별-권장사항">사용 시나리오별 권장사항&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>시나리오&lt;/th>
&lt;th>권장 방법&lt;/th>
&lt;th>이유&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>API 응답 타입 정의&lt;/td>
&lt;td>명시적 인터페이스 + 필요시 인덱스 액세스&lt;/td>
&lt;td>타입 안전성과 가독성&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>설정 객체&lt;/td>
&lt;td>Record 타입 또는 명시적 속성 + 인덱스 시그니처&lt;/td>
&lt;td>유연성과 타입 안전성 균형&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>상수 객체에서 값 타입 추출&lt;/td>
&lt;td>&lt;code>as const&lt;/code> + &lt;code>typeof&lt;/code> + &lt;code>keyof&lt;/code> 조합&lt;/td>
&lt;td>리터럴 타입 보장&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>배열/튜플 요소 타입&lt;/td>
&lt;td>&lt;code>ArrayType[number]&lt;/code> 또는 &lt;code>TupleType[Index]&lt;/code>&lt;/td>
&lt;td>정확한 요소 타입 추출&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="정리">정리&lt;/h2>
&lt;h3 id="핵심-개념-정리">핵심 개념 정리&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>인덱스 시그니처&lt;/strong>는 동적인 키를 가진 객체의 타입을 정의할 때 사용합니다&lt;/li>
&lt;li>&lt;strong>인덱스 액세스 타입&lt;/strong>은 기존 타입에서 특정 속성의 타입을 추출할 때 사용합니다&lt;/li>
&lt;li>&lt;strong>typeof 연산자&lt;/strong>는 값의 타입을 추론하고, &lt;strong>keyof 연산자&lt;/strong>는 타입의 모든 키를 추출합니다&lt;/li>
&lt;li>배열에서는 모든 요소가 같은 타입을 가지지만, 튜플에서는 각 위치마다 다른 타입을 가질 수 있습니다&lt;/li>
&lt;/ol>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html">Indexed Access Types&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/2/typeof-types.html">Typeof Type Operator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/2/keyof-types.html">Keyof Type Operator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://alexop.dev/posts/exploring-the-power-of-square-brackets-in-typescript/">Exploring the Power of Square Brackets in TypeScript&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://refine.dev/blog/typescript-keyof/#typescript-keyof-with-generic-type-mappers">TypeScript keyof with Examples&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dev.to/leapcell/a-deep-dive-into-typescripts-infer-keyword-1o4b">A Deep Dive into TypeScript&amp;rsquo;s infer Keyword&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Typescript: erasableSyntaxOnly</title><link>https://netscout.github.io/posts/typescript-erasablesyntaxonly/</link><pubDate>Sat, 19 Jul 2025 23:47:01 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-erasablesyntaxonly/</guid><description>최근에 몇가지 시험해볼 게 있어서 프로토타이핑을 진행할 일이 있었습니다. 평소 처럼 리액트 프로젝트를 새로 구성하고 이것저것 코딩을 하다가 처음보는 에러를 발견했습니다.</description><content>&lt;hr>
&lt;p>최근에 몇가지 시험해볼 게 있어서 프로토타이핑을 진행할 일이 있었습니다. 평소 처럼 리액트 프로젝트를 새로 구성하고 이것저것 코딩을 하다가 처음보는 에러를 발견했습니다.&lt;/p>
&lt;p align="center">&lt;img src="./01.png" alt="erasableSyntaxOnly" />&lt;/p>
&lt;p>&lt;code>erasableSyntaxOnly&lt;/code>&amp;hellip; 이건 뭘까요? 잠시 에러를 쳐다보다가 찾아보기 시작했습니다.&lt;/p>
&lt;p>그러니까 이 &lt;code>erasableSyntaxOnly&lt;/code>는 &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#the---erasablesyntaxonly-option">Typescript 5.8에 추가된 컴파일러 옵션&lt;/a>이었습니다.&lt;/p>
&lt;p>이 옵션이 추가된 배경은 뭔지, 그래서 뭘 어떻게 바꿔야 하는지 하나씩 알아보겠습니다.&lt;/p>
&lt;h2 id="옵션이-추가된-배경">옵션이 추가된 배경&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>런타임&lt;/th>
&lt;th>TypeScript 지원 상태&lt;/th>
&lt;th>특징&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Bun.js&lt;/td>
&lt;td>✅ 네이티브 지원&lt;/td>
&lt;td>초기부터 TypeScript 실행 지원&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Deno&lt;/td>
&lt;td>✅ 네이티브 지원&lt;/td>
&lt;td>TypeScript 우선 설계&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Node.js (이전)&lt;/td>
&lt;td>❌ 별도 도구 필요&lt;/td>
&lt;td>&lt;code>tsc&lt;/code>, &lt;code>ts-node&lt;/code> 등 필요&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Node.js 22.6.0+&lt;/td>
&lt;td>✅ 실험적 지원&lt;/td>
&lt;td>&lt;code>--experimental-strip-types&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Node.js 23.6.0+&lt;/td>
&lt;td>✅ 기본 지원&lt;/td>
&lt;td>별도 플래그 불필요&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>원래 node.js는 Typescript 코드를 실행하는 기능이 없었습니다. 그래서 &lt;code>tsc&lt;/code>를 통해 코드를 트랜스파일링 해서 Javascript로 변환해서 실행하거나 &lt;code>ts-node&lt;/code>같은 도구를 사용했었죠. 하지만 &lt;code>Bun.js&lt;/code>나 &lt;code>Deno&lt;/code> 같이 Typescript 실행을 지원하는 런타임이 나오면서 node.js가 좀 뒤쳐지는 모양새가 되었습니다. 그러던 중에 node.js 22.6.0에서 Typescript 실행을 지원하는 &lt;a href="https://github.com/nodejs/node/releases/tag/v22.6.0">&amp;ndash;experimental-strip-types 플래그가 추가되었고&lt;/a> , &lt;a href="https://github.com/nodejs/node/releases/tag/v23.6.0">node.js 23.6.0에서 별도의 설정없이 Typescript 코드를 실행할 수 있게 되었습니다.&lt;/a>&lt;/p>
&lt;p>물론 아직까지는 &lt;a href="https://nodejs.org/api/typescript.html#type-stripping">몇가지 제약사항이 있습니다.&lt;/a>&lt;/p>
&lt;h2 id="제약사항">제약사항&lt;/h2>
&lt;p>node.js는 Typescript 코드를 실행하기 위해 타입을 제거하는 과정(type stripping)을 거칩니다. 이 과정은 Typescript 컴파일러의 트랜스파일 없이 진행되는데요, 이렇게 하면 몇가지 이점이 있습니다. 우선 타입 제거와 트랜스파일링을 비교해볼까요?&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>특징&lt;/th>
&lt;th>타입 스트리핑&lt;/th>
&lt;th>전통적 트랜스파일링&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&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>소스 맵&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>디버깅&lt;/strong>&lt;/td>
&lt;td>직관적 (WYSIWYG)&lt;/td>
&lt;td>소스 맵 의존&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>지원 문법&lt;/strong>&lt;/td>
&lt;td>제한적 (erasable만)&lt;/td>
&lt;td>모든 TS 문법&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>소스 맵이 필요없음: 코드의 구조가 전혀 바뀌지 않기 때문에, 소스 맵을 생성할 필요가 없으며 직관적인 디버깅이 가능합니다.&lt;/li>
&lt;li>성능 최적화: 컴파일 과정이 없기 때문에, 더 빠른 실행 속도를 기대할 수 있습니다.&lt;/li>
&lt;li>실행 과정이 직관적: 타입을 제외하고 작성된 코드 그대로(WYSIWYG) 실행됩니다.&lt;/li>
&lt;/ul>
&lt;p>다만, 컴파일 과정을 거치지 않기 때문에 몇가지 제약사항이 생깁니다. Typescript 컴파일러가 특정 기능을 지원하기 위해 Javascript로 변환하는 과정에서 뭔가 코드를 생성해야 한다면 어떨까요? 앞서 말씀 드렸듯이 node.js는 Typescript 코드를 타입만을 제거하고 있는 그대로 실행합니다. 그렇기 때문에 이런 기능들은 더 이상 사용할 수 없으며 다른 방법을 찾아야 합니다.&lt;/p>
&lt;p>이 글을 시작하면서 보여드렸던 enum이 이런 제약사항에 가장 큰 영향을 받는 기능입니다. enum을 중심으로 좀 더 자세히 알아보죠!&lt;/p>
&lt;h3 id="enum으로-제약사항-알아보기">enum으로 제약사항 알아보기&lt;/h3>
&lt;p>우선 다음과 같이 tsconfig.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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;erasableSyntaxOnly&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;outDir&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;./dist&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>그리고 다음과 같이 enum을 선언해보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// enum/enum.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">enum&lt;/span> &lt;span class="nx">Direction&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">UP&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DOWN&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">LEFT&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">RIGHT&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npx tsc
&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-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// dist/enum.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">var&lt;/span> &lt;span class="nx">Direction&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Direction&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">Direction&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="nx">Direction&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;UP&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;UP&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">Direction&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="nx">Direction&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;DOWN&amp;#34;&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 class="o">=&lt;/span> &lt;span class="s2">&amp;#34;DOWN&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">Direction&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="nx">Direction&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;LEFT&amp;#34;&lt;/span>&lt;span class="p">]&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="s2">&amp;#34;LEFT&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">Direction&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="nx">Direction&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;RIGHT&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;RIGHT&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="nx">Direction&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Direction&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>결과에서 볼 수 있듯이 enum은 원래 Javascript에서 제공되지 않으므로 Typescript 컴파일러가 트랜스파일 하는 과정에서 Javascript 코드를 생성합니다. 하지만 node.js에서 이 코드를 바로 실행하기 위해 &lt;code>erasableSyntaxOnly&lt;/code> 옵션을 true로 설정하면, Typescript 컴파일러는 enum에 대해 불평을 쏟아냅니다.&lt;/p>
&lt;p align="center">&lt;img src="./02.png" alt="erasableSyntaxOnly" />&lt;/p>
&lt;h4 id="해결-방법">해결 방법&lt;/h4>
&lt;p>&lt;code>erasableSyntaxOnly&lt;/code> 옵션은 이름 그대로 &lt;code>타입을 제거하고 실행할 때 제거될 수 있는 문법들만 허용하겠다&lt;/code> 그래서 &lt;code>node.js가 직접 Typescript를 실행할 수 있는 코드만 허용하겠다&lt;/code>는 의미입니다.&lt;/p>
&lt;p>enum은 별도의 코드를 생성해야 하므로 사용할 수 없으므로, 다음과 같이 별도의 과정없이 사용할 수 있는 형태로 변경해야 합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// enum/enum.ts
&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">// enum Direction {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// UP,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// DOWN,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// LEFT,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// RIGHT,
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// erasableSyntaxOnly 옵션에서 사용가능한 enum
&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">Direction&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">Up&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;up&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">Down&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;down&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">Left&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;left&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">Right&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;right&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">as&lt;/span> &lt;span class="kr">const&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">DirectionType&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="k">keyof&lt;/span> &lt;span class="k">typeof&lt;/span> &lt;span class="nx">Direction&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">direction&lt;/span>: &lt;span class="kt">DirectionType&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Up&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="nx">direction&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>enum 대신에 객체 리터럴(literal)을 사용해서 enum을 대신할 수 있도록 합니다. 컴파일된 결과를 확인해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// dist/enum.js
&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">// enum Direction {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// UP,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// DOWN,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// LEFT,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// RIGHT,
&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">// erasableSyntaxOnly 옵션에서 사용가능한 enum
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">var&lt;/span> &lt;span class="nx">Direction&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">Up&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;up&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">Down&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;down&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">Left&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;left&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">Right&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;right&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="kd">var&lt;/span> &lt;span class="nx">direction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Up&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="nx">direction&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node dist/enum.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>type DirectionType = (typeof Direction)[keyof typeof Direction];&lt;/code> 이 부분이 상당히 혼란스러운데요. 이 부분은 다음과 같이 해석할 수 있습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;code>typeof Direction&lt;/code>: Direction은 객체 리터럴이고 다음과 같은 타입입니다.
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">readonly&lt;/span> &lt;span class="nx">Up&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;up&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">readonly&lt;/span> &lt;span class="nx">Down&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;down&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">readonly&lt;/span> &lt;span class="nx">Left&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;left&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">readonly&lt;/span> &lt;span class="nx">Right&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;right&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;/li>
&lt;li>&lt;code>keyof typeof Direction&lt;/code>: 위 객체의 키들을 의미합니다. 즉, &lt;code>&amp;quot;Up&amp;quot; | &amp;quot;Down&amp;quot; | &amp;quot;Left&amp;quot; | &amp;quot;Right&amp;quot;&lt;/code> 입니다.&lt;/li>
&lt;li>&lt;code>(typeof Direction)[keyof typeof Direction]&lt;/code>: 인덱스 엑세스 타입을 의미하는데요, Direction 객체에서 사용가능한 모든 값을 유니온 타입으로 반환합니다. 즉, &lt;code>&amp;quot;up&amp;quot; | &amp;quot;down&amp;quot; | &amp;quot;left&amp;quot; | &amp;quot;right&amp;quot;&lt;/code> 입니다.&lt;/li>
&lt;/ul>
&lt;p>조금 복잡해 보이긴 하지만, 이렇게 함으로써 다음과 같은 효과를 얻을 수 있습니다.&lt;/p>
&lt;ul>
&lt;li>타입 안전성: enum과 마찬가지로 컴파일 타임에 타입 체크가 지원됩니다.&lt;/li>
&lt;li>IDE 지원: 타입 정보가 있으니까 IDE가 자동 완성을 지원합니다.&lt;/li>
&lt;li>소스 맵이 필요없음: 삭제되는 코드는 빈칸으로 남겨서 라인 수를 유지하기 때문에 소스 맵을 생성 및 사용할 필요가 없습니다.&lt;/li>
&lt;li>삭제가능!: 타입 정보만 삭제하고 바로 실행 가능합니다.&lt;/li>
&lt;li>디버그 직관성: WYSIWYG(What You See Is What You Get). 코드를 보면 바로 이해할 수 있습니다.&lt;/li>
&lt;/ul>
&lt;h4 id="좀-더-간단한-버전">좀 더 간단한 버전&lt;/h4>
&lt;p>만약, &lt;code>Direction.Up&lt;/code> 대신에 문자열 리터럴을 그대로 사용해도 된다면, 다음과 같이 유니온 타입으로 더 간단하게 사용할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">type&lt;/span> &lt;span class="nx">Direction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;up&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;down&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;left&amp;#34;&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;right&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">const&lt;/span> &lt;span class="nx">direction&lt;/span>: &lt;span class="kt">Direction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;up&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="nx">direction&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>타입 정보를 활용할 수 있기 때문에 다음과 같이 IDE의 도움을 받을 수도 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./03.png" alt="erasableSyntaxOnly" />&lt;/p>
&lt;h4 id="enum-대안-비교표">Enum 대안 비교표&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>방법&lt;/th>
&lt;th>문법 복잡도&lt;/th>
&lt;th>타입 안전성&lt;/th>
&lt;th>IDE 지원&lt;/th>
&lt;th>런타임 비용&lt;/th>
&lt;th>추천도&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>기존 enum&lt;/strong>&lt;/td>
&lt;td>⭐⭐&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>높음 (객체 생성)&lt;/td>
&lt;td>❌ (erasableSyntaxOnly에서 불가)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>const 객체 + as const&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>낮음&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>string literal union&lt;/strong>&lt;/td>
&lt;td>⭐&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;td>없음&lt;/td>
&lt;td>⭐⭐⭐&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="그외-제약사항들">그외 제약사항들&lt;/h3>
&lt;p>클래스를 정의할 때 속성을 정의하는 대신에 생성자의 파라미터로 대신하는 패턴은 사용할 수 없습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ❌ erasableSyntaxOnly 옵션에서는 이렇게 하면 안됩니다!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">class&lt;/span> &lt;span class="nx">Person&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">private&lt;/span> &lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">public&lt;/span> &lt;span class="nx">age&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="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">class&lt;/span> &lt;span class="nx">Person&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">private&lt;/span> &lt;span class="nx">name&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="kr">public&lt;/span> &lt;span class="nx">age&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">age&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">age&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">age&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>그리고 commonjs 스타일의 임포트 구문은 &lt;code>erasableSyntaxOnly&lt;/code> 옵션에서 사용할 수 없습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ❌ erasableSyntaxOnly 옵션에서는 이렇게 하면 안됩니다!
&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="nx">foo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;foo&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">import&lt;/span> &lt;span class="nx">foo&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;foo&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="erasablesyntaxonly-제약사항-요약">erasableSyntaxOnly 제약사항 요약&lt;/h4>
&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;strong>enum&lt;/strong>&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>런타임 코드 생성 필요&lt;/td>
&lt;td>&lt;code>const&lt;/code> 객체 + &lt;code>as const&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>클래스 파라미터 속성&lt;/strong>&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>자동 속성 할당 코드 생성&lt;/td>
&lt;td>명시적 속성 선언&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>CommonJS import&lt;/strong>&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>import 변환 필요&lt;/td>
&lt;td>ES6 import&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="하지만-typescript-기능을-활용할-수-있는-방법은-있습니다">하지만, Typescript 기능을 활용할 수 있는 방법은 있습니다.&lt;/h3>
&lt;p>&lt;a href="https://github.com/nodejs/node/releases/tag/v22.7.0">node.js 22.7.0에서 추가된&lt;/a> &lt;code>--experimental-transform-types&lt;/code> 플래그를 사용하면 Typescript 에서만 제공되는 enum 같은 문법을 그대로 사용할 수도 있습니다!&lt;/p>
&lt;p>테스트를 위해 일단 &lt;code>erasableSyntaxOnly&lt;/code> 옵션을 비활성화 하고 enum 코드를 실행해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// enum/enum.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">enum&lt;/span> &lt;span class="nx">Direction&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">UP&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DOWN&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">LEFT&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">RIGHT&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">const&lt;/span> &lt;span class="nx">direction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Direction&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">UP&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="nx">direction&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; node --experimental-transform-types enum/enum.ts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>node:63565&lt;span class="o">)&lt;/span> ExperimentalWarning: Type Stripping is an experimental feature and might change at any &lt;span class="nb">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>Use &lt;span class="sb">`&lt;/span>node --trace-warnings ...&lt;span class="sb">`&lt;/span> to show where the warning was created&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>불평을 하긴 하지만, 정상적으로 실행되는 걸 확인할 수 있습니다. 이 옵션을 사용하면 내부적으로 &lt;a href="https://www.npmjs.com/package/amaro">Amaro&lt;/a> 를 활용해서 소스코드를 해석하고 변환한다고 합니다. 따라서 enum과 같은 Typescript 전용 문법도 사용할 수 있고, 디버깅을 위한 소스맵도 생성됩니다. 하지만 실행을 위해 코드를 변환해야 하니까 아무래도 성능은 떨어지겠죠?&lt;/p>
&lt;h4 id="nodejs-typescript-실행-옵션-비교">Node.js TypeScript 실행 옵션 비교&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>플래그&lt;/th>
&lt;th>추가 버전&lt;/th>
&lt;th>처리 방식&lt;/th>
&lt;th>enum 지원&lt;/th>
&lt;th>성능&lt;/th>
&lt;th>소스 맵&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>--experimental-strip-types&lt;/code>&lt;/td>
&lt;td>22.6.0&lt;/td>
&lt;td>타입만 제거&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>빠름&lt;/td>
&lt;td>불필요&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--experimental-transform-types&lt;/code>&lt;/td>
&lt;td>22.7.0&lt;/td>
&lt;td>Amaro 변환&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>보통&lt;/td>
&lt;td>생성됨&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="권장사항">권장사항&lt;/h3>
&lt;p>앞서 설명했듯이 &lt;code>erasableSyntaxOnly&lt;/code> 옵션은 타입을 제거하고 실행할 수 있는 문법만 허용합니다. Typescript 컴파일러가 코드를 변환하는 작업이 없어야 한다는 의미이며, 임포트 생략(import elision) 또한 여기에 포함됩니다. Typescript가 임의로 임포트를 생략하지 않도록 &lt;code>verbatimModuleSyntax&lt;/code> 옵션을 같이 사용하는 게 좋습니다. &lt;a href="https://raw.githubusercontent.com/netscout/typescript-studies/main/src/type-only-import/README.md">임포트 생략과 &lt;code>verbatimModuleSyntax&lt;/code> 옵션에 대해서 다룬 글&lt;/a>이 있으니 관심 있으신 분은 참고바랍니다.&lt;/p>
&lt;h3 id="한-가지-더-type-as-comments">한 가지 더: Type as Comments&lt;/h3>
&lt;p>Javascript 표준에서도 타입을 지원하기 위한 논의가 진행되고 있습니다. &lt;a href="https://github.com/tc39/proposal-type-annotations">TC39: Type as Comments&lt;/a> 를 보면, Typescript와 유사한 형태로 타입을 선언할 수 있도록 제안이 진행되고 있습니다. 여기서도 타입은 역시 런타임에서는 무시되는 형태인데요, Typescript가 가고자 하는 방향도 이와 맞닿아 있는 걸로 보입니다.&lt;/p>
&lt;h2 id="요약">요약&lt;/h2>
&lt;h3 id="erasablesyntaxonly-핵심-정리">erasableSyntaxOnly 핵심 정리&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>항목&lt;/th>
&lt;th>내용&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>목적&lt;/strong>&lt;/td>
&lt;td>Node.js의 네이티브 TypeScript 실행 지원&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>원리&lt;/strong>&lt;/td>
&lt;td>타입만 제거하고 코드 변환 없이 실행&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>주요 장점&lt;/strong>&lt;/td>
&lt;td>⚡ 빠른 실행, 🚫 소스 맵 불필요, 🎯 직관적 디버깅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>주요 제약&lt;/strong>&lt;/td>
&lt;td>enum, 클래스 파라미터 속성, CommonJS import 사용 불가&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>권장 설정&lt;/strong>&lt;/td>
&lt;td>&lt;code>erasableSyntaxOnly: true&lt;/code> + &lt;code>verbatimModuleSyntax: true&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="마이그레이션-체크리스트">마이그레이션 체크리스트&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;code>enum&lt;/code> → &lt;code>const&lt;/code> 객체 + &lt;code>as const&lt;/code> 또는 string literal union으로 변경&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 클래스 파라미터 속성 → 명시적 속성 선언으로 변경&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;code>import foo = require()&lt;/code> → ES6 &lt;code>import&lt;/code> 구문으로 변경&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;code>verbatimModuleSyntax: true&lt;/code> 옵션 추가&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;code>erasableSyntaxOnly: true&lt;/code> 옵션 설정&lt;/li>
&lt;/ul>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#the---erasablesyntaxonly-option">TypeScript 5.8 릴리즈 노트&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/nodejs/node/releases/tag/v22.6.0">Node.js 22.6.0 릴리즈 노트&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/nodejs/node/releases/tag/v23.6.0">Node.js 23.6.0 릴리즈 노트&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://oida.dev/erasable-syntax-only/">TypeScript&amp;rsquo;s &lt;code>erasableSyntaxOnly&lt;/code> Flag&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://maxheiber.medium.com/alternatives-to-typescript-enums-50e4c16600b1">Alternatives to Typescript Enums&lt;/a>&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>Typescript: 타입 전용 import &amp; export와 import 생략(elision)</title><link>https://netscout.github.io/posts/typescript-%ED%83%80%EC%9E%85-%EC%A0%84%EC%9A%A9-import-export%EC%99%80-import-%EC%83%9D%EB%9E%B5-elision/</link><pubDate>Sat, 05 Jul 2025 01:04:20 +0000</pubDate><guid>https://netscout.github.io/posts/typescript-%ED%83%80%EC%9E%85-%EC%A0%84%EC%9A%A9-import-export%EC%99%80-import-%EC%83%9D%EB%9E%B5-elision/</guid><description>최근에 typescript로 간단한 테스트 프로젝트를 설정할 일이 있었습니다. 폴더를 만들고 간단한 테스트를 위해서 기존 프로젝트에서 코드를 조금씩 복사해서 테스트 프로젝트를 구성하던 중 처음 보는 lint 오류를 발견했습니다. 타입 정보로만 사용되는 import에 …</description><content>&lt;h1 id="들어가며">들어가며&lt;/h1>
&lt;p>최근에 typescript로 간단한 테스트 프로젝트를 설정할 일이 있었습니다. 폴더를 만들고 간단한 테스트를 위해서 기존 프로젝트에서 코드를 조금씩 복사해서 테스트 프로젝트를 구성하던 중 &lt;a href="https://typescript-eslint.io/rules/consistent-type-imports/">처음 보는 lint 오류&lt;/a>를 발견했습니다. 타입 정보로만 사용되는 import에 type을 붙여라! 는 거였죠.&lt;/p>
&lt;p>최대한 간단하게 테스트를 해보려던 저는 머리가 복잡해졌죠. 아니 이게 도대체 뭔데 프로젝트 여기저기서 난리가 난걸까 하고 말이죠. 그래서 일단 eslint의 룰을 해제하고 작업을 이어갔습니다.&lt;/p>
&lt;p>그리고 나중에 시간이 좀 생겼을 때 관련해서 찾아보니 타입 전용 import, export 그리고 import 생략(elision)에 대해 알게 되었죠.&lt;/p>
&lt;p>import 생략은 typescript가 코드를 js로 컴파일 및 변환하는 과정에서 타입 정보로만 사용되는 import를 자동으로 제거한다는 것입니다. 생각해보면, js에서는 딱히 타입을 체크하지 않으니 typescript에서 타입을 체크하기 위해 사용하는 정보가 필요없겠구나 싶긴 했지만 그렇다면 왜 굳이 import에 이게 타입을 위한 import인지 적어야 할까 하는 생각이 들었습니다.&lt;/p>
&lt;p>그래서 좀 알아본 뒤, 나름 이해한 내용에 대해 잊어먹지 않기 위해 이 글을 적게 되었죠.&lt;/p>
&lt;h2 id="타입-전용type-only-import--export-란">타입 전용(type only) import &amp;amp; export 란?&lt;/h2>
&lt;p>타입 전용 import &amp;amp; export는 컴파일 타임에 타입을 체크하기 위한 용도로 사용되는 import와 실제 런타임에서 사용되는 import 값들을 구분하기 위해 사용됩니다. 기존 import, export에 type을 붙여서 &lt;code>import type&lt;/code>, &lt;code>export type&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="c1">// 타입 전용 import
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Status&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./types&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">// 타입 전용 export
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Status&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">// 혼합된 import (TypeScript 4.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">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="kr">type&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">api&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 타입 전용 import로 가져온 타입을 다시 타입 전용 export로 내보내기
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./user&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>여기서 가장 중요한 점은 &lt;code>import type&lt;/code>으로 가져온 타입 관련 코드는 컴파일 과정에서 완전히 사라진다는 겁니다. 다음과 같이 테스트해볼 수 있습니다.&lt;/p>
&lt;p>우선 &lt;code>ex1/types.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">export&lt;/span> &lt;span class="kr">interface&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&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="nx">age&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;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>ex1/main.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="kr">type&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./types&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">// 여기서 User는 컴파일 타임의 타입 체크 용도로만 사용되고 있습니다.
&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">user&lt;/span>: &lt;span class="kt">User&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&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">age&lt;/span>: &lt;span class="kt">30&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="nx">user&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 &lt;code>npx tsc&lt;/code> 명령어를 통해 컴파일을 하고 &lt;code>dist/type-only-import/ex1/main.js&lt;/code> 파일을 확인해보면 다음과 같이 타입 정보로만 사용되는 import가 제거된 것을 확인할 수 있습니다.&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="s2">&amp;#34;use strict&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="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">defineProperty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;__esModule&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">value&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="c1">// 여기서 User는 컴파일 타임의 타입 체크 용도로만 사용되고 있습니다.
&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">user&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&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">age&lt;/span>: &lt;span class="kt">30&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">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="nx">user&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="왜-이게-중요한가요">왜 이게 중요한가요?&lt;/h2>
&lt;p>큰 프로젝트를 빌드하다보면, 모듈 간의 의존성이 복잡해집니다. 어디서 뭘 export하고 import 하는지가 명확하지 않고 외부 라이브러리 까지 복잡하게 얽히면 파악하기 더 어려워집니다. 그리고 빌드 및 번들링과정을 거치면 파일 크기가 커지고 빌드도 오래걸리며, 앱이 실행될 때 덩치가 큰 파일을 로드해야 하니까 메모리 사용도 늘어납니다.&lt;/p>
&lt;p>그런데 타입 전용 import &amp;amp; export를 사용하면 컴파일 타임에 타입 정보로만 사용되는 import를 제거하고, 런타임에는 필요한 값들만 남기기 때문에 빌드 시간과 메모리 사용량을 줄일 수 있습니다.&lt;/p>
&lt;p>게다가 이해하기 힘든 순환 참조, 사이드 이펙트 등의 오류도 방지할 수 있습니다.&lt;/p>
&lt;h3 id="순환-참조-오류">순환 참조 오류&lt;/h3>
&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="c1">// src/ex2/a.ts
&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">B&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./b&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="kr">class&lt;/span> &lt;span class="nx">A&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span>: &lt;span class="kt">B&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&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="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;A created&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="c1">// src/ex2/b.ts
&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">A&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./a&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="kr">class&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="nx">a&lt;/span>: &lt;span class="kt">A&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">A&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;B created&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="c1">// src/ex2/main.ts
&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">A&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./a&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">const&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">A&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 예제를 빌드 후 &lt;code>node dist/type-only-import/ex2/main.js&lt;/code> 명령어를 통해 실행해보면 다음과 같은 오류가 발생합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/Users/onlifecoding/src/test/typescript-studies/dist/type-only-import/ex2/b.js:7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> this.a = new a_1.A();
&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">RangeError: Maximum call stack size exceeded
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new B (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/b.js:7:9)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new A (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/a.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new B (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/b.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new A (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/a.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new B (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/b.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new A (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/a.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new B (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/b.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new A (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/a.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new B (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/b.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> at new A (/Users/onlifecoding/src/test/typescript-studies/dist/ex2/a.js:7:18)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Node.js v22.14.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>클래스 A, B가 각각 타입으로 상대방을 참조하고 있기 때문에 순환참조 오류가 발생합니다. 위 코드에서 타입 전용 import를 사용해볼까요?&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="c1">// src/ex2/a.ts
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">B&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./b&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="kr">class&lt;/span> &lt;span class="nx">A&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">b&lt;/span>: &lt;span class="kt">B&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">undefined&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&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">//this.b = new B(); &amp;lt;- 값이 아닌 타입으로만 사용하도록 변경 했습니다.
&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;A created&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="c1">// src/ex2/b.ts
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">A&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./a&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="kr">class&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="nx">a&lt;/span>: &lt;span class="kt">A&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">undefined&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">constructor&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">//this.a = new A(); &amp;lt;- 값이 아닌 타입으로만 사용하도록 변경 했습니다.
&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;B created&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;/code>&lt;/pre>&lt;/div>&lt;p>이제 다시 빌드 후 실행하면 오류가 발생하지 않습니다.&lt;/p>
&lt;h3 id="사이드-이펙트-오류">사이드 이펙트 오류&lt;/h3>
&lt;p>타입 전용 import는 컴파일 과정에서 완전 제거 되기 때문에 import로 인한 예상하지 못한 동작을 방지할 수 있습니다.&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="c1">// analytics.ts
&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;Analytics module loaded!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 모듈이 로드될 때 실행될 console.log
&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">export&lt;/span> &lt;span class="kr">interface&lt;/span> &lt;span class="nx">TrackingEvent&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&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="nx">data&lt;/span>: &lt;span class="kt">Record&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="na">any&lt;/span>&lt;span class="p">&amp;gt;;&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">// main.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 타입 전용 import는 console.log가 실행되지 않습니다.
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">TrackingEvent&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./analytics&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">import&lt;/span> &lt;span class="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">TrackingEvent&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./analytics&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="s2">&amp;#34;./analytics&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 사이드 이펙트 발생
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="타입-전용-import--export-사용-시-주의사항">타입 전용 import &amp;amp; export 사용 시 주의사항&lt;/h2>
&lt;h3 id="코드-가독성을-위한-구조화">코드 가독성을 위한 구조화&lt;/h3>
&lt;p>다음과 같이 같은 파일에서 import하더라도 타입 전용 import와 런타임에 값으로 사용되는 import를 명확하게 구분하는 게 좋습니다.&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="c1">// 타입 전용 import
&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">ComponentProps&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./components&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">// 런타임에 사용되는 import
&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">Button&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Modal&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./components&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="isolatedmodules-옵션-사용시-type-전용-export-사용">isolatedModules 옵션 사용시 type 전용 export 사용&lt;/h3>
&lt;p>Babel, SWC등의 빌드 툴을 사용하는 경우 전체 프로젝트를 한 번에 분석하지 않기 때문에 각 파일이 독립적으로 transpile 될 수 있도록 isolatedModules 옵션을 사용합니다. 이 옵션을 사용할 때는 type 전용 export를 사용해서 타입 정보를 내보내야 합니다.&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="c1">// 잘못된 방식 - isolatedModules에서 컴파일 에러
&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="p">{&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./user&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">export&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Post&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./post&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="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./user&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">export&lt;/span> &lt;span class="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Post&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./post&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="verbatimmodulesyntax-옵션-사용">verbatimModuleSyntax 옵션 사용&lt;/h3>
&lt;p>tsconfig.json에서 &lt;code>verbatimModuleSyntax&lt;/code> 옵션을 사용하면 다음과 같이 타입 전용 import, export의 사용 규칙이 명확해집니다.&lt;/p>
&lt;ul>
&lt;li>타입 전용 import, export는 빌드 과정에서 완전히 제거됩니다.&lt;/li>
&lt;li>&lt;code>type&lt;/code> 키워드가 사용되지 않은 import, export는 빌드 후에도 유지됩니다.&lt;/li>
&lt;li>typescript가 import에 대해서 타입으로 사용되는지 아닌지 검사하지 않도록 합니다. 따라서 typescript가 자동으로 수행하던 import 생략(elision)을 수행하지 않습니다.&lt;/li>
&lt;/ul>
&lt;h1 id="참고-자료">참고 자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax">Verbatim Module Syntax&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://typescript-eslint.io/blog/consistent-type-imports-and-exports-why-and-how/">Consistent Type Imports and Exports: Why and How&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://typestronglab.in/tutorial/type-imports">Type-Only Imports&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://stackoverflow.com/questions/48418317/circular-dependency-caused-by-importing-typescript-type">Circular dependency caused by importing typescript type&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/sverweij/dependency-cruiser/issues/695">Issue: no-circular does not follow dependencyTypesNot: [&amp;rsquo;type-only&amp;rsquo;] over entire cycle&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>Cursor와 MCP를 활용한 프로젝트 설정</title><link>https://netscout.github.io/posts/cursor%EC%99%80-mcp%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%A4%EC%A0%95/</link><pubDate>Sat, 24 May 2025 09:36:07 +0900</pubDate><guid>https://netscout.github.io/posts/cursor%EC%99%80-mcp%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%A4%EC%A0%95/</guid><description>2000년대 까지만 하더라도 꼭꼭 숨어있는 정보를 찾아내는 게 능력이었습니다. 구글에서 적절하게 검색어를 넣고 자료를 뒤지면서 남들보다 더 많은 정보를 빠르게 찾는 나름의 방법을 익혀나갔죠. 외국에서 공부하고 왔던 한 개발자에게 구글에서 &amp;ldquo;xxx&amp;quot;에 대해서 …</description><content>&lt;h2 id="앉아봐라-그-시절에는-말이야">앉아봐라 그 시절에는 말이야&amp;hellip;&lt;/h2>
&lt;p>2000년대 까지만 하더라도 꼭꼭 숨어있는 정보를 찾아내는 게 능력이었습니다. 구글에서 적절하게 검색어를 넣고 자료를 뒤지면서 남들보다 더 많은 정보를 빠르게 찾는 나름의 방법을 익혀나갔죠. 외국에서 공부하고 왔던 한 개발자에게 구글에서 &amp;ldquo;xxx&amp;quot;에 대해서 검색해보라고 했더니 정말 검색창에 &amp;ldquo;xxx&amp;quot;만 입력하는 걸 보고 경악했던 일도 있었습니다. 그 당시 구글은 대화 상대도 아니었고 그저 정보를 찾기 위한 검색툴에 불과했으니 툴을 사용해보지 않은 사람들은 사용이 어려웠던 겁니다.&lt;/p>
&lt;p>그런데 ChatGPT를 기점으로 시대가 많이 변하더니 이젠 변화의 속도가 점점 가속화 되어 잠시만 눈을 떼면 따라잡기가 어렵다는 느낌이 듭니다. 심지어 이러다가 뒤쳐지는 정도가 아니라 아예 쓸모없어지는게 아닌가 하는 공포마저 들게 됩니다. 별 수 있나요&amp;hellip; 최소한 이런 발전에 뒤쳐지지만 말자고 생각하고 노력하는 수 밖에 없죠!&lt;/p>
&lt;h2 id="mcp가-정말-좋다며-근데">MCP가 정말 좋다며? 근데&amp;hellip;&lt;/h2>
&lt;p>최근에는 MCP나 A2A 같이 에이전트의 활용에 대한 내용들이 많이 나오는 거 같습니다. A2A는 에이전트 간의 협업에 대한 거고, MCP는 에이전트가 활용할 수 있는 툴에 대한 거니까 범위는 다르지만 어쨌건 에이전트 시대에 에이전트를 좀 더 잘 활용할 수 있는 시대로 변화하고 있음은 분명합니다. 세상은 돌고 도는 거라 2000년대 초반에 SOAP를 기반으로 한 웹서비스의 느낌이 들기도 합니다만 어쨌건 구조가 비슷하게 느껴질 뿐 하는 일은 천지차이죠.&lt;/p>
&lt;p>MCP에 대해 사람들이 이야기 하는 걸 들어보면 분명 엄청난 프로토콜은 맞습니다만, 저와는 상관이 없어보였습니다. 피그마에서 디자인한 내용을 MCP를 통해 프론트까지 구현한다던지 하는 건 분명 대단하지만, 저는 피그마를 사용하지 않기 때문에 &amp;ldquo;아 저게 어떤 상황에서는 미친 생산성이 나오겠구나&amp;quot;하는 정도였죠. 근데 진짜 누구에게나 사용가능한 미친 MCP를 발견했습니다. 아니, 이미 존재하고 있었지만 조합해보니 대단했던 거죠.&lt;/p>
&lt;h2 id="context7과-sequential-thinking">Context7과 Sequential Thinking&lt;/h2>
&lt;p>&lt;a href="https://context7.com">Context7&lt;/a> 은 AI를 사용하는 데 있어 가장 중요한 부분인 context의 문제를 해결해줍니다. AI를 학습된 내용을 바탕으로 코드나 답변을 생성하기 때문에 뭔가 최신으로 업데이트된 내용은 제대로 고려하지 못합니다. Context7은 주요 프레임워크나 SDK등의 사용법을 주요 언어 및 환경 별로 어떻게 사용해야 하는지 등을 정리해서 모아놓은 사이트이고, Context7 MCP는 에이전트가 이러한 내용을 자율적으로 검색해서 구현이나 답변에 참고할 수 있도록 해줍니다. 최신 정보를 에이전트가 알아서 가져다 쓸 수 있다는 거죠!&lt;/p>
&lt;p>그리고 &lt;a href="https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking">Sequential Thinking MCP Server&lt;/a> 는 에이전트가 사용자의 요청을 받았을 때, 구조화된 사고 과정을 거쳐서 큰 문제를 작게 쪼개고 더 나은 방법을 찾기 위해서 자신의 사고를 검증 하는 등 에이전트의 문제 해결력을 더 높여주는 MCP입니다. 이 MCP는 단독으로 사용해도 큰 효과를 주지만 Context7이랑 합치면 더 시너지가 납니다!&lt;/p>
&lt;h2 id="직접-해보자">직접 해보자.&lt;/h2>
&lt;p>우선 &lt;a href="https://youtu.be/TrXBzzsUUY4?si=OrOAD1JO8qobF-S8">개발 동생님의 Context7 소개 영상&lt;/a> 을 보면, 두 MCP를 활용해서 아주 간단한 사칙연산을 하는 MCP 서버를 만드는 과정을 보여줍니다. 저도 한 번 따라해봤습니다. 와 정말 대박이었습니다. 이 간단한 과정을 통해서 실제 동작하는 MCP 서버를 만들 수 있다니! 이렇게 함으로써 저는 typescript를 이용해서 MCP 서버를 어떻게 만들어야 하는지 한 번에 이해할 수 있었습니다.&lt;/p>
&lt;p>그리고 실제 업무나 개인적으로 스터디할 때 이 MCP를 적극활용하기 시작했습니다. 효과는 정말 대단했습니다. 그 중에 개인 스터디를 위해 준비했던 내용을 공유해보겠습니다.&lt;/p>
&lt;h2 id="어떤-상황이냐">어떤 상황이냐&lt;/h2>
&lt;p>최근에 mongodb에 대해서(특히 aggregation pipeline) 한 번 정리하고 싶은 마음이 생겨서 에제 코드를 작성해보고자 했습니다. 그래서 ChatGPT를 통해 기본 구조를 뽑아냈죠. 학생과 선생님 collection이 있는 구조를 설명해주고 초기 데이터를 구성하는 seed 코드를 다음과 같이 생성했습니다.&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">// seed.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Node.js MongoDB seed script for students and teachers collections
&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="p">{&lt;/span> &lt;span class="nx">MongoClient&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ObjectId&lt;/span> &lt;span class="p">}&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;mongodb&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">faker&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;faker&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">// Configuration
&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">MONGO_URI&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">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MONGO_URI&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="s2">&amp;#34;mongodb://localhost:27017&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">DB_NAME&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">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DB_NAME&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="s2">&amp;#34;school&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">// Constants
&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">GRADES&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&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">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6&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">CLASSES&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;A&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;B&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">// Utility to generate random scores array
&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">generateScores&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">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">datatype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">min&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">max&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">5&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">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">from&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">length&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="p">}).&lt;/span>&lt;span class="nx">map&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="nx">math&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">datatype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">min&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">max&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">100&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">english&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">datatype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">min&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">max&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">100&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">coding&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">datatype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">min&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">max&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">100&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="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">seed&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">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MongoClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">MONGO_URI&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useUnifiedTopology&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="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">await&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">connect&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">db&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">db&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">DB_NAME&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">// Clear existing data
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;students&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">deleteMany&lt;/span>&lt;span class="p">({});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;teachers&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">deleteMany&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">// Generate students
&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">students&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">GRADES&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">grade&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="nx">CLASSES&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">cls&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="kr">const&lt;/span> &lt;span class="nx">className&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">grade&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">cls&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="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">18&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">students&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&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="o">:&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">ObjectId&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">findName&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">grade&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">class&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">className&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">scores&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">generateScores&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Generate teachers
&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">teachers&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">GRADES&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">grade&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="nx">CLASSES&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">cls&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="kr">const&lt;/span> &lt;span class="nx">className&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">grade&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">cls&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">teachers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&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="o">:&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">ObjectId&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">faker&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">findName&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">grade&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">class&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">className&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">// Insert seed data
&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">studentResult&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;students&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">insertMany&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">students&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">teacherResult&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;teachers&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">insertMany&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">teachers&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">`Inserted &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">studentResult&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">insertedCount&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> students`&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">`Inserted &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">teacherResult&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">insertedCount&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> teachers`&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;Seeding completed successfully.&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">err&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">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Error during seeding:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&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">finally&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">close&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">seed&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 코드가 아주 정확하냐 뭐 그런건 그렇게 중요하진 않았습니다. 나중에 수정하면 되니까요. 그리고 빈 폴더를 하나 만들고 &lt;code>Cursor&lt;/code>를 열고 다음과 같이 요청했습니다. 사용된 모델은 &lt;code>Cluade 3.7 Sonnet&lt;/code> 입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">i need to create node.js app for studying mongodb aggregation pipeline.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">so i need to create basic project structure and seed script using mongodb node.js driver.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use context7 for looking for how to use node.js mongodb driver.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use sequantial thinking mcp to plan how to create basic project structure and seeding mongodb collestion.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">here&amp;#39;s starter seed script:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// seed.js
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Node.js MongoDB seed script for students and teachers collections
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">const { MongoClient, ObjectId } = require(&amp;#39;mongodb&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">const faker = require(&amp;#39;faker&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">const MONGO_URI = process.env.MONGO_URI || &amp;#39;mongodb://localhost:27017&amp;#39;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">const DB_NAME = process.env.DB_NAME || &amp;#39;school&amp;#39;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Constants
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">const GRADES = [1, 2, 3, 4, 5, 6];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">const CLASSES = [&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// Utility to generate random scores array
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function generateScores() {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const count = faker.datatype.number({ min: 3, max: 5 });
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return Array.from({ length: count }).map(() =&amp;gt; ({
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> math: faker.datatype.number({ min: 40, max: 100 }),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> english: faker.datatype.number({ min: 40, max: 100 }),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> coding: faker.datatype.number({ min: 40, max: 100 })
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">async function seed() {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const client = new MongoClient(MONGO_URI, { useUnifiedTopology: true });
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> try {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> await client.connect();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const db = client.db(DB_NAME);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // Clear existing data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> await db.collection(&amp;#39;students&amp;#39;).deleteMany({});
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> await db.collection(&amp;#39;teachers&amp;#39;).deleteMany({});
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // Generate students
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const students = [];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> GRADES.forEach(grade =&amp;gt; {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CLASSES.forEach(cls =&amp;gt; {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const className = `${grade}-${cls}`;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> for (let i = 0; i &amp;lt; 18; i++) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> students.push({
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: new ObjectId(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: faker.name.findName(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> grade,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> class: className,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> scores: generateScores()
&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>&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"> // Generate teachers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const teachers = [];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> GRADES.forEach(grade =&amp;gt; {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CLASSES.forEach(cls =&amp;gt; {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const className = `${grade}-${cls}`;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> teachers.push({
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> _id: new ObjectId(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name: faker.name.findName(),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> grade,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> class: className
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // Insert seed data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const studentResult = await db.collection(&amp;#39;students&amp;#39;).insertMany(students);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const teacherResult = await db.collection(&amp;#39;teachers&amp;#39;).insertMany(teachers);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> console.log(`Inserted ${studentResult.insertedCount} students`);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> console.log(`Inserted ${teacherResult.insertedCount} teachers`);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> console.log(&amp;#39;Seeding completed successfully.&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } catch (err) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> console.error(&amp;#39;Error during seeding:&amp;#39;, err);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } finally {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> await client.close();
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">seed();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Context7 MCP를 통해 mongodb 드라이버의 사용법을 참고하고, Sequential Thinking MCP를 어떻게 작업할지 구상해보라고 했습니다. 그러자 에이전트가 MCP를 호출하기 시작합니다.&lt;/p>
&lt;p align="center">&lt;img src="./01.png">&lt;/p>
&lt;p>mongodb와 node.js driver 연결방법, 그리고 aggregation pipeline 사용법을 참고하고 있네요!&lt;/p>
&lt;p>그리고 최종적으로 다음과 같이 정리해줍니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">I&amp;#39;ve created a complete Node.js project for studying MongoDB aggregation pipelines. Here&amp;#39;s what I&amp;#39;ve set up:
&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="gu">### Project Structure
&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">mongodb-aggregation-study/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── package.json # Dependencies and npm scripts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── .gitignore # Git ignore file
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── README.md # Project documentation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── db.js # MongoDB connection configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── examples/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── basic.js # Basic aggregation examples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── advanced.js # Advanced aggregation examples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── index.js # Main application entry point
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── scripts/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── seed.js # Database seeding script
&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="gu">### Key Features
&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">&lt;span class="k">1.&lt;/span> &lt;span class="gs">**Database Configuration**&lt;/span>: A reusable MongoDB connection module in &lt;span class="sb">`db.js`&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">2.&lt;/span> &lt;span class="gs">**Seed Script**&lt;/span>: Updated version of your script using &lt;span class="sb">`@faker-js/faker`&lt;/span> to populate the database with students and teachers data
&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">3.&lt;/span> &lt;span class="gs">**Aggregation Examples**&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">-&lt;/span> **Basic**: Simple examples using $match, $group, $project, and $lookup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> **Advanced**: Complex examples with $expr, $bucket, $reduce, and $facet
&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">4.&lt;/span> &lt;span class="gs">**NPM Scripts**&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> &lt;span class="sb">`npm run seed`&lt;/span> - Populate the database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> &lt;span class="sb">`npm run example:basic`&lt;/span> - Run basic aggregation examples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> &lt;span class="sb">`npm run example:advanced`&lt;/span> - Run advanced aggregation examples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> &lt;span class="sb">`npm start`&lt;/span> - Run the main application
&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="gu">### Getting Started
&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">To use this project:
&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">1.&lt;/span> Ensure MongoDB is running locally (or update the connection URI in your environment)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">2.&lt;/span> Install dependencies with &lt;span class="sb">`npm install`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">3.&lt;/span> Run &lt;span class="sb">`npm run seed`&lt;/span> to populate the database
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">4.&lt;/span> Try the examples with &lt;span class="sb">`npm run example:basic`&lt;/span> or &lt;span class="sb">`npm run example:advanced`&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">The project provides a practical environment for learning and experimenting with MongoDB&amp;#39;s powerful aggregation pipeline features.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mongodb-aggregation-study/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── package.json # Dependencies and npm scripts
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── .gitignore # Git ignore file
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── README.md # Project documentation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── db.js # MongoDB connection configuration
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── examples/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── basic.js # Basic aggregation examples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── advanced.js # Advanced aggregation examples
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── index.js # Main application entry point
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── scripts/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── seed.js # Database seeding script
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>딱히 제가 요청하지 않았던 프로젝트 구조와 기초 및 고급 예제, 그리고 커맨드 까지 생성해서 알려줍니다!&lt;/p>
&lt;p>이번엔 mongodb를 도커로 실행하는 방법을 요청해보겠습니다. 이번에도 context7 MCP를 통해 사용법을 참고하라고 알려줍니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">very well! i need to run mongodb using docker on local. can you add instruction in @README.md for this?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use context7 if you need to reference how to do it
&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-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">Here are clear instructions you can add to your &lt;span class="sb">`README.md`&lt;/span> for running MongoDB locally using Docker. This is the most common and recommended way for local development, and it will work cross-platform.
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Running MongoDB Locally with Docker
&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">If you don&amp;#39;t have MongoDB installed on your machine, you can easily run it using Docker:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker run --name mongodb-aggregation-study -d -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=mongo -e MONGO_INITDB_ROOT_PASSWORD=mongo mongo:latest
&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">-&lt;/span> This command will:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> Pull the latest MongoDB image if you don&amp;#39;t have it.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> Start a MongoDB server in a container named &lt;span class="sb">`mongodb-aggregation-study`&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> Expose MongoDB on the default port &lt;span class="sb">`27017`&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> Set the root username and password to &lt;span class="sb">`mongo`&lt;/span> (you can change these if you want).
&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="gu">### Connecting from your app
&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">&lt;span class="k">-&lt;/span> By default, your &lt;span class="sb">`.env`&lt;/span> should use:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MONGO_URI=mongodb://localhost:27017
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DB_NAME=school
&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">-&lt;/span> If you want to use authentication, update your &lt;span class="sb">`.env`&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"> MONGO_URI=mongodb://mongo:mongo&lt;span class="ni">@localhost:27017&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DB_NAME=school
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> And make sure your MongoDB connection code supports authentication.
&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="gu">### Stopping and Removing the Container
&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">To stop MongoDB:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker stop mongodb-aggregation-study
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">To remove the container:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker rm mongodb-aggregation-study
&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-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">currently @generateScores() generate randomly 3-5 scores for math, english, coding. but let&amp;#39;s update this to:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> mid: { math, english, coding },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> final: { math, english, coding }
&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">and update example aggregations if needed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그러면 예제까지 싹 다 고쳐줍니다! 물론 중간 중간에 .env의 호출이 빠져서 환경 변수를 못읽어오는 바람에 mongodb에 접속이 안된다던지 하는 사소한 문제가 있긴 했지만요.&lt;/p>
&lt;h2 id="ai-시대는-일하는-방법이-다르구나">AI 시대는 일하는 방법이 다르구나!&lt;/h2>
&lt;p>AI를 개발에 활용한지 2년이 다 되어가는데, 너무 많이 부족합니다. 그만큼 AI의 발전이 빠르다는 말이겠죠. 최근에는 그냥 자연어 기반으로 에이전트를 백그라운드에서 굴리는 OpenAI Codex나 Claude Code 등이 새로운 개발 트렌드로 떠오르는 거 같습니다. 이렇게 되면 에이전트를 여러 개 굴리면서 그냥 말로만 코딩하는 시대가 될 거 같기도 한데요&amp;hellip; 아직 돈이 없어서 이런 툴들을 써보진 못했습니다. 조만간에 기회가 있으면 시도해봐야죠. 적어도 뒤쳐지지는 말아야죠!&lt;/p></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><item><title>CRA &amp; CRACO -> VITE 마이그레이션</title><link>https://netscout.github.io/posts/cra-craco-vite-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98/</link><pubDate>Thu, 01 May 2025 08:40:44 +0900</pubDate><guid>https://netscout.github.io/posts/cra-craco-vite-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98/</guid><description>2022년 한 프로젝트를 처음 시작할 때 저는 리액트에 대해 아주 잘 알지는 못했습니다. 그렇기 때문에 공식 사이트에 나와있는 대로 CRA(Create React App)을 이용해 프로젝트를 생성했습니다. 그리고 몇 달 후, 새롭게 합류한 프론트 개발자가 그 …</description><content>&lt;h2 id="cra와-씨름하다">CRA와 씨름하다&lt;/h2>
&lt;p>2022년 한 프로젝트를 처음 시작할 때 저는 리액트에 대해 아주 잘 알지는 못했습니다. 그렇기 때문에 공식 사이트에 나와있는 대로 CRA(Create React App)을 이용해 프로젝트를 생성했습니다. 그리고 몇 달 후, 새롭게 합류한 프론트 개발자가 그 프로젝트에 &lt;a href="https://craco.js.org">CRACO&lt;/a>를 추가했습니다. 그 당시에는 CRA의 여러 문제점으로 인해서 eject하여 커스텀하게 설정을 하던지, CRACO나 &lt;a href="https://github.com/timarney/react-app-rewired">React App Rewired&lt;/a>같은 방법을 사용했던 걸로 기억합니다.&lt;/p>
&lt;p>그리고 프로젝트가 점점 커지자 몇 가지 문제가 발생했습니다.&lt;/p>
&lt;ul>
&lt;li>개발 환경의 메모리 문제
CRA가 Webpack + Babel을 사용하는데, 앱이 시작 될 때 src 폴더와 node_modules 폴더에서 임포트 되는 모든 파일을 찾아서 의존성 그래프를 생성합니다. 그리고 생성된 결과를 번들링해서 메모리에 저장하는데요, 이 과정에서 많은 메모리가 필요합니다. 프로젝트가 복잡해지고 커질수록 더 악화되겠죠! 그래서 &lt;code>npm run dev&lt;/code> 명령에 &lt;code>NODE_OPTIONS='--max-old-space-size=4096'&lt;/code> 옵션을 추가하여 메모리를 조금 더 사용할 수 있도록 했습니다.&lt;/li>
&lt;li>빌드 시간 문제
프로젝트가 커짐에 따라 시간이 점점 더 걸리더니 결국 빌드에 10분씩 걸리는 지경에 이르렀습니다. 사소한 실수 하나를 수정하기 위해 10분이나 기다려야 하는 건 너무 비효율적이었습니다. 그래서 빌드 시간을 단축시키기 위해서 esbuild를 craco에 엮어서 사용하는 방식을 통해 어느 정도 줄일 수 있었지만 여전히 4-5분 정도의 빌드 타임이 소요되었습니다.&lt;/li>
&lt;li>CRA의 지원 종료
2023년 즈음해서 사실상 &lt;a href="https://github.com/facebook/create-react-app/issues/13072">React 팀에서도 CRA에 대한 지원을 조용히 그만두기로 결정&lt;/a>했습니다. 그래서 회사에서 추가로 진행하는 프로젝트에 더 이상 CRA를 사용할 수 없는 상황이었고, 이 때문에 대안을 찾아야 했습니다.&lt;/li>
&lt;/ul>
&lt;p>이때쯤 부터 &lt;code>VITE&lt;/code>에 대한 이야기가 더 많아졌던 것 같습니다. 몇 년전에 &lt;code>Vue.js 3&lt;/code>를 처음 스터디 해볼 때 &lt;code>VITE&lt;/code>라는 게 있더라 정도는 봤던 기억이 있었는데, 구체적으로 이게 뭔지는 몰랐죠. 그래서 작년 말에 새로운 프로젝트를 시작 할 때 &lt;code>VITE&lt;/code>를 사용해보기로 했습니다.&lt;/p>
&lt;h2 id="vite에-놀라다">VITE에 놀라다&lt;/h2>
&lt;p>VITE는 제 생각보다 더 훌륭했습니다. 왜 사람들이 VITE를 칭송하는지 알겠더군요!&lt;/p>
&lt;ul>
&lt;li>개발 환경의 쾌적함
VITE는 앱이 시작될 때 앱 전체를 번들링 하지 않습니다. 요청되는 페이지와 관련있는 임포트만 번들링하여 제공합니다. 그러니까 엄청 빠르고, 메모리 문제도 없습니다!&lt;/li>
&lt;li>빌드도 빠르다!
초기 프로젝트 생성과정에서 간단하게 SWC를 선택할 수 있고, SWC를 이용하면 빌드 시간이 매우 단축됩니다. CRA에는 없던 다른 문제가 발생하긴 했지만 그건 나중에 자세히 말씀드리죠.&lt;/li>
&lt;/ul>
&lt;p>왜 그동안 CRA를 가지고 씨름했었을까 하는 현타가 몰려왔지만, 어쩔 수 없었죠. VITE가 좋은지 몰랐으니까!&lt;/p>
&lt;p>그래서 한동안 새 프로젝트를 작업하다보니 그 작업이 다시 돌아왔습니다. 네, 기존 프로젝트 유지보수 작업이죠. 즐거운 작업입니다. 근데 이제와서 다시 CRA를 사용하는 건 마음이 불편했습니다. 실제로도 불편했고요. 메모리 옵션을 줬음에도 불구하고 꺼지는 앱과 HMR 할 때 마다 걸리는 시간! 그래서 대충 각을 잡아보고 마이그레이션 작업을 시작했습니다.&lt;/p>
&lt;h2 id="인생은-실전이다-cra---vite-마이그레이션">인생은 실전이다! CRA -&amp;gt; VITE 마이그레이션&lt;/h2>
&lt;ol>
&lt;li>CRA와 craco 제거&lt;/li>
&lt;/ol>
&lt;p>우선 CRA와 craco를 제거합니다.&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 uninstall react-scripts @craco/craco
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>VITE와 필수 플러그인을 설치&lt;/li>
&lt;/ol>
&lt;p>다음 명령으로 VITE 및 필수 플러그인을 설치합니다.&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 install -D vite @vitejs/plugin-react-swc vite-plugin-svgr vite-tsconfig-paths @swc/core @swc/cli
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>vite외에 설치되는 패키지 목록은 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>@vitejs/plugin-react-swc: VITE가 React를 지원할 수 있도록 하며, SWC를 사용하여 빌드 속도를 높입니다.&lt;/li>
&lt;li>vite-plugin-svgr: SVG 파일을 React 컴포넌트로 변환합니다.(추후에 왜 설치했는지 설명합니다.)&lt;/li>
&lt;li>vite-tsconfig-paths: 타입스크립트 경로 별칭을 사용할 수 있도록 합니다.&lt;/li>
&lt;li>@swc/core: SWC의 핵심 모듈입니다.&lt;/li>
&lt;li>@swc/cli: SWC를 명령줄에서 사용할 수 있도록 합니다.&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>VITE 설정 파일을 생성&lt;/li>
&lt;/ol>
&lt;p>프로젝트의 루트 폴더에 &lt;code>vite.config.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">defineConfig&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;vite&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="nx">react&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@vitejs/plugin-react-swc&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="nx">tsconfigPaths&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;vite-tsconfig-paths&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">iport&lt;/span> &lt;span class="nx">svgr&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;vite-plugin-svgr&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">// https://vite.dev/config/
&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="k">default&lt;/span> &lt;span class="nx">defineConfig&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">define&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="kr">global&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;globalThis&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// &amp;lt;- 패키지 중에 global 관련 에러가 발생할 경우 추가
&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="nx">plugins&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">react&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tsconfigPaths&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">svgr&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">server&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">port&lt;/span>: &lt;span class="kt">3000&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;ol start="4">
&lt;li>tsconfig.json 파일의 경로 정보 수정&lt;/li>
&lt;/ol>
&lt;p>프로젝트의 tsconfig.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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;compilerOptions&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;baseUrl&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;./src&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;paths&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;@/*&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;@store&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;./app/store&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;@pages/*&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;./pages/*&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;@components/*&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;./components/*&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="err">...&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;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;es2015&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;lib&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;dom&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;dom.iterable&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;esnext&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;allowJs&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;skipLibCheck&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;esModuleInterop&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;allowSyntheticDefaultImports&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;strict&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;forceConsistentCasingInFileNames&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;noFallthroughCasesInSwitch&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;module&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;esnext&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;moduleResolution&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;node&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;resolveJsonModule&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;isolatedModules&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;noEmit&amp;#34;&lt;/span>&lt;span class="p">:&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="nt">&amp;#34;jsx&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;react-jsx&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;noImplicitAny&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&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;include&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src&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;ol start="5">
&lt;li>vite-env.d.ts 파일 생성&lt;/li>
&lt;/ol>
&lt;p>&lt;code>src/vite-env.d.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="c1">/// &amp;lt;reference types=&amp;#34;vite/client&amp;#34; /&amp;gt;
&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">// svg 파일을 React 컴포넌트로 사용할 수 있도록 선언
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">declare&lt;/span> &lt;span class="nx">module&lt;/span> &lt;span class="s2">&amp;#34;*.svg?react&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="o">*&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">ReactComponent&lt;/span>: &lt;span class="kt">React.FunctionComponent&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">React&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SVGProps&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">SVGSVGElement&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">title?&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="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">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">ReactComponent&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;ol start="6">
&lt;li>index.html 파일 수정&lt;/li>
&lt;/ol>
&lt;p>&lt;code>public/index.html&lt;/code> 파일을 프로젝트의 루트 폴더로 옮기고, 스크립트 링크를 다음과 같이 수정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;root&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;module&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/src/main.tsx&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&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;/code>&lt;/pre>&lt;/div>&lt;ol start="7">
&lt;li>환경 변수 업데이트&lt;/li>
&lt;/ol>
&lt;p>프로젝트의 .env 파일에서 환경 변수를 &lt;code>REACT_APP_&lt;/code> 대신 &lt;code>VITE_&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">REACT_APP_API_URL&lt;/span>&lt;span class="o">=&lt;/span>http://localhost:3000
&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="nv">VITE_API_URL&lt;/span>&lt;span class="o">=&lt;/span>http://localhost:3000
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="8">
&lt;li>package.json 파일의 스크립트 수정&lt;/li>
&lt;/ol>
&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="err">...&lt;/span>
&lt;/span>&lt;/span>&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="nt">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;vite&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;build&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;NODE_ENV=production tsc -b &amp;amp;&amp;amp; vite build&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="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="9">
&lt;li>svg 임포트 수정&lt;/li>
&lt;/ol>
&lt;p>앞서 vite-plugin-svgr 플러그인을 설치했었는데요, VITE에서는 CRA 처럼 svg파일을 컴포넌트로 임포트 할 수 없습니다. 그래서 플러그인을 설치하고 임포트 부분을 다음과 같이 수정합니다.&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="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">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">ReactComponent&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">Checklist&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;../assets/checklist.svg&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">import&lt;/span> &lt;span class="nx">Checklist&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;../assets/checklist.svg?react&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>앞서 작성한 &lt;code>vite-env.d.ts&lt;/code> 파일을 다시 확인해보시면 &lt;code>.svg?react&lt;/code>를 처리하기 위한 선언이 추가되어 있습니다.&lt;/p>
&lt;ol start="10">
&lt;li>ERR_REQUIRE_ESM 에러&lt;/li>
&lt;/ol>
&lt;p>&lt;code>npm run dev&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>&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 /path/to/vite-tsconfig-paths/dist/index.js from /path/to/vite.config.ts not supported.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&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-json" data-lang="json">&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;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;module&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;ol start="11">
&lt;li>global is not defined 에러&lt;/li>
&lt;/ol>
&lt;p>몇몇 패키지는 global 변수를 참조하는데, VITE가 global 대신에 브라우저에서 사용하는 globalThis를 사용하도록 설정해주면 됩니다.&lt;/p>
&lt;p>앞서 작성한 &lt;code>vite.config.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">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">defineConfig&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">define&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="kr">global&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;globalThis&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 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;ol start="12">
&lt;li>process.env.NODE_ENV 를 찾을 수 없는 오류&lt;/li>
&lt;/ol>
&lt;p>VITE는 &lt;code>process.env.NODE_ENV&lt;/code> 대신 &lt;code>import.meta.env.MODE&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="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">isProduction&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">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NODE_ENV&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>&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">isProduction&lt;/span> &lt;span class="o">=&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">env&lt;/span>&lt;span class="p">.&lt;/span>&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="13">
&lt;li>require를 import로 변경&lt;/li>
&lt;/ol>
&lt;p>VITE는 CommonJS 모듈 시스템을 사용하지 않습니다. 그래서 require를 import로 변경해야 합니다.&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="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">import&lt;/span> &lt;span class="nx">i18n&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;i18next&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">initReactI18next&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-i18next&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">i18n&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">initReactI18next&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">init&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fallbackLng&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;en-US&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">lng&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;en-US&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">resources&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">en&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">translations&lt;/span>: &lt;span class="kt">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./locales/en/translations.json&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">ko&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">translations&lt;/span>: &lt;span class="kt">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./locales/ko/translations.json&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;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">import&lt;/span> &lt;span class="nx">i18n&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;i18next&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">initReactI18next&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-i18next&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="nx">enTranslations&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./locales/en/translations.json&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="nx">koTranslations&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./locales/ko/translations.json&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">i18n&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">initReactI18next&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">init&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fallbackLng&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;en-US&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">lng&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;en-US&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">resources&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">en&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">translations&lt;/span>: &lt;span class="kt">enTranslations&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">ko&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">translations&lt;/span>: &lt;span class="kt">koTranslations&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;h2 id="빌드에서-문제가-발생하다">빌드에서 문제가 발생하다.&lt;/h2>
&lt;p>CRA는 개발환경에서는 문제가 많았지만, 빌드 속도를 제외하고 빌드에서 문제는 특별히 없었습니다. 그런데 VITE로 변경하니 로컬에서는 빌드가 잘 되는데 Github Actions에서는 메모리 이유로 빌드가 실패하는 문제가 발생했습니다.&lt;/p>
&lt;p>원인을 찾다보니, 너무 거대한 번들 사이즈가 원인이었습니다. 현재 작업 중인 프로젝트는 좀 복잡한 요구사항으로 인해 무거운 패키지를 많이 사용할 수 밖에 없었는데요. 가장 큰 번들의 크기가 무려 16MB에 이를 정도 였습니다.&lt;/p>
&lt;p>다음과 같이 manualChunks 옵션을 사용하여 번들의 크기를 분할하는 방법이 있었지만,&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">build&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">rollupOptions&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">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">manualChunks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&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">id&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;node_modules&amp;#39;&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">id&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;vendor-react&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">if&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">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;codemirror&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;vendor-codemirror&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">return&lt;/span> &lt;span class="s1">&amp;#39;vendor&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 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;p>다른 방법으로는 lazy import를 사용하는 방법이었는데, 프로젝트가 워낙 복잡한 상황이었기에 한 번에 적용할 수는 없었고 일단은 Github Actions의 빌드 설정에 메모리 옵션을 추가하는 것으로 해결했습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">- &lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">CI=false npm run build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NODE_OPTIONS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>--&lt;span class="l">max-old-space-size=8192&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>일단은 한숨 돌리고 프로젝트를 유지보수 하면서 점진적으로 lazy import를 적용해 나가면서 상황을 개선 해봐야 겠습니다.&lt;/p></content></item><item><title>React 19 &amp; 19.1 알아보기</title><link>https://netscout.github.io/posts/react-19-19-1-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0/</link><pubDate>Sat, 15 Mar 2025 00:10:50 +0000</pubDate><guid>https://netscout.github.io/posts/react-19-19-1-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0/</guid><description>우선 vite를 이용해서 react 프로젝트를 설정합니다.</description><content>&lt;h2 id="1-프로젝트-세팅하기">1. 프로젝트 세팅하기&lt;/h2>
&lt;h3 id="11-vite-프로젝트-세팅">1.1 vite 프로젝트 세팅&lt;/h3>
&lt;p>우선 vite를 이용해서 react 프로젝트를 설정합니다.&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 create vite@latest react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Need to install the following packages:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">create-vite@6.3.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Ok to proceed? &lt;span class="o">(&lt;/span>y&lt;span class="o">)&lt;/span> y
&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">&amp;gt; npx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; create-vite react-19
&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">◇ Select a framework:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ React
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">◇ Select a variant:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ TypeScript + SWC
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">◇ Scaffolding project in /Users/onlifecoding/src/studies/react-19...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└ Done. Now run:
&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">cd&lt;/span> react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> npm install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> npm run dev
&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; &lt;span class="nb">cd&lt;/span> react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; npm i
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 프로젝트를 설정하고 에디터를 통해 프로젝트를 열어보면, 다음과 같이 react 19 프로젝트가 설정된 것을 확인할 수 있습니다.&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="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;dependencies&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;react&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;^19.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;react-dom&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;^19.0.0&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="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@types/react&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;^19.0.10&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;@types/react-dom&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;^19.0.4&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="err">...&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;h3 id="12-tailwindcss-설정">1.2 tailwindcss 설정&lt;/h3>
&lt;p>tailwindcss를 설치합니다.&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 i tailwindcss @tailwindcss/vite
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>vite.config.ts&lt;/code> 파일에 tailwindcss를 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&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">defineConfig&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;vite&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="nx">react&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@vitejs/plugin-react-swc&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="nx">tailwindcss&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@tailwindcss/vite&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">// https://vite.dev/config/
&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="k">default&lt;/span> &lt;span class="nx">defineConfig&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">react&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">tailwindcss&lt;/span>&lt;span class="p">()],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">server&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">port&lt;/span>: &lt;span class="kt">3000&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;code>src/index.css&lt;/code> 파일의 최상단에 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">import&lt;/span> &lt;span class="s2">&amp;#34;tailwindcss&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="13-react-router-dom-및-기본-라우팅-설정">1.3 react-router-dom 및 기본 라우팅 설정&lt;/h3>
&lt;p>페이지간의 이동을 위해 react-router-dom을 설치합니다.&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 install react-router-dom
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>src/Router.tsx&lt;/code> 파일을 생성하고 다음과 같이 코드를 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">lazy&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">createBrowserRouter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Outlet&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">lazy&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./pages/Index&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">About&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">lazy&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./pages/About&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">RootLayout&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Outlet&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">router&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createBrowserRouter&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">path&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">RootLayout&lt;/span> &lt;span class="p">/&amp;gt;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">children&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">index&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">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Index&lt;/span> &lt;span class="p">/&amp;gt;,&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="nx">path&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/about&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">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">About&lt;/span> &lt;span class="p">/&amp;gt;,&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>라우팅 확인을 위해 &lt;code>src/pages/Index.tsx&lt;/code> 파일을 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이어서 &lt;code>src/pages/About.tsx&lt;/code> 파일을 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">About&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">About&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>마지막으로 라우팅이 동작하도록 &lt;code>src/App.tsx&lt;/code> 파일을 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Suspense&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">RouterProvider&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">router&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./Router&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">App() {&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Suspense&lt;/span> &lt;span class="na">fallback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Loading&lt;/span>&lt;span class="p">...&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">RouterProvider&lt;/span> &lt;span class="na">router&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">router&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Suspense&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">App&lt;/span>&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; npm run dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="2-react-19-알아보기">2. React 19 알아보기&lt;/h2>
&lt;h3 id="21-actions">2.1 Actions&lt;/h3>
&lt;p>React에서는 데이터를 작성하고 저장하거나 수정하는 일이 빈번하게 발생합니다. 사실 웹 페이지라는 게 데이터를 저장하고, 표시하고, 수정하고, 삭제하는 것이 전부이기 때문입니다. 이렇게 데이터를 다루는 과정에서 무언가 상태 값이 변경되겠죠?(ex: 서버에 데이터를 전송하고 처리되기 기다릴 때, 요청 상태는 요청중 -&amp;gt; 요청 완료 또는 요청중 -&amp;gt; 요청 실패 등으로 변경될 겁니다!) 이렇게 상태가 변경되는 걸 상태 전이(transition)이라고 합니다. 그리고 &lt;a href="https://react.dev/blog/2024/12/05/react-19#by-convention-functions-that-use-async-transitions-are-called-actions">공식 문서&lt;/a>에 따르면, 비동기 상태 전이(async transition)을 사용하는 함수를 Action이라고 정의하고 있습니다.&lt;/p>
&lt;h4 id="211-usetransition">2.1.1 useTransition&lt;/h4>
&lt;p>예시를 하나 들어볼까요? 서버에 데이터를 전송하는 경우를 생각해보죠. 서버와의 통신 작업을 진행할 때 중요한 점은, 사용자가 실수로 저장 버튼을 두번 클릭하는 걸 방지할 수 있어야 합니다. 만약 이런 처리가 제대로 되지 않는다면, 데이터가 중복으로 저장되어 문제가 발생할 수 있습니다. 예시를 위해서 &lt;code>src/pages/Index.tsx&lt;/code> 파일을 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">useState&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">wait&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">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="kr">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&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">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&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">log&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="err">[]&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="kr">const&lt;/span> &lt;span class="nx">handleSave&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">prev&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 class="nx">prev&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="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;메세지를 입력하세요&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">value&lt;/span>&lt;span class="o">=&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="na">onChange&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&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 class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleSave&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>&lt;span class="err">저장&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">index&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="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">item&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>완성된 페이지는 다음과 같습니다.&lt;/p>
&lt;p align="center">&lt;img src="./images/01.png">&lt;/p>
&lt;p>메세지를 입력할 수 있는 입력 필드와 저장 버튼을 표시하고, 저장 버튼을 클릭하면 메세지를 서버에 저장하는 기능을 구현했습니다. 그리고 서버에 저장하는 걸 흉내내기 위해서 저장 버튼을 누르면 2초 대기 후 저장된 메세지를 표시하도록 했습니다.&lt;/p>
&lt;p>이 페이지에서 메세지를 입력하고, 저장 버튼을 누르면 2초 후에 저장된 메세지가 로그에 표시되는 걸 확인할 수 있습니다. 그런데 저장 버튼을 마구 누르면 어떻게 될까요?&lt;/p>
&lt;p align="center">&lt;img src="./images/02.png">&lt;/p>
&lt;p>서버에 저장하는 동안 입력을 방지하는 작업을 하지 않았기 때문에 누르는 만큼 중복되어 저장되는 걸 볼 수 있습니다. 그러면 입력을 방지하기 위한 장치를 추가해보겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Index&lt;/span> &lt;span class="o">=&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="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">isSaving&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setIsSaving&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">handleSave&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">setIsSaving&lt;/span>&lt;span class="p">(&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="k">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">prev&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 class="nx">prev&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="nx">setIsSaving&lt;/span>&lt;span class="p">(&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="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col gp&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border-2 border-gray-300 rounded-md p-2 disabled:opacity-50 disabled:cursor-not-allowed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleSave&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">disabled&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">isSaving&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&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="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>데이터를 저장하는 중인지 알 수 있는 상태 &lt;code>isSaving&lt;/code>을 추가했습니다. 그리고 저장 버튼을 누르면 이 상태를 &lt;code>true&lt;/code>로 변경하고, 서버와의 통신이 끝나면 &lt;code>false&lt;/code>로 변경합니다. 이렇게 하면 저장 버튼이 눌리지 않은 상태에서는 비활성화되어 입력을 방지할 수 있습니다. 그리고 사용자에게 버튼을 누를 수 없다는 알림을 위해 &lt;code>disabled&lt;/code> 속성과 스타일을 추가했습니다.&lt;/p>
&lt;p>이제 저장 버튼을 누르면 중복으로 저장되는 걸 막을 수 있습니다. React 19에서는 이런 작업을 쉽게 할 수 있도록 &lt;code>useTransition&lt;/code> 훅을 제공합니다. 이 훅은 서버에 데이터를 저장하는 등의 비동기 작업이 시작될 때 &lt;code>isPending&lt;/code> 상태를 &lt;code>true&lt;/code>로 변경하고, 끝나면 &lt;code>false&lt;/code>로 변경합니다. 위 예제를 다음과 같이 수정할 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">useState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">useTransition&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">wait&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">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="kr">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&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">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&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">log&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([]);&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">isPending&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">startTransition&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useTransition&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">handleSave&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">startTransition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&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">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">prev&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 class="nx">prev&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;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="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col gp&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border-2 border-gray-300 rounded-md p-2 disabled:opacity-50 disabled:cursor-not-allowed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleSave&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">disabled&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">isPending&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&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="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>isSaving&lt;/code>과 같은 별도의 상태를 사용하지 않아도 되고, 사용자가 직업 &lt;code>setIsSaving(true)&lt;/code>, &lt;code>setIsSaving(false)&lt;/code>를 호출해야 할 필요도 없어졌습니다. &lt;code>startTransition&lt;/code> 함수를 사용하면, 호출이 시작될 때와 종료될 때 &lt;code>useTransition&lt;/code> 훅이 자동으로 상태를 관리해줍니다.&lt;/p>
&lt;blockquote>
&lt;p>useTransition은 React 18에서도 사용할 수 있습니다!&lt;/p>&lt;/blockquote>
&lt;h4 id="212-useactionstate">2.1.2 useActionState&lt;/h4>
&lt;p>만약 데이터를 전송하던 중에 오류가 발생한다면 어떻게 될까요? 사용자에게 뭔가 오류가 발생했다는 사실을 알려줘야 합니다. 그렇지 않으면, 사용자는 뭐가 안되는지도 모르면서 계속 여기저기 클릭하다가 결국 사용을 포기하겠죠!&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">useState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">useTransition&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">wait&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">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="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">const&lt;/span> &lt;span class="nx">submitMessage&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">message&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="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">`submitMessage: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">message&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="k">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&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">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&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">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&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">log&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([]);&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">isPending&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">startTransition&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useTransition&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">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setError&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&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="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="kr">const&lt;/span> &lt;span class="nx">handleSave&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">setError&lt;/span>&lt;span class="p">(&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="nx">startTransition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&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">submitMessage&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">prev&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 class="nx">prev&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 class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>: &lt;span class="kt">unknown&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">setError&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error&lt;/span> &lt;span class="k">instanceof&lt;/span> &lt;span class="nb">Error&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;알 수 없는 오류가 발생했습니다.&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;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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col gp&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border-2 border-gray-300 rounded-md p-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;메세지를 입력하세요&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">value&lt;/span>&lt;span class="o">=&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="na">onChange&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&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 class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border-2 border-gray-300 rounded-md p-2 disabled:opacity-50 disabled:cursor-not-allowed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleSave&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">disabled&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">isPending&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&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="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">error&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-red-500&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-lg font-bold text-gray-500&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">로그&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">index&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="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">item&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>저장 버튼을 눌렀을 때, 서버에 메세지를 전송하는 걸 흉내내기 위해 &lt;code>submitMessage&lt;/code> 함수를 추가했습니다. 그리고 서버에서 뭔가 오류가 발생하는 상황을 만들기 위해 일부러 예외를 던지도록 했습니다.&lt;/p>
&lt;p>이제 저장 버튼을 누르면 2초 대기 후 에러 메세지가 표시되는 걸 볼 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./images/03.png">&lt;/p>
&lt;p>React 19에 추가된 &lt;code>useActionState&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">useActionState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">useState&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">wait&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">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="kr">interface&lt;/span> &lt;span class="nx">submitMessageResult&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error&lt;/span>: &lt;span class="kt">string&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="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">const&lt;/span> &lt;span class="nx">submitMessage&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">message&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="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">`submitMessage: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">message&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="k">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&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">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&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">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&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">log&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&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"> * 각각 Action의 결과, 폼의 값을 전송할 Action, Action의 진행 상태를 나타냅니다.
&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="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">submitAction&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isPending&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useActionState&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prevState&lt;/span>: &lt;span class="kt">submitMessageResult&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">formData&lt;/span>: &lt;span class="kt">FormData&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">submitMessageResult&lt;/span>&lt;span class="p">&amp;gt;&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">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">formData&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kr">as&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>&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">prevState&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">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">prevState&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="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">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">submitMessage&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLog&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">prev&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 class="nx">prev&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>&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">error&lt;/span>: &lt;span class="kt">null&lt;/span> &lt;span class="p">};&lt;/span> &lt;span class="c1">// Action의 결과를 리턴합니다. 오류가 없었으므로 null을 리턴합니다.
&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 class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>: &lt;span class="kt">unknown&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">errorMessage&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error&lt;/span> &lt;span class="k">instanceof&lt;/span> &lt;span class="nb">Error&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">:&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="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">error&lt;/span>: &lt;span class="kt">errorMessage&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 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">error&lt;/span>: &lt;span class="kt">null&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 초기 상태는 null입니다.
&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="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col gp&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">form&lt;/span> &lt;span class="na">action&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">submitAction&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;message&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border-2 border-gray-300 rounded-md p-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;메세지를 입력하세요&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">value&lt;/span>&lt;span class="o">=&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="na">onChange&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&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 class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border-2 border-gray-300 rounded-md p-2 disabled:opacity-50 disabled:cursor-not-allowed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;submit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">disabled&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">isPending&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&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="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">isPending&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-red-500&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">form&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-lg font-bold text-gray-500&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">로그&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex items-center gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">index&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="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">item&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>직접 &lt;code>setError&lt;/code>를 통해 오류 메세지를 설정할 필요 없이 useActionState 훅을 통해 Action의 결과와 진행 상태를 관리할 수 있습니다. 그리고 이전 상태까지도 접근할 수 있어서, 뭔가 이전 상태에 따른 처리를 할 수도 있습니다.&lt;/p>
&lt;h4 id="213-use">2.1.3 use&lt;/h4>
&lt;p>아마도 웹에서 가장 빈번한 작업을 서버에서 비동기(async)로 데이터를 조회해서 목록을 출력하는 작업일겁니다. 그런 작업을 하나 추가해볼까요? &lt;code>pages/List.tsx&lt;/code> 파일을 추가하고 다음과 같이 작성합니다.&lt;/p>
&lt;blockquote>
&lt;p>단순한 구현을 위해 코드 중복은 무시하겠습니다!&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Suspense&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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="nx">MemberList&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./components/MemberList&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">const&lt;/span> &lt;span class="nx">wait&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">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="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 멤버 목록
&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">fetchMemberList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&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="k">return&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&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="nx">id&lt;/span>: &lt;span class="kt">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&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="nx">id&lt;/span>: &lt;span class="kt">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jim&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="kr">const&lt;/span> &lt;span class="nx">List&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">List&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* 비동기 처리를 위해 Suspense를 사용 */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Suspense&lt;/span> &lt;span class="na">fallback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">목록을&lt;/span> &lt;span class="err">불러오는&lt;/span> &lt;span class="err">중입니다&lt;/span>&lt;span class="p">...&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* 멤버 목록을 조회하는 함수의 Promise를 넘겨줍니다. */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MemberList&lt;/span> &lt;span class="na">memberListPromise&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">fetchMemberList&lt;/span>&lt;span class="p">()}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Suspense&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">List&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>MemberList&lt;/code> 컴포넌트에 멤버 목록을 조회하는 함수의 결과인 Promise를 넘겨주고 있습니다.
이제 &lt;code>pages/components/MemberList.tsx&lt;/code> 파일을 추가하고 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">use&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">interface&lt;/span> &lt;span class="nx">MemberListProps&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memberListPromise&lt;/span>: &lt;span class="kt">Promise&lt;/span>&lt;span class="o">&amp;lt;&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">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">name&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 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="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 memberListPromise 멤버 목록을 조회하는 Promise
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @returns 멤버 목록
&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">MemberList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">memberListPromise&lt;/span> &lt;span class="p">}&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">MemberListProps&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">// Promise의 결과가 resolve될 때까지 기다립니다.
&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">memberList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">memberListPromise&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">memberList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">member&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="p">&amp;lt;&lt;/span>&lt;span class="nt">li&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">member&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">member&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">member&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">li&lt;/span>&lt;span class="p">&amp;gt;&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">&amp;lt;/&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">MemberList&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Promise를 받아서 &lt;code>use&lt;/code> 훅으로 넘겨줍니다. &lt;code>use&lt;/code> 훅은 Promise 결과가 나올 때 까지(resolve 또는 reject) 기다리고, 그 결과를 반환합니다. 결과를 받아서 &lt;code>li&lt;/code> 요소를 생성합니다.&lt;/p>
&lt;p>마지막으로 &lt;code>Router.tsx&lt;/code> 파일에 다음과 같이 라우팅을 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">lazy&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">createBrowserRouter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Outlet&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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">const&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">lazy&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./pages/Index&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">About&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">lazy&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./pages/About&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">List&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">lazy&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kr">import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./pages/List&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">// eslint-disable-next-line react-refresh/only-export-components
&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">RootLayout&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Outlet&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">router&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">createBrowserRouter&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">path&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">RootLayout&lt;/span> &lt;span class="p">/&amp;gt;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">children&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">index&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">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Index&lt;/span> &lt;span class="p">/&amp;gt;,&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="nx">path&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/about&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">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">About&lt;/span> &lt;span class="p">/&amp;gt;,&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="nx">path&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/list&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">element&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">List&lt;/span> &lt;span class="p">/&amp;gt;,&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>이제 브라우저에서 &lt;code>http://localhost:3000/list&lt;/code> 주소로 이동하면, 서버의 응답을 기다리는 동안 &lt;code>Suspense&lt;/code> 컴포넌트의 &lt;code>fallback&lt;/code> 속성으로 설정한 내용이 표시되다가 서버의 응답이 오면 멤버 목록을 조회하는 작업이 진행되는 걸 볼 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./images/04.png">&lt;/p>
&lt;p>만약 서버에서 데이터를 조회하는 작업이 실패한다면, 그러니까 Promise가 reject 된다면 어떻게 될까요? &lt;code>fetchMemberList&lt;/code> 함수에서 일부러 오류가 발생하도록 수정해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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 멤버 목록
&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">fetchMemberList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&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="k">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&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="k">return&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&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="nx">id&lt;/span>: &lt;span class="kt">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&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="nx">id&lt;/span>: &lt;span class="kt">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jim&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;/code>&lt;/pre>&lt;/div>&lt;p>페이지를 새로고침 해보면, 컴포넌트 내용 대신에 오류 메세지가 표시됩니다!&lt;/p>
&lt;p align="center">&lt;img src="./images/05.png">&lt;/p>
&lt;p>이럴 때는 Promise에 catch 핸들러를 추가할 수도 있지만, &lt;code>ErrorBoundary&lt;/code>를 사용해서 오류를 사용자에게 친화적인 메시지로 표시할 수 있습니다. 우선, &lt;code>react-error-boundary&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 install react-error-boundary
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>src/pages/List.tsx&lt;/code> 파일의 &lt;code>Suspense&lt;/code> 컴포넌트를 &lt;code>ErrorBoundary&lt;/code> 컴포넌트로 감싸줍니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Suspense&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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="nx">MemberList&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./components/MemberList&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">ErrorBoundary&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-error-boundary&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">const&lt;/span> &lt;span class="nx">wait&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">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="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&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">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="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 멤버 목록
&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">fetchMemberList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&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">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Error&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="k">await&lt;/span> &lt;span class="nx">wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 서버와의 통신을 흉내내기 위해 2초 대기
&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="k">return&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">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;John&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="nx">id&lt;/span>: &lt;span class="kt">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jane&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="nx">id&lt;/span>: &lt;span class="kt">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jim&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="kr">const&lt;/span> &lt;span class="nx">List&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">List&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col gap-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ErrorBoundary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">fallback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">목록을&lt;/span> &lt;span class="err">불러오는&lt;/span> &lt;span class="err">중에&lt;/span> &lt;span class="err">문제가&lt;/span> &lt;span class="err">발생했습니다&lt;/span>&lt;span class="p">.&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* 비동기 처리를 위해 Suspense를 사용 */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Suspense&lt;/span> &lt;span class="na">fallback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">목록을&lt;/span> &lt;span class="err">불러오는&lt;/span> &lt;span class="err">중입니다&lt;/span>&lt;span class="p">...&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* 멤버 목록을 조회하는 함수의 Promise를 넘겨줍니다. */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MemberList&lt;/span> &lt;span class="na">memberListPromise&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">fetchMemberList&lt;/span>&lt;span class="p">()}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Suspense&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">ErrorBoundary&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">List&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하면 데이터를 조회하는 작업이 실패했을 때, 사용자에게 친화적인 메시지를 표시할 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./images/06.png">&lt;/p>
&lt;h1 id="react-191-업데이트하기">React 19.1 업데이트하기&lt;/h1>
&lt;p>다음 명령을 통해 설치된 패키지의 현재 버전과 최신 버전을 확인합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;gt; npm outdated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Package Current Wanted Latest Location Depended by
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@eslint/js 9.22.0 9.24.0 9.24.0 node_modules/@eslint/js react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@tailwindcss/vite 4.0.14 4.1.3 4.1.3 node_modules/@tailwindcss/vite react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@types/react 19.0.10 19.1.1 19.1.1 node_modules/@types/react react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@types/react-dom 19.0.4 19.1.2 19.1.2 node_modules/@types/react-dom react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@vitejs/plugin-react-swc 3.8.0 3.8.1 3.8.1 node_modules/@vitejs/plugin-react-swc react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">eslint 9.22.0 9.24.0 9.24.0 node_modules/eslint react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">globals 15.15.0 15.15.0 16.0.0 node_modules/globals react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">react 19.0.0 19.1.0 19.1.0 node_modules/react react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">react-dom 19.0.0 19.1.0 19.1.0 node_modules/react-dom react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">react-router-dom 7.3.0 7.5.0 7.5.0 node_modules/react-router-dom react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tailwindcss 4.0.14 4.1.3 4.1.3 node_modules/tailwindcss react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">typescript 5.7.3 5.7.3 5.8.3 node_modules/typescript react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">typescript-eslint 8.26.1 8.29.1 8.29.1 node_modules/typescript-eslint react-19
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vite 6.2.2 6.2.6 6.2.6 node_modules/vite react-19
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 내용 중 &lt;code>Current&lt;/code>, &lt;code>Wanted&lt;/code>, &lt;code>Latest&lt;/code> 열은 다음과 같은 의미를 가집니다.&lt;/p>
&lt;ul>
&lt;li>Current: 현재 설치된 버전&lt;/li>
&lt;li>Wanted: package.json 파일에 설정된 버전 범위를 만족하는 최신 버전(ex: &amp;ldquo;~5.7.2&amp;quot;는 5.7.2 &amp;lt;= 패키지 버전 &amp;lt; 5.8.0, &amp;ldquo;^6.2.0&amp;quot;은 6.2.0 &amp;lt;= 패키지 버전 &amp;lt; 7.0.0)&lt;/li>
&lt;li>Latest: 최신 버전&lt;/li>
&lt;/ul>
&lt;p>자 그러면, 모든 패키지를 &lt;code>Wanted&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 update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 다시 패키지 버전을 확인해보면, 다음과 같이 모든 패키지가 최신 버전으로 업데이트 된 것을 확인할 수 있습니다.&lt;/p>
&lt;h2 id="owner-stacks">Owner Stacks&lt;/h2>
&lt;p>React 19.1 버전에서 추가된 Owner Stacks는 컴포넌트의 오류가 발생했을 때, 오류가 발생한 컴포넌트의 계층 구조를 표시해줍니다. 그래서 실제로 어떤 컴포넌트에서 오류가 발생했고, 그 컴포넌트는 어떤 컴포넌트가 렌더링한 건지 확인할 수 있어 디버깅할 때 유용합니다.&lt;/p>
&lt;p>&lt;a href="https://react.dev/reference/react/captureOwnerStack">공식 문서&lt;/a>를 참조하여 다음과 같은 예제를 작성해보겠습니다.&lt;/p>
&lt;p>우선, 무언가 에러가 발생했을 때 화면에 오버레이를 표시할 수 있도록 &lt;code>index.html&lt;/code> 파일을 수정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;!doctype html&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">html&lt;/span> &lt;span class="na">lang&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;en&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">charset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;UTF-8&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;icon&amp;#34;&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;image/svg+xml&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/vite.svg&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;viewport&amp;#34;&lt;/span> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Vite + React + TS&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!--
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"> Error dialog in raw HTML
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"> since an error in the React app may crash.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;error-dialog&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;hidden&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;error-title&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-red&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Error&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">pre&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;error-body&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">pre&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mb-10&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h2&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Owner Stack:&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h2&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">pre&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;error-owner-stack&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;nowrap&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">pre&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;error-close&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onclick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;document.getElementById(&amp;#39;error-dialog&amp;#39;).classList.add(&amp;#39;hidden&amp;#39;)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Close
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;root&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;module&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/src/main.tsx&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 이 오버레이의 스타일을 &lt;code>src/index.css&lt;/code> 파일에 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">position&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">absolute&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">top&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="k">right&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="k">bottom&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="k">left&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="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">white&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">opacity&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.9&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">text-wrap&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">wrap&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">overflow&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">scroll&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="nt">h1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-top&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="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">22&lt;/span>&lt;span class="kt">px&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="nt">h2&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-top&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="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="kt">px&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">text-red&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">red&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">mb-20&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="kt">px&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">mb-10&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">mb-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">margin-bottom&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">mt-10&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-top&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="nt">pre&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">text-wrap&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">wrap&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="nt">pre&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">nowrap&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">text-wrap&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nowrap&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="p">#&lt;/span>&lt;span class="nn">error-dialog&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nc">hidden&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">display&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&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;code>Owner Stack&lt;/code>을 표시할 수 있도록 하는 유틸 함수를 &lt;code>src/utils/errorOverlay.ts&lt;/code> 파일에 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">onConsoleError&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">consoleMessage&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ownerStack&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="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">consoleMessage&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="nx">ownerStack&lt;/span>: &lt;span class="kt">string&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 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">errorDialog&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;error-dialog&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">errorBody&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;error-body&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">errorOwnerStack&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;error-owner-stack&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">errorBody&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">// Display console.error() message
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">errorBody&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">consoleMessage&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">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">errorOwnerStack&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">// Display owner stack
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">errorOwnerStack&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">ownerStack&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="s2">&amp;#34;-no owner stack-&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">errorDialog&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">// Show the dialog
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">errorDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">classList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hidden&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;/code>&lt;/pre>&lt;/div>&lt;p>이제 마지막으로 &lt;code>src/main.tsx&lt;/code> 파일에 &lt;code>console.error&lt;/code> 함수를 재정의하여 &lt;code>console.error&lt;/code> 함수가 호출될 때 오버레이에 에러 메세지와 &lt;code>Owner Stack&lt;/code>을 표시할 수 있도록 합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">captureOwnerStack&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">StrictMode&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">createRoot&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-dom/client&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="s2">&amp;#34;./index.css&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="nx">App&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./App.tsx&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">onConsoleError&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./utils/errorOverlay.ts&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">// 원래의 console.error 함수를 저장
&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">originalConsoleError&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&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">// 재정의된 console.error 함수
&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">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">patchedConsoleError&lt;/span>&lt;span class="p">(...&lt;/span>&lt;span class="nx">args&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">// 원래의 console.error 함수를 호출
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">originalConsoleError&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Owner Stack을 캡쳐
&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">ownerStack&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">captureOwnerStack&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 오버레이에 에러 메세지와 Owner Stack을 표시
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">onConsoleError&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">consoleMessage&lt;/span>: &lt;span class="kt">args&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="nx">ownerStack&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">createRoot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;root&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">render&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">StrictMode&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">App&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">StrictMode&lt;/span>&lt;span class="p">&amp;gt;&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>Owner Stack&lt;/code>을 표시할 수 있습니다. 그러면 간단하게 테스트를 해볼까요?&lt;/p>
&lt;p>다음과 같이 &lt;code>src/pages/components/Parent.tsx&lt;/code>와 &lt;code>src/pages/components/Child.tsx&lt;/code> 파일을 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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="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">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Child 컴포넌트에서 에러가 발생했습니다.&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="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Child&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;;&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="k">default&lt;/span> &lt;span class="nx">Child&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-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">Child&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./Child&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">const&lt;/span> &lt;span class="nx">Parent&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Parent&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Child&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Parent&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>src/pages/About.tsx&lt;/code> 파일에 &lt;code>Parent&lt;/code> 컴포넌트를 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">Link&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-router-dom&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="nx">Parent&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./components/Parent&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">const&lt;/span> &lt;span class="nx">About&lt;/span> &lt;span class="o">=&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">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-screen w-full flex flex-col&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-full flex items-center gap-2 text-blue-500 underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Link&lt;/span> &lt;span class="na">to&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/about&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">About&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Parent&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&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="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">About&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 브라우저에서 &lt;code>About&lt;/code> 페이지로 이동해서 에러를 확인해볼까요?&lt;/p>
&lt;p align="center">&lt;img src="./images/07.png">&lt;/p>
&lt;p>오버레이가 표시되고, &lt;code>Owner Stack&lt;/code>에 오류가 발생한 컴포넌트의 계층 구조가 표시되는 것을 확인할 수 있습니다. 에러가 발생한 컴포넌트의 부모 컴포넌트는 &lt;code>Parent&lt;/code> 컴포넌트이고, &lt;code>Parent&lt;/code> 컴포넌트의 부모 컴포넌트는 &lt;code>About&lt;/code> 컴포넌트인 거죠.&lt;/p>
&lt;p>단, &lt;code>Owner Stack&lt;/code>은 개발 환경에서만 사용가능하기 때문에 &lt;code>main.tsx&lt;/code>를 다음과 같이 수정할 필요가 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&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">captureOwnerStack&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">StrictMode&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react&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">createRoot&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;react-dom/client&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="s2">&amp;#34;./index.css&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="nx">App&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./App.tsx&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">onConsoleError&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./utils/errorOverlay.ts&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="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">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NODE_ENV&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;development&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="kr">const&lt;/span> &lt;span class="nx">originalConsoleError&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&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">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">patchedConsoleError&lt;/span>&lt;span class="p">(...&lt;/span>&lt;span class="nx">args&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">originalConsoleError&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">args&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">ownerStack&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">captureOwnerStack&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">onConsoleError&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">consoleMessage&lt;/span>: &lt;span class="kt">args&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="nx">ownerStack&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="nx">createRoot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;root&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">render&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">StrictMode&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">App&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">StrictMode&lt;/span>&lt;span class="p">&amp;gt;&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>process&lt;/code>를 사용하려면 다음 명령으로 &lt;code>node&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 install --save-dev @types/node
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item><item><title>k8s 노드에 접속하기(+ AKS 노드에 접속하기)</title><link>https://netscout.github.io/posts/k8s-%EB%85%B8%EB%93%9C%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0-aks-%EB%85%B8%EB%93%9C%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0/</link><pubDate>Tue, 03 Sep 2024 11:11:51 +0900</pubDate><guid>https://netscout.github.io/posts/k8s-%EB%85%B8%EB%93%9C%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0-aks-%EB%85%B8%EB%93%9C%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0/</guid><description>k8s에 대해서 스터디하거나 서비스를 배포하다 보면 가끔 워커 노드에 접속해야 할 때가 있습니다. 예를 들면 pod에서 DB나 Redis 등에 제대로 접속하지 못할 때 혹시 네트워크에 문제 있는지 확인하거나 워커 노드의 로그 등을 수집할 때 해당 로그의 위치가 …</description><content>&lt;p>k8s에 대해서 스터디하거나 서비스를 배포하다 보면 가끔 워커 노드에 접속해야 할 때가 있습니다. 예를 들면 pod에서 DB나 Redis 등에 제대로 접속하지 못할 때 혹시 네트워크에 문제 있는지 확인하거나 워커 노드의 로그 등을 수집할 때 해당 로그의 위치가 어디인지 등을 확인해야 할 때가 있습니다.&lt;/p>
&lt;p>이럴 때 노드에 접속할 수 있는 몇 가지 방법을 알아보겠습니다.&lt;/p>
&lt;h2 id="k9s-사용하기">k9s 사용하기&lt;/h2>
&lt;h3 id="k9s-설치하기">k9s 설치하기&lt;/h3>
&lt;p>&lt;a href="https://k9scli.io/">k9s&lt;/a>는 k8s 클러스터를 쉽게 관리할 수 있는 터미널 기반의 UI 도구입니다. k9s를 사용하면 k8s 클러스터의 상태를 실시간으로 확인하고, 리소스를 생성하거나 삭제할 수 있습니다. 또한 k8s 클러스터의 로그를 확인하거나, 특정 pod에 쉘로 접속하는 등의 작업을 쉽게 수행할 수 있습니다. 그래픽 환경이 아니라 처음엔 어색할 수 있지만, 익숙해지면 훨씬 빠르게 작업할 수 있습니다.&lt;/p>
&lt;p>k9s는 다음과 같이 설치할 수 있습니다.&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"># MacOS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; brew install derailed/k9s/k9s
&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"># Ubuntu(linuxbrew)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; brew install derailed/k9s/k9s
&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"># Windows&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; scoop install k9s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;strong>k9s&lt;/strong>를 실행하면 됩니다.&lt;/p>
&lt;h3 id="k9s로-노드에-접속하기">k9s로 노드에 접속하기&lt;/h3>
&lt;p>k9s에서 &lt;strong>:pod&lt;/strong> 명령으로 pod를 선택하고 &lt;strong>s&lt;/strong>를 누르면 해당 pod에 쉘로 접속할 수 있습니다. 그런데 &lt;strong>:node&lt;/strong> 명령으로 노드 목록을 표시해보면 노드에는 쉘로 접속할 수 있는 &lt;strong>s&lt;/strong> 옵션이 표시되지 않습니다. 클러스터의 설정 파일을 찾아서 해당 클러스터에 대해서 쉘을 사용할 수 있도록 설정해줘야 합니다.&lt;/p>
&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; k9s info
&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="p">|&lt;/span> &lt;span class="p">|&lt;/span>/ _/ __ &lt;span class="se">\_&lt;/span>_____
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &amp;lt; &lt;span class="se">\_&lt;/span>___ / ___/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\ &lt;/span> / /&lt;span class="se">\_&lt;/span>__ &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>&lt;span class="p">|&lt;/span>____&lt;span class="p">|&lt;/span>__ &lt;span class="se">\ &lt;/span>/____//____ &amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="se">\/&lt;/span> &lt;span class="se">\/&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">Version: v0.32.5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Config: /Users/onlifecoding/Library/Application Support/k9s/config.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Config&lt;/strong> 항목에 표시되는 경로에 이동하면 &lt;strong>clusters&lt;/strong> 폴더가 있고, 이 폴더에는 k9s로 접속한 적이 있는 k8s 클러스터의 설정 파일이 각 폴더 별로 저장되어 있습니다.&lt;/p>
&lt;p>쉘로 접속하려는 클러스터의 폴더로 이동해서 &lt;strong>config.yaml&lt;/strong> 파일의 &lt;strong>nodeShell&lt;/strong> 항목을 &lt;strong>true&lt;/strong>로 변경하면 됩니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">k9s&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cluster&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">aks-test-stg&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">active&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lockFavorites&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">favorites&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">view&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">active&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">node&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">featureGates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodeShell&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 노드에 쉘 접근 허용하기&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">portForwardAddress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">localhost&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 다시 k9s에서 &lt;strong>:node&lt;/strong> 명령을 실행하면 노드에 쉘로 접속할 수 있는 &lt;strong>s&lt;/strong> 옵션이 표시됩니다. 해당 노드를 선택하고 &lt;strong>s&lt;/strong>를 누르면 노드에 pod이 배포되고, 해당 pod로 노드의 네트워크 통신 등을 테스트 할 수 있습니다.&lt;/p>
&lt;p>참고로, 생성되는 pod는 앞서 &lt;strong>k9s info&lt;/strong>로 확인했던 &lt;strong>Config&lt;/strong> 파일에 설정되어 있으며, 기본 값은 이렇습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">shellPod&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">busybox:1.35.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100m&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">100Mi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 방법은 매우 간단하긴 하지만 노드 자체에는 접근할 수 없다는 단점이 있습니다.&lt;/p>
&lt;h2 id="aks-노드에-접속하기">AKS 노드에 접속하기&lt;/h2>
&lt;p>AKS에서는 &lt;strong>kubectl debug&lt;/strong>를 통해 노드에 접속할 수 있습니다.&lt;/p>
&lt;p>우선 k9s나 kubectl을 통해 노드의 이름을 확인합니다.&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; kubectl get nodes -o wide
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aks-default-28123321-vmss000000 Ready &amp;lt;none&amp;gt; 18m v1.29.7 10.224.0.4 &amp;lt;none&amp;gt; Ubuntu 22.04.4 LTS 5.15.0-1071-azure containerd://1.7.20-1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 접속하려는 노드의 이름을 사용하여 &lt;strong>kubectl debug&lt;/strong> 명령을 실행합니다.&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; kubectl debug node/aks-default-28123321-vmss000000 -it --image&lt;span class="o">=&lt;/span>mcr.microsoft.com/cbl-mariner/busybox:2.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Creating debugging pod node-debugger-aks-default-28123321-vmss000000-2jj6t with container debugger on node aks-default-28123321-vmss000000.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">If you don&lt;span class="err">&amp;#39;&lt;/span>t see a &lt;span class="nb">command&lt;/span> prompt, try pressing enter.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>노드에 debug용 pod이 생성되고, 해당 pod에 접속되었습니다. 이제 다음 명령으로 node에 접근할 수 있습니다.&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; chroot /host
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>노드에서 확인작업을 마치고 &lt;strong>exit&lt;/strong> 빠져나오면, pod은 종료되지만 삭제되지 않고 남아있는데요, k9s에서 &lt;strong>ctrl + d&lt;/strong>를 눌러 삭제하거나 &lt;strong>kubectl delete pod&lt;/strong> 명령으로 삭제할 수 있습니다.&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; kubectl delete pod node-debugger-aks-default-28123321-vmss000000-2jj6t
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="혹시-클라우드-프로바이더에-상관없이-노드에-접속하는-방법은">혹시 클라우드 프로바이더에 상관없이 노드에 접속하는 방법은?&lt;/h2>
&lt;p>마지막으로 &lt;a href="https://github.com/luksa/kubectl-plugins">kubectl-plugins&lt;/a>를 이용하는 방법이 있습니다. 이 방법은 클라우드 프로바이더에 상관없이 노드에 접속할 수 있는 방법인데요, 클러스터의 어드민 권한이 필요합니다.&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://k9scli.io/topics/shell/">k9s NodeShell&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/ko-kr/azure/aks/node-access">유지 관리 또는 문제 해결을 위해 AKS(Azure Kubernetes Service) 클러스터 노드에 연결&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/luksa/kubectl-plugins">kubectl-plugins&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>S3에 업로드된 이미지 접근시 CORS 이슈, 그리고 no-cors</title><link>https://netscout.github.io/posts/s3%EC%97%90-%EC%97%85%EB%A1%9C%EB%93%9C%EB%90%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%91%EA%B7%BC%EC%8B%9C-cors-%EC%9D%B4%EC%8A%88-%EA%B7%B8%EB%A6%AC%EA%B3%A0-no-cors/</link><pubDate>Fri, 21 Jun 2024 08:46:23 +0900</pubDate><guid>https://netscout.github.io/posts/s3%EC%97%90-%EC%97%85%EB%A1%9C%EB%93%9C%EB%90%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%91%EA%B7%BC%EC%8B%9C-cors-%EC%9D%B4%EC%8A%88-%EA%B7%B8%EB%A6%AC%EA%B3%A0-no-cors/</guid><description>현재 개발 중인 서비스는 이미지나 기타 파일을 업로드하고 가져올 때 NCP(네이버 클라우드 플랫폼)의 Object Storage를 사용 중입니다. 제목에서는 S3라고 했는데? 라고 생각하실 수 있지만, NCP의 Object Storage는 S3와 호환되는 API를 …</description><content>&lt;h2 id="개요">개요&lt;/h2>
&lt;p>현재 개발 중인 서비스는 이미지나 기타 파일을 업로드하고 가져올 때 NCP(네이버 클라우드 플랫폼)의 Object Storage를 사용 중입니다. &lt;code>제목에서는 S3라고 했는데?&lt;/code> 라고 생각하실 수 있지만, NCP의 Object Storage는 S3와 호환되는 API를 제공하기 때문에 사실상 S3와 동일하게 생각하고 사용할 수 있습니다. 따라서 이후에는 그냥 S3라고 표현하겠습니다.&lt;/p>
&lt;p>S3에 업로드된 이미지 등의 에셋을 가져올 때는 CORS 이슈가 발생할 수 있습니다. 이 문제를 해결하려면 S3의 버킷에 CORS 설정을 추가해줘야 합니다. 여러가지 방법이 있을 수 있지만 이 글에서는 &lt;a href="https://s3browser.com/">S3 Browser&lt;/a>를 사용하여 CORS 설정을 추가하는 방법을 알아보겠습니다.&lt;/p>
&lt;h2 id="버킷에-cors-설정하기">버킷에 CORS 설정하기&lt;/h2>
&lt;p>S3 Browser를 통해 S3에 접속합니다. 만약 NCP의 Object Storage와 같이 S3 API와 호환되는 서비스를 사용 중이라면, &lt;strong>Account type&lt;/strong>을 &lt;strong>S3 Compatible Storage&lt;/strong>로 선택하고, 접속합니다.&lt;/p>
&lt;p align="center">&lt;img src="./cors01.png">&lt;/p>
&lt;p>접속하면, 해당 계정의 버킷 목록이 나타납니다. CORS를 설정하고자 하는 버킷을 선택하고 &lt;strong>Ctrl + R&lt;/strong> 을 누르거나 마우스 우클릭 후 &lt;strong>CORS Configuration&lt;/strong>을 선택합니다. 그리고 XML 형식으로 CORS 설정을 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34; standalone=&amp;#34;yes&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;CORSConfiguration&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://s3.amazonaws.com/doc/2006-03-01/&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CORSRule&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;AllowedMethod&amp;gt;&lt;/span>GET&lt;span class="nt">&amp;lt;/AllowedMethod&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;AllowedOrigin&amp;gt;&lt;/span>http://localhost:3000&lt;span class="nt">&amp;lt;/AllowedOrigin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;AllowedHeader&amp;gt;&lt;/span>*&lt;span class="nt">&amp;lt;/AllowedHeader&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/CORSRule&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/CORSConfiguration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;strong>Apply&lt;/strong> 버튼을 눌러 설정을 저장합니다. 이제 해당 버킷에 업로드된 이미지 등의 에셋을 가져올 때 CORS 이슈가 발생하지 않습니다&amp;hellip; 그런데 말입니다. 😣&lt;/p>
&lt;h2 id="그런데-말입니다">그런데 말입니다&lt;/h2>
&lt;p>그런데 한 가지 변수가 있습니다. 이 글의 제목에서 언급된 &lt;strong>no-cors&lt;/strong>입니다. &lt;strong>no-cors&lt;/strong>가 뭔지 일단 알아보야겠죠? &lt;strong>no-cors&lt;/strong>는 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode">Sec-Fetch-Mode&lt;/a> 헤더의 설정 값 중에 하나입니다.&lt;/p>
&lt;h3 id="sec-fetch-mode">Sec-Fetch-Mode??&lt;/h3>
&lt;p>MDN의 설명을 보면, 유저가 HTML 페이지를 왔다 갔다 하는 요청과 이미지 등의 리소스를 가져오는 요청을 구분하기 위해 사용된다고 합니다. 그러니까 HTML 문서를 요청하는 경우는 &lt;strong>navigate&lt;/strong>, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#value">이미지 등의 리소스를 요청하는 경우는 &lt;strong>no-cors&lt;/strong>로 설정된다는 거죠.&lt;/a>&lt;/p>
&lt;p>&lt;strong>no-cors&lt;/strong> 요청은 메서드가 HEAD, GET, POST 중 하나여야 하고, &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header">CORS에서 허용된 헤더&lt;/a> 만을 사용할 수 있으며 요청의 결과로 받은 응답을 javascript 등에서 접근할 수 없다고 합니다.&lt;/p>
&lt;p>이제 제가 왜 갑자기 &lt;strong>no-cors&lt;/strong> 이야기를 하는지 눈치 채신 분들도 있을 겁니다. css의 &lt;strong>background-image&lt;/strong> 속성에 S3에 업로르된 이미지가 url로 지정되는 부분이 있었는데, 바로 여기서 그 이미지를 가져오는 요청이 &lt;strong>no-cors&lt;/strong> 였던 겁니다!&lt;/p>
&lt;p>그러니까 S3에 CORS 설정을 해줬지만 계속 CORS 오류가 발생한 거죠&amp;hellip; 😫&lt;/p>
&lt;h2 id="해결-방법">해결 방법&lt;/h2>
&lt;p>관련해서 문제를 찾다보니 이미 관련해서 먼저 고민한 분들이 있었습니다. &lt;a href="https://velog.io/@wwlee94/CloudFront-%EC%9D%B4%EB%AF%B8%EC%A7%80-CORS-%EC%9D%B4%EC%8A%88">CloudFront 이미지 CORS 이슈&lt;/a> 를 보면, 결론적으로 이 문제는 &lt;strong>Origin&lt;/strong> 헤더가 누락되었기 때문에 발생한 문제였습니다. CORS 허용이 필요한 경우 요청에 대한 응답으로 &lt;strong>Access-Control-Allow-Origin&lt;/strong> 헤더에 요청한 &lt;strong>Origin&lt;/strong>을 포함해서 돌려줘야 하는데, S3는 요청에 &lt;strong>Origin&lt;/strong> 헤더가 없는 경우 응답에 &lt;strong>Access-Control-Allow-Origin&lt;/strong> 헤더를 넣어주지 않는다는 거죠!&lt;/p>
&lt;p>그래서 기존에는 S3의 에셋에 직접 접근 하던걸 NCP의 CDN+로 접근하도록 변경한 다음에, CDN+가 응답 헤더에 &lt;code>Access-Control-Allow-Origin: *&lt;/code>를 추가해주도록 설정해서 문제를 해결했습니다. 에셋에 접근하는 도메인이 하나가 아니었기 때문에 부득이 하게 &lt;code>*&lt;/code>로 설정했지만, 그렇지 않은 경우는 명시적으로 도메인을 적어줘야 합니다.&lt;/p>
&lt;h2 id="난-잘되는데">난 잘되는데?&lt;/h2>
&lt;p>참고로 브라우저 마다 &lt;strong>no-cors&lt;/strong> 를 처리하는 방법이 조금씩 다른 듯 합니다. 테스트 결과 별도의 처리 없이도 &lt;strong>Firefox&lt;/strong>에서는 이미지를 가져오는 데 문제가 없었지만, &lt;strong>Chrome&lt;/strong>에서는 문제가 발생했습니다.&lt;/p>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode">Sec-Fetch-Mode&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header">CORS-safelisted_request_header&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://velog.io/@wwlee94/CloudFront-%EC%9D%B4%EB%AF%B8%EC%A7%80-CORS-%EC%9D%B4%EC%8A%88">CloudFront 이미지 CORS 이슈&lt;/a>&lt;/li>
&lt;/ul></content></item><item><title>How to Terraform(7) - 코드 중복 최소화하기</title><link>https://netscout.github.io/posts/how-to-terraform-7-%EC%BD%94%EB%93%9C-%EC%A4%91%EB%B3%B5-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EA%B8%B0/</link><pubDate>Sat, 16 Dec 2023 14:09:14 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-7-%EC%BD%94%EB%93%9C-%EC%A4%91%EB%B3%B5-%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EA%B8%B0/</guid><description>이전 글에서 우리는 모듈을 활용하여 코드를 재사용하는 방법을 알아봤습니다. 그 결과 동일한 리소스에 대해서 코드 중복을 많이 줄일 수 있었죠. 그런데 여전히 중복되는 코드가 있었습니다. 각 리소스마다 프로바이더와 백엔드는 어쩔 수 없이 매번 작성해야 했습니다. 이번 …</description><content>&lt;h2 id="시리즈-목차">시리즈 목차&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5%2FHow%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5.md">How to Terraform(2) - 변수와 출력&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC%2FHow%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC.md">How to Terraform(3) - 상태 관리&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%284%29%20-%20%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%20%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%284%29%20-%20%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%20%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0.md">How to Terraform(4) - 상태관리 개선하기&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88%2FHow%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88.md">How to Terraform(5) - 모듈&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%286%29%20-%20%EB%AA%A8%EB%93%88%20%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%2FHow%20to%20Terraform%286%29%20-%20%EB%AA%A8%EB%93%88%20%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC.md">How to Terraform(6) - 모듈 버전관리&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>이전 글에서 우리는 모듈을 활용하여 코드를 재사용하는 방법을 알아봤습니다. 그 결과 동일한 리소스에 대해서 코드 중복을 많이 줄일 수 있었죠. 그런데 여전히 중복되는 코드가 있었습니다. 각 리소스마다 프로바이더와 백엔드는 어쩔 수 없이 매번 작성해야 했습니다. 이번 글에서는 이런 중복을 최소화하는 방법을 알아보겠습니다.&lt;/p>
&lt;h2 id="terragrunt-설치하기">Terragrunt 설치하기&lt;/h2>
&lt;p>Terragrunt는 Terraform의 래퍼(wrapper)로 동작하며, Terraform 코드에서 중복되는 부분을 많이 줄일 수 있도록 도와줍니다. 래퍼로 동작하기 때문에 Terraform으로 실행가능한 init, plan, apply, destroy등의 명령은 그대로 사용할 수 있습니다. 우선 Terragrunt를 설치해볼까요?&lt;/p>
&lt;h3 id="homebrew로-설치하기">Homebrew로 설치하기&lt;/h3>
&lt;p>리눅스와 맥에서는 Homebrew를 사용해서 Terragrunt를 설치할 수 있습니다.&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; brew install terragrunt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; terragrunt --version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">terragrunt version 0.54.5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Terraform과 Terragrunt는 다음과 같이 alias로 등록하여 각각 tf, tg로 사용할 수 있습니다.&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="nb">alias&lt;/span> &lt;span class="nv">tf&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;terraform&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">alias&lt;/span> &lt;span class="nv">tg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;terragrunt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&lt;h3 id="terragrunt-설정하기">Terragrunt 설정하기&lt;/h3>
&lt;p>먼저 이전 글에서 작업하던 폴더 구조를 다시 확인해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">terraform-live
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── service1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | ├── main.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | └── outputs.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── service2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── main.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── outputs.tf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>terraform-modules 리포지토리에 정의되어 있는 모듈을 참조하여 service1, service2를 정의했었습니다. 각 서비스의 main.tf 파일에는 다음과 같은 내용이 공통으로 존재했습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>&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>Terragrunt로 이 중복을 제거해보겠습니다.&lt;/p>
&lt;p>terraform-live/terragrunt.hcl 파일을 생성하고 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="cl">&lt;span class="k">generate&lt;/span> &lt;span class="s2">&amp;#34;providers&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;providers.tf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> if_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;overwrite_terragrunt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> contents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">&amp;lt;&amp;lt;&lt;/span>&lt;span class="k">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">//&lt;/span> &lt;span class="k">아시아&lt;/span> &lt;span class="k">태평양&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">서울&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">리전&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 service1의 모든 파일을 삭제하고 terragrunt.hcl 파일을 생성하고 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="cl">&lt;span class="k">terraform&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> source&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="n"> &amp;#34;github.com/netscout/terraform-modules//modules/ec2?ref&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">v0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="err">&amp;#34;&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="k">include&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">find_in_parent_folders&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">inputs&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&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-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">terraform-live
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── terragrunt.hcl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── service1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> | └── terragrunt.hcl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── service2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── main.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── outputs.tf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 Terragrunt의 plan 명령을 실행합니다. 참고로, Terragrunt는 plan 명령을 실행 할 때, init 명령을 자동으로 실행합니다.&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; &lt;span class="nb">cd&lt;/span> dev/ec2/service1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tg plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">2&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes to Outputs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>provider 설정이 없는데도 정상적으로 동작합니다. 그리고 outputs.tf가 없어졌는데도 출력값을 확인할 수 있습니다. 어떻게 가능한지 확인해볼까요? service1 폴더를 다시 확인해보면, &lt;code>.terragrunt-cache&lt;/code> 폴더가 생성되어 있습니다. 이 폴더에는 Terragrunt가 실행될 때 자동으로 생성된 Terraform 코드가 저장되어 있습니다. 폴더 구조를 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">service1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="o">.&lt;/span>&lt;span class="n">terragrunt&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">lIQuDV1aWBSG5nm4zKCulAdnsus&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">modules&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">ec2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">outputs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">providers&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">variables&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└──&lt;/span> &lt;span class="n">terragrunt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hcl&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>다른 파일들이 있긴 하지만, 핵심만 추리면 이렇습니다. 그러니까 모듈로 선언된 Terraform 코드가 그대로 다운로드 되어 있고, &lt;code>providers.tf&lt;/code> 파일이 새로 생성되어 있습니다. 이 파일의 내용을 보면 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>provider 설정 없이도 plan 명령이 정상적으로 동작한 이유가 뭔지 이제 알 수 있죠? Terragrunt가 자동으로 생성해준 코드를 통해 provider 설정이 추가되었기 때문입니다. 다시 terraform-live/terragrunt.hcl 파일의 내용을 확인해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="cl">&lt;span class="k">generate&lt;/span> &lt;span class="s2">&amp;#34;providers&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;providers.tf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> if_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;overwrite_terragrunt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> contents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">&amp;lt;&amp;lt;&lt;/span>&lt;span class="k">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">//&lt;/span> &lt;span class="k">아시아&lt;/span> &lt;span class="k">태평양&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">서울&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">리전&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>providers.tf 파일을 생성하도록 설정하였고, 파일이 존재한다면 덮어쓰도록 했습니다. 그리고 파일의 내용은 provider 블록을 통해 aws 프로바이더를 정의해두었습니다. 그래서 &lt;code>tg plan&lt;/code>명령이 실행 될 때 Terragrunt가 providers.tf 파일을 생성하고, 이 파일을 통해 provider 설정이 추가되었던 것입니다.&lt;/p>
&lt;h3 id="좀-더-복잡한-경우">좀 더 복잡한 경우&lt;/h3>
&lt;p>겨우 이 몇 줄의 코드 때문에 Terragrunt를 써야 하는 거냐고 생각하실 수도 있지만 이 코드는 좀 더 길어질 수도 있습니다. 예를 들어 프로바이더 설정과 백엔드 설정을 추가한다면 어떻게 될까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># terragrunt가 각 모듈에 대해서 backend.tf를 생성한다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">remote_state&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;s3&amp;#34;&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="n"> generate&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;backend.tf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> if_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;overwrite&amp;#34;&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="n"> config&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-state-example-20231216&amp;#34;&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="c1">&lt;/span>&lt;span class="n"> key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${path_relative_to_include()}/terraform.tfstate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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="c1"> # 상태 파일을 락으로 사용할 DynamoDB 테이블
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n"> dynamodb_table&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&amp;#34;&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="c1">&lt;/span>&lt;span class="n"> encrypt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">generate&lt;/span> &lt;span class="s2">&amp;#34;providers&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;providers.tf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> if_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;overwrite_terragrunt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> contents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">&amp;lt;&amp;lt;&lt;/span>&lt;span class="k">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">terraform&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="c1">&lt;/span> &lt;span class="k">required_providers&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> aws&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hashicorp/aws&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt; 5.0&amp;#34;&lt;/span>&lt;span class="c1"> # 5.0 이상 버전 사용
&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>&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="err">//&lt;/span> &lt;span class="k">AWS&lt;/span> &lt;span class="k">프로바이더&lt;/span> &lt;span class="k">설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">//&lt;/span> &lt;span class="k">아시아&lt;/span> &lt;span class="k">태평양&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">서울&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">리전&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n"> region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>remote_state 블록에는 모든 Terraform 리소스에 적용될 backend 설정을 정의했습니다. config 속성의 내용은 기존의 backend 속성의 내용을 그대로 옮긴 것이며, generate 속성에는 backend.tf 파일을 생성하고 기존에 존재하는 경우 덮어쓰도록 설정했습니다. 자세히 보시면 config.key 속성이 기존과 다릅니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="cl">&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${path_relative_to_include()}/terraform.tfstate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>즉, 현재 이 파일의 위치를 기준으로 해서 Terragrunt 명령이 실행되는 폴더의 경로를 가져오도록 했습니다. 만약 &lt;code>dev/ec2/service1&lt;/code> 폴더에서 &lt;code>tg plan&lt;/code> 명령을 실행한다면, key 값은 &lt;code>dev/ec2/service1/terraform.tfstate&lt;/code>가 됩니다. 매번 리소스를 정의할 때 마다 폴더 경로를 key 값으로 수정하지 않아도 알아서 해준다는 거죠!&lt;/p>
&lt;p>그리고 generate 블록에는 프로바이더 버전 정보를 추가했습니다. 이렇게 하면 모든 리소스에 대해서 프로바이더 버전을 일괄적으로 관리할 수 있습니다.&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>이제 Terraform을 통해 IaC를 구현하는 방법의 기초적인 내용은 모두 알아보았습니다. 익숙해지기 전에는 예전에 하던 것 처럼 수작업으로 일일이 리소스를 생성하는 게 훨씬 빠르다는 생각도 들게 됩니다. 하지만, 한 번 잘 설정해두면 속도가 빨라지는 것 뿐만 아니라 리소스의 변경을 추적할 수 있고, 리소스를 정리할 때도 커맨드 한 번으로 정리할 수 있게 됩니다. 지금까지 읽어주셔서 감사드리며, 도움이 되셨기를 바랍니다.&lt;/p>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://terragrunt.gruntwork.io/">Terragrunt&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>How to Terraform(6) - 모듈 버전관리</title><link>https://netscout.github.io/posts/how-to-terraform-6-%EB%AA%A8%EB%93%88-%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC/</link><pubDate>Sat, 09 Dec 2023 14:04:13 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-6-%EB%AA%A8%EB%93%88-%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC/</guid><description>이전 글에서는 모듈을 활용해서 반복되는 리소스를 재사용 가능한 형태로 정의하는 방법을 알아봤습니다. 그런데 모듈로 재사용성을 높이는 건 좋지만, 문제가 있긴 했습니다. 모듈을 업데이트해야 하는 경우, 이미 모듈을 사용해서 생성된 리소스 역시 업데이트되는 모듈의 영향을 …</description><content>&lt;h2 id="시리즈-목차">시리즈 목차&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5%2FHow%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5.md">How to Terraform(2) - 변수와 출력&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC%2FHow%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC.md">How to Terraform(3) - 상태 관리&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%284%29%20-%20%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%20%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%284%29%20-%20%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%20%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0.md">How to Terraform(4) - 상태관리 개선하기&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88%2FHow%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88.md">How to Terraform(5) - 모듈&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="../How%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88%2FHow%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88.md">이전 글&lt;/a>에서는 모듈을 활용해서 반복되는 리소스를 재사용 가능한 형태로 정의하는 방법을 알아봤습니다. 그런데 모듈로 재사용성을 높이는 건 좋지만, 문제가 있긴 했습니다. 모듈을 업데이트해야 하는 경우, 이미 모듈을 사용해서 생성된 리소스 역시 업데이트되는 모듈의 영향을 받게 된다는 점입니다. 모듈을 업데이트 하되 기존 리소스는 기존 모듈을 그대로 사용할 수 있도록 하위호환성을 도입하려면 어떻게 해야 할까요?&lt;/p>
&lt;h2 id="git-으로-모듈-버전-관리하기">Git 으로 모듈 버전 관리하기&lt;/h2>
&lt;p>여기서는 Git을 이용해서 모듈의 버전을 관리하고, 리소스도 각 모듈의 버전을 지정해서 참조하도록 해서 모듈과 리소스 간의 의존성을 관리하는 방법을 알아보겠습니다. 이렇게 하면 모듈을 업데이트할 때 기존 리소스에 영향을 주지 않으면서도 모듈을 업데이트할 수 있습니다. Git으로 모듈 버전을 관리하려면 2개의 Git 리포지토리가 필요합니다. 하나는 모듈을 관리하는 리포지토리, 또 하나는 모듈을 사용하여 리소스를 정의하는 리포지토리입니다. 우선 모듈을 관리하는 리포지토리를 만들어 보겠습니다.&lt;/p>
&lt;h2 id="모듈-리포지토리-만들기">모듈 리포지토리 만들기&lt;/h2>
&lt;p>모듈을 관리하는 리포지토리를 terraform-modules, 모듈을 사용해서 리소스를 관리하는 리포지토리를 terraform-live라고 가정하고 진행하겠습니다. 먼저 terraform-modules 리포지토리를 생성하고, &lt;a href="../How%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88%2FHow%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88.md">이전 글&lt;/a>에서 작성했던 EC2 인스턴스 모듈을 이 리포지토리에 옮깁니다. 폴더 구조는 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">terraform&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">modules&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└──&lt;/span> &lt;span class="n">modules&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└──&lt;/span> &lt;span class="n">ec2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">├──&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">├──&lt;/span> &lt;span class="n">outputs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└──&lt;/span> &lt;span class="n">variables&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 이 리포지토리를 GitHub에 푸시합니다. 그리고 다음 명령을 통해 모듈의 버전을 태그합니다.&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; git tag -a v0.0.1 -m &lt;span class="s2">&amp;#34;EC2 모듈 추가&amp;#34;&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">~/src/terraform-modules main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; git push --follow-tags
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enumerating objects: 1, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Counting objects: 100% &lt;span class="o">(&lt;/span>1/1&lt;span class="o">)&lt;/span>, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing objects: 100% &lt;span class="o">(&lt;/span>1/1&lt;span class="o">)&lt;/span>, &lt;span class="m">185&lt;/span> bytes &lt;span class="p">|&lt;/span> 185.00 KiB/s, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>delta 0&lt;span class="o">)&lt;/span>, reused &lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>delta 0&lt;span class="o">)&lt;/span>, pack-reused &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">To https://github.com/netscout/terraform-modules.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * &lt;span class="o">[&lt;/span>new tag&lt;span class="o">]&lt;/span> v0.0.1 -&amp;gt; v0.0.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>만약 이전 커밋에 지정된 태그를 삭제하고 싶다면 &lt;code>git tag -d v0.0.1&lt;/code>의 형태로 명령을 사용하고 새로운 커밋에 다시 태그를 붙이면 됩니다. 이미 태그를 푸시한 상태라면 &lt;code>git push --delete origin v0.0.1&lt;/code> 명령을 사용하면 됩니다.&lt;/p>&lt;/blockquote>
&lt;h2 id="모듈-리포지토리-참조하기">모듈 리포지토리 참조하기&lt;/h2>
&lt;p>EC2 모듈의 첫 버전을 v0.0.1로 태그 했습니다. 이제 리소스 리포지토리를 만들어서 이 모듈을 참조해보겠습니다. 우선 terraform-live라는 리포지토리를 생성하고, &lt;a href="../How%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88%2FHow%20to%20Terraform%285%29%20-%20%EB%AA%A8%EB%93%88.md">이전 글&lt;/a>에서 작성했던 EC2 인스턴스 리소스를 이 리포지토리로 옮겨줍니다. 폴더 구조는 다음과 같습니다.&lt;/p>
&lt;p>terraform-live 폴더 안에 dev 폴더를 만들고, 그 안에 ec2 폴더를 만들고, service1 폴더를 만들어서 EC2 인스턴스 리소스를 옮겨줍니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">terraform-live
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── service1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── main.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── outputs.tf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이전 글에서 작성한 &lt;code>main.tf&lt;/code>는 로컬에 있는 모듈을 참조하도록 설정되어 있습니다. 로컬 대신에 Git의 모듈 리포지토리를 참조하도록 변경하겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="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">module&lt;/span> &lt;span class="s2">&amp;#34;ec2&amp;#34;&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"> //source = &amp;#34;../../modules/ec2&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;github.com/netscout/terraform-modules//modules/ec2?ref=v0.0.1&amp;#34;&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="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">env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&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>source 속성을 자세히 보시면 중간에 &lt;code>//&lt;/code>가 들어가 있습니다. &lt;code>//&lt;/code>의 앞쪽에는 모듈이 저장된 git 리포지토리의 주소를, 그리고 뒤 쪽에는 모듈의 경로와 태그로 지정된 버전을 명시합니다. 모듈을 참조하기 위한 Terraform 코드 작성은 완료됐지만 아직 한 가지 더 검토해야 할 게 남아있습니다.&lt;/p>
&lt;h2 id="terraform-에게-git-리포지토리-접근-허용해주기">Terraform 에게 Git 리포지토리 접근 허용해주기&lt;/h2>
&lt;p>아마도 모듈 리포지토리는 private 리포지토리로 생성되었을 겁니다. 회사에서 작업하는 모듈에 대한 정의를 public 하게 공개할 수는 없기 때문이죠. 그렇다면 Terraform이 private 리포지토리에 접근할 수 있는 방법을 알려줘야 합니다.&lt;/p>
&lt;h3 id="ssh-키-생성-및-등록하기">SSH 키 생성 및 등록하기&lt;/h3>
&lt;p>Terraform이 모듈 리포지토리를 참조하기 위해서 SSH 키를 생성해서 github에 등록해야 합니다. 여기서는 ed25519 알고리즘을 사용해서 키를 생성하겠습니다. ed25519는 rsa보다 보안이 더 강화된 알고리즘으로, 최근에는 ed25519를 사용하는 것을 권장하고 있습니다. 다음 명령을 실행하여 키를 생성합니다.&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"># &amp;#34;your_email@example.com&amp;#34; 대신에 github 이메일 주소를 입력하세요.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; ssh-keygen -t ed25519 -C &lt;span class="s2">&amp;#34;your_email@example.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ssh-agent를 실행하여, 키를 등록한다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">eval&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>ssh-agent -s&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; ssh-add ~/.ssh/id_ed25519
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 생성된 키의 내용을 클립보드에 복사한 후, 그 내용을 github에 등록합니다. 키의 내용을 클립보드에 복사하려면 ubuntu에서는 xclip을 사용하고,&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; cat ~/.ssh/id_ed25519.pub &lt;span class="p">|&lt;/span> xclip -selection clipboard
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>mac에서는 pbcopy를 사용하면 됩니다.&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; cat ~/.ssh/id_ed25519.pub &lt;span class="p">|&lt;/span> pbcopy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 클립보드에 복사된 키를 github에 등록할 차례입니다. github에 접속하고 우측 상단의 아바타 버튼을 누르고 &lt;code>Settings&lt;/code> &amp;gt; &lt;code>SSH and GPG keys&lt;/code> &amp;gt; &lt;code>New SSH key&lt;/code>를 클릭합니다.&lt;/p>
&lt;p>&lt;code>Title&lt;/code>에는 키를 등록하는 컴퓨터의 이름을 적고, &lt;code>Key&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; &lt;span class="nb">cd&lt;/span> dev/ec2/service1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing modules...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloading git::https://github.com/netscout/terraform-modules.git?ref&lt;span class="o">=&lt;/span>v0.0.1 &lt;span class="k">for&lt;/span> ec2...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- ec2 in .terraform/modules/ec2/modules/ec2
&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">Terraform has been successfully initialized!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>정상적으로 잘 참조하고 있습니다! 이제 모듈을 업데이트 해볼까요?&lt;/p>
&lt;h3 id="모듈-업데이트하기">모듈 업데이트하기&lt;/h3>
&lt;p>모듈 리포지토리의 EC2 모듈을 업데이트 해보겠습니다. &lt;code>modules/ec2/main.tf&lt;/code> 파일을 열고, locals의 변수 정의를 수정하고 aws_security_group.sg 리소스에 ssh 접속을 위한 인바운트 규칙을 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&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">locals&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"> // EC2 인스턴스에 할당할 태그
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">security_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;ec2&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ami_id&lt;/span>&lt;span class="c1"> # 변수 ami_id 참조
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">instance_type&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">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p ${var.port} &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;sg&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">local&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">security_group_name&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">cidr_blocks&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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="c1"> // ssh 접속을 위한 인바운드 규칙(새로 추가)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">cidr_blocks&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>다음으로 variables.tf 파일을 열고, name 변수를 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// name 변수 추가
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;name&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&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">variable&lt;/span> &lt;span class="s2">&amp;#34;env&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> // 변수 값이 &amp;#34;dev&amp;#34;, &amp;#34;stg&amp;#34;, &amp;#34;prod&amp;#34; 중에 하나로 설정되야함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">validation&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">condition&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="nb"> contains&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;stg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;prod&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;env must be one of dev, stg, prod&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;ami_id&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;instance_type&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;port&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">number&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">variable&lt;/span> &lt;span class="s2">&amp;#34;cidr_blocks&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">type&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="nb"> list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; git tag -a v0.0.2 -m &lt;span class="s2">&amp;#34;EC2에 SSH 인바운드 규칙 추가&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; git push --follow-tags
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enumerating objects: 1, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Counting objects: 100% &lt;span class="o">(&lt;/span>1/1&lt;span class="o">)&lt;/span>, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Writing objects: 100% &lt;span class="o">(&lt;/span>1/1&lt;span class="o">)&lt;/span>, &lt;span class="m">207&lt;/span> bytes &lt;span class="p">|&lt;/span> 207.00 KiB/s, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>delta 0&lt;span class="o">)&lt;/span>, reused &lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>delta 0&lt;span class="o">)&lt;/span>, pack-reused &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">To https://github.com/netscout/terraform-modules.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * &lt;span class="o">[&lt;/span>new tag&lt;span class="o">]&lt;/span> v0.0.2 -&amp;gt; v0.0.2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="업데이트된-모듈-참조하기">업데이트된 모듈 참조하기&lt;/h3>
&lt;p>이제 업데이트된 모듈을 참조해보겠습니다. terraform-live/dev/ec2/service2 폴더를 추가하고, terraform-live/dev/ec2/service1 폴더에 작성된 main.tf, outputs.tf 파일을 복사해서 붙여넣습니다. 그리고 service2 폴더에 있는 main.tf 파일을 열고, 다음과 같이 모듈의 버전을 v0.0.2로 변경하고 name 변수에 인자를 할당합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="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">module&lt;/span> &lt;span class="s2">&amp;#34;ec2&amp;#34;&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"> //source = &amp;#34;../../modules/ec2&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;github.com/netscout/terraform-modules//modules/ec2?ref=v0.0.2&amp;#34;&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="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">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;service2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&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>init 명령을 실행해서 모듈 참조를 확인합니다.&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; &lt;span class="nb">cd&lt;/span> dev/ec2/service2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing modules...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Downloading git::https://github.com/netscout/terraform-modules.git?ref&lt;span class="o">=&lt;/span>v0.0.2 &lt;span class="k">for&lt;/span> ec2...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- ec2 in .terraform/modules/ec2/modules/ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>v0.0.2 버전의 모듈의 정상적으로 참조하고 있죠? v0.0.1을 참조하는 service1을 다시 확인해볼까요?&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"># 모듈 다시 업데이트 하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf get -update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>service1의 리소스 정의에는 모듈 v0.0.2에서 새로 추가된 name 변수가 지정되어 있지 않지만, name 변수가 아직 추가되지 않는 v0.0.1을 참조하고 있기 때문에 모듈의 버전 변경과 관계없이 잘 동작하는 걸 확인할 수 있습니다.&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>Git으로 모듈의 버전을 관리하면 모듈이 변경되더라도 이전에 생성된 리소스에는 영향을 주지 않고 안전하게 모듈을 업데이트할 수 있습니다. 점점 좋아지고 있죠? 그런데 여전히 중복은 남아있습니다. service1과 service2 에는 각각 provider에 대한 정의가 있습니다. 그리고 여기서는 backend를 사용하지 않았지만, S3같은 backend를 사용하면 그 설정 역시 각 리소스에 중복해서 들어가게 될 겁니다. 다음에는 이런 중복을 제거할 수 있는 방법을 알아보겠습니다.&lt;/p>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>How to Terraform(5) - 모듈</title><link>https://netscout.github.io/posts/how-to-terraform-5-%EB%AA%A8%EB%93%88/</link><pubDate>Sat, 02 Dec 2023 13:47:25 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-5-%EB%AA%A8%EB%93%88/</guid><description>Terraform을 사용하다보면 동일한 유형의 리소스를 여러번 생성해야 할 때가 있습니다. 예를 들면 개발, 스테이징, 운영 환경을 각각 분리된 EC2 인스턴스로 생성한다고 했을 때, 거의 동일한 EC2 인스턴스를 리소스로 정의하게 되겠죠. 지금까지는 동일한 코드를 …</description><content>&lt;h2 id="시리즈-목차">시리즈 목차&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5%2FHow%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5.md">How to Terraform(2) - 변수와 출력&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC%2FHow%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC.md">How to Terraform(3) - 상태 관리&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%284%29%20-%20%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%20%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%284%29%20-%20%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%20%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0.md">How to Terraform(4) - 상태관리 개선하기&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Terraform을 사용하다보면 동일한 유형의 리소스를 여러번 생성해야 할 때가 있습니다. 예를 들면 개발, 스테이징, 운영 환경을 각각 분리된 EC2 인스턴스로 생성한다고 했을 때, 거의 동일한 EC2 인스턴스를 리소스로 정의하게 되겠죠. 지금까지는 동일한 코드를 복사해서 붙여넣는 방식으로 각 리소스를 정의했습니다. 그런데 이렇게 하면 EC2 인스턴스의 설정이 복잡해지고, 개수가 많아질 수록 중복되는 코드의 양이 늘어나겠죠? 만약 기존에 정의된 EC2 인스턴스의 AMI를 변경해야 한다면, 모든 코드를 찾아서 일일이 변경해야만 할 겁니다. 그리고 이렇게 하다 보면 실수할 확률이 높아지겠죠?&lt;/p>
&lt;p>이렇게 동일한 리소스를 반복해서 사용해야 하는 경우, 리소스의 공통적인 내용을 재사용 가능한 형태로 정의해놓고 필요할 때마다 참조해서 사용하되, 바뀌는 부분만 따로 명시해줄 수 있으면 좋을 겁니다. 이런 재사용 가능한 형태로 정의된 리소스를 모듈이라고 합니다.&lt;/p>
&lt;h2 id="모듈이란">모듈이란?&lt;/h2>
&lt;p>Terraform 모듈은 동일한 유형의 리소스를 여러 번 생성할 때, 코드를 중복해서 작성하지 않도록 재사용 가능한 형태로 정의할 수 있습니다. 프로그래밍 언어에서는 함수나 클래스를 떠올려보시면 비슷합니다. 자 그럼 우선 모듈을 사용하기 전에 중복해서 리소스를 정의하는 코드를 먼저 확인해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1">// 개발 환경 EC2 인스턴스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example_dev&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&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">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example_dev&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p 8080 &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example_dev&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1">// 스테이징 환경 EC2 인스턴스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example2_stg&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&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">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example_stg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p 8080 &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example_stg&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example-stg&amp;#34;&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="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>개발 환경과 스테이징 환경에서 동작할 EC2 인스턴스를 2개 정의해놓았습니다. 대부분의 코드가 중복되어 있죠? 간단한 코드지만 계속해서 늘어난다고 생각하면 좀 별로이긴합니다. 그러면 일단 중복을 제거할 수 있도록 모듈을 작성해보겠습니다. &lt;code>modules/ec2&lt;/code> 폴더를 만들고, 그 안에 &lt;code>ec2&lt;/code> 폴더를 만들어서 &lt;code>main.tf&lt;/code> 파일을 생성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&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">locals&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"> // EC2 인스턴스에 할당할 태그
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">security_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;ec2&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ami_id&lt;/span>&lt;span class="c1"> # 변수 ami_id 참조
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">instance_type&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">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p ${var.port} &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;sg&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">local&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">security_group_name&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">cidr_blocks&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>variables.tf&lt;/code> 파일을 생성해서 변수를 정의합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;env&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> // 변수 값이 &amp;#34;dev&amp;#34;, &amp;#34;stg&amp;#34;, &amp;#34;prod&amp;#34; 중에 하나로 설정되야함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">validation&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">condition&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="nb"> contains&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;stg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;prod&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;env must be one of dev, stg, prod&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;ami_id&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;instance_type&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;port&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">number&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">variable&lt;/span> &lt;span class="s2">&amp;#34;cidr_blocks&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">type&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="nb"> list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>마지막으로 &lt;code>outputs.tf&lt;/code> 파일을 생성해서 EC2 인스턴스의 public IP 주소를 출력하도록 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">output&lt;/span> &lt;span class="s2">&amp;#34;public_ip&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">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_instance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ec2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">public_ip&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>dev/ec2&lt;/code> 폴더에 &lt;code>main.tf&lt;/code> 파일을 생성하고, 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="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">module&lt;/span> &lt;span class="s2">&amp;#34;ec2&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;../../modules/ec2&amp;#34;&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="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">env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&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>stg/ec2&lt;/code> 폴더에도 &lt;code>main.tf&lt;/code> 파일을 생성하고, 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="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">module&lt;/span> &lt;span class="s2">&amp;#34;ec2&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;../../modules/ec2&amp;#34;&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="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">env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&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>그리고 모듈의 output을 이용해서 EC2 인스턴스의 public IP 주소를 출력하도록 &lt;code>outputs.tf&lt;/code> 파일을 생성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">output&lt;/span> &lt;span class="s2">&amp;#34;public_ip&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">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ec2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">public_ip&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-gdscript3" data-lang="gdscript3">&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 class="err">├──&lt;/span> &lt;span class="n">modules&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">ec2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├──&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">variables&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">|&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">outputs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├──&lt;/span> &lt;span class="n">dev&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">ec2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└──&lt;/span> &lt;span class="n">outputs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└──&lt;/span> &lt;span class="n">stg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└──&lt;/span> &lt;span class="n">ec2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└──&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">└──&lt;/span> &lt;span class="n">outputs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>우선 &lt;code>dev/ec2&lt;/code> 폴더에서 &lt;code>tf init&lt;/code> 명령을 실행합니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>terraform&lt;/code>이라고 매번 입력하는게 어려우니 .zshrc나 .bashrc에 다음과 같이 alias 설정을 해서 &lt;code>tf&lt;/code>로 사용하는 것도 방법입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">alias tf=terraform
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&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; &lt;span class="nb">cd&lt;/span> dev/ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing modules...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- ec2 in ../../modules/ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing provider plugins...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>가장 먼저 지정된 모듈의 소스 경로에서 모듈을 가져와서 초기화 하는 걸 확인할 수 있죠? 그리고 &lt;code>tf plan&lt;/code> 명령을 실행해볼까요?&lt;/p>
&lt;blockquote>
&lt;p>참고로 모듈을 수정한 다음에 수정된 모듈을 반영하려면 &lt;code>tf get -update&lt;/code>명령을 실행해야 합니다.&lt;/p>&lt;/blockquote>
&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">2&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes to Outputs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&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">&amp;gt; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">2&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Outputs:
&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="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>모듈에 설정된 내용대로 인스턴스가 생성되었고, 모듈의 출력으로 설정된 public_ip도 출력을 확인할 수 있습니다. 이제 &lt;code>stg/ec2&lt;/code> 폴더에서도 동일한 명령을 실행해서 인스턴스를 추가로 생성할 수 있습니다.&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>모듈을 활용하면 코드의 중복을 줄일 수 있으며, 수작업으로 인해 실수를 방지할 수 있습니다. 하지만 모듈은 또 다른 단점을 가지고 있습니다. 모듈을 수정하게 되면 모듈을 참조하는 모든 리소스의 상태 관리에 영향을 미친다는 점입니다. 어떻게 하면 모듈을 업데이트하면서도 특정 리소스는 이전 모듈을 참조하고, 특정 리로스는 새로 업데이트된 모듈을 참조하도록 할 수 있을까요? 다음 시간에는 그 방법을 알아보겠습니다!&lt;/p>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>How to Terraform(4) - 상태관리 개선하기</title><link>https://netscout.github.io/posts/how-to-terraform-4-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0/</link><pubDate>Sat, 25 Nov 2023 14:14:54 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-4-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0/</guid><description>이전 글에서는 Terraform의 상태에 대해 알아보았고, 로컬 파일로 관리할 때의 단점을 해결하기 위해서 상태를 S3에 저장하는 방법을 알아보았습니다. 하지만 여전히 문제가 남아있었습니다. 그 문제가 뭐였는지 알아볼까요?</description><content>&lt;h2 id="시리즈-목차">시리즈 목차&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5%2FHow%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5.md">How to Terraform(2) - 변수와 출력&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC%2FHow%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC.md">How to Terraform(3) - 상태 관리&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="../How%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC%2FHow%20to%20Terraform%283%29%20-%20%EC%83%81%ED%83%9C%20%EA%B4%80%EB%A6%AC.md">이전 글&lt;/a>에서는 Terraform의 상태에 대해 알아보았고, 로컬 파일로 관리할 때의 단점을 해결하기 위해서 상태를 S3에 저장하는 방법을 알아보았습니다. 하지만 여전히 문제가 남아있었습니다. 그 문제가 뭐였는지 알아볼까요?&lt;/p>
&lt;h2 id="현재-상태관리의-문제점">현재 상태관리의 문제점&lt;/h2>
&lt;p>이전 글에서는 모든 테라폼 리소스가 한 개의 상태 파일로 관리되고 있었습니다. 예시를 위해서 새로 폴더를 만들고 다음 코드를 &lt;code>main.tf&lt;/code>에 작성합니다.&lt;/p>
&lt;blockquote>
&lt;p>일단 예시를 위해서 S3를 사용하지 않고 로컬 파일을 사용합니다.&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>그리고 init, plan, apply 명령을 실행합니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>terraform&lt;/code>이라고 매번 입력하는게 어려우니 .zshrc나 .bashrc에 다음과 같이 alias 설정을 해서 &lt;code>tf&lt;/code>로 사용하는 것도 방법입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">alias tf=terraform
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has been successfully initialized!
&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">&amp;gt; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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">&amp;gt; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">1&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>자 여기까지는 뭐 기존과 다를 게 하나도 없습니다. 여기에 두번째 EC2 인스턴스를 추가해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//main.tf 기존 코드...
&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">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example2&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>그리고 다시 plan을 실행해봅니다.&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Refreshing state... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00c12ab694ff80dfa&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">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>두번째 EC2 인스턴스를 추가할 때 우리의 관심사는 &lt;code>aws_instance.example2&lt;/code> 리소스 입니다. 하지만 plan 명령을 실행하자 이미 추가된 리소스인 &lt;code>aws_instance.example&lt;/code>에 대한 검사가 먼저 진행되었죠? &lt;code>aws_instance.example&lt;/code> 리소스가 &lt;code>aws_instance.example2&lt;/code> 리소스와 하나의 상태 파일로 관리되고 있기 때문에 Terraform으로서는 어쩔 수 없이 상태 파일의 내용과 테라폼 코드의 리소스를 모두 비교할 수 밖에 없는 거죠. EC2 인스턴스 몇 개는 금방 끝나니까 별 문제가 아닐 수 있지만, 이런 식으로 리소스가 계속 늘어나다 보면 어느새 plan 한번 실행하는데 몇 분이 걸리는 일도 발생할 수 있습니다.&lt;/p>
&lt;p>그리고 좀 더 복잡한 문제가 생기는 경우도 있습니다. 만약 반드시 변수로 입력받아야 하는 값이 있다고 생각해볼까요? 테라폼 코드에 작성된 상당수의 리소스가 환경변수 등을 통해서 변수를 입력받아야 한다면 어떨까요? 내가 관심 있는 리소스 하나 작업하기 위해서 모든 변수 값을 제대로 설정해야만 하는 문제가 발생합니다. 정리해보자면,&lt;/p>
&lt;ul>
&lt;li>여러 리소스가 하나의 상태 파일로 관리되기 때문에, 리소스가 추가됨에 따라 상태 파일이 점점 거대해진다.&lt;/li>
&lt;li>따라서 새로 추가하는 리소스에 대한 plan, apply 명령에도 기존에 관리되고 있는 모든 리소스에 대한 검사가 진행되므로 시간이 점점 더 오래걸린다.&lt;/li>
&lt;li>모든 리소스가 하나의 상태 파일로 관리되면 다른 사람이 일부 리소스를 변경했을 때 변경된 상태 값 역시 공유된다. 만약 다른 사람이 변경한 테라폼 코드를 업데이트 받지 않은 상태에서 내 작업을 진행하면 리소스의 상태를 다시 변경 이전 상태로 되돌리는 등의 예상치 못한 오류가 발생할 수 있다.&lt;/li>
&lt;/ul>
&lt;p>이러한 문제를 해결하기 위해서는 각 리소스에 대한 상태를 최대한 작게 나눠서 관리하는 게 좋을 것 같습니다. 나눠서 관리한다는 게 생각보다 별 거 없습니다. 그냥 폴더를 쪼개면 됩니다.&lt;/p>
&lt;h2 id="폴더로-나눠서-관리하기">폴더로 나눠서 관리하기&lt;/h2>
&lt;p>S3 백엔드와 EC2 인스턴스 2개를 만드는 상황을 가정하면 폴더는 대강 이렇게 나눌 수 있습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── global
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── s3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── main.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── ec2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── example
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── main.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── example2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── main.tf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>우선 &lt;code>global&lt;/code> 폴더에는 S3 백엔드를 생성하는 코드를 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1"># S3 버킷 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;terraform_state&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-state-example-20231125&amp;#34;&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="c1"> # 실수로 버킷이 삭제되지 않도록 하려면 아래 주석을 해제하세요.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # lifecycle {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # prevent_destroy = true
&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 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="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">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket_versioning&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;enabled&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">terraform_state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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">versioning_configuration&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Enabled&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 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="c1"># S3에 저장될 때 암호화 하도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket_server_side_encryption_configuration&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;default&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">terraform_state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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">rule&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">apply_server_side_encryption_by_default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sse_algorithm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;AES256&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;span class="line">&lt;span class="cl">&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="c1"># 모든 public access를 차단(실수로 버킷이 공개되지 않도록 하기 위함)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket_public_access_block&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;public_access&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">terraform_state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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">block_public_acls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">block_public_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ignore_public_acls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">restrict_public_buckets&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1"># 여러 사람이 동시에 상태 파일을 수정하지 못하도록 DynamoDB를 사용한 락 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_dynamodb_table&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">billing_mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;PAY_PER_REQUEST&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">hash_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;LockID&amp;#34;&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">attribute&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;LockID&amp;#34;&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;S&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>그리고 우선 init, plan, apply 를 실행해서 백엔드 설정을 위한 S3 버킷을 생성합니다.&lt;/p>
&lt;blockquote>
&lt;p>참고로, VS Code에서는 cd 명령 대신에 폴더에 우클릭하고 &lt;code>통합 터미널에서 열기&lt;/code>를 선택하면 해당 폴더로 터미널이 이동합니다.&lt;/p>
&lt;p align="center">&lt;img src="./01.png">&lt;/p>&lt;/blockquote>
&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; &lt;span class="nb">cd&lt;/span> global/s3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has been successfully initialized!
&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">&amp;gt; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">5&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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">&amp;gt; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">5&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 생성된 S3에 현재 생성된 리소스의 상태를 저장하기 위해서 &lt;code>main.tf&lt;/code>상단에 다음 코드를 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">terraform&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="c1">&lt;/span> &lt;span class="nx">required_providers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">aws&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hashicorp/aws&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt; 5.0&amp;#34;&lt;/span>&lt;span class="c1"> # 5.0 이상 버전 사용
&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 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="c1"> # 상태 파일을 S3에 저장하도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;s3&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-state-example-20231125&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;global/s3/terraform.tfstate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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="c1"> # 상태 파일을 락으로 사용할 DynamoDB 테이블
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">dynamodb_table&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">encrypt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&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="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="c1">// main.tf 기존 코드...
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>여기서 주목해야 할 점은 상태 파일이 저장될 경로를 리소스 파일이 정의된 폴더 경로와 일치하도록 설정했다는 점입니다. 이렇게 하면 리로스와 상태 파일의 경로가 일치하므로 상태 파일을 덮어쓴다던지 하는 오류를 피할 수 있습니다. 이제 init 명령을 통해 벡엔드를 설정하겠습니다.&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you want to copy existing state to the new backend?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pre-existing state was found &lt;span class="k">while&lt;/span> migrating the previous &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span> backend to the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> newly configured &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span> backend. No existing state was found in the newly
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> configured &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span> backend. Do you want to copy this state to the new &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> backend? Enter &lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span> to copy and &lt;span class="s2">&amp;#34;no&amp;#34;&lt;/span> to start with an empty state.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enter a value: yes
&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">Successfully configured the backend &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span>! Terraform will automatically
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use this backend unless the backend configuration changes.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>콘솔에서 S3를 확인해보면,&lt;/p>
&lt;p align="center">&lt;img src="./02.png">&lt;/p>
&lt;p>지정된 경로에 상태 파일이 생성된 것을 확인할 수 있습니다.&lt;/p>
&lt;blockquote>
&lt;p>이렇게 리소스 별로 상태를 관리할 때 아무래도 자주 관리하지 않는 리소스가 생기기 마련입니다. 시간이 한참 지나서 다시 관리를 해야 할 때, 이전에 사용하던 프로바이더의 버전과 현재 버전이 차이가 많이 날 수도 있겠죠. 그럴 때 기존에 작성한 코드가 제대로 동작하지 않는 문제가 생길 수도 있으므로 프로바이더의 버전을 명시하는 것도 좋은 방법입니다!&lt;/p>&lt;/blockquote>
&lt;p>이제 &lt;code>dev/ec2/example/main.tf&lt;/code> 파일을 생성하고 다음 코드를 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">terraform&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="c1">&lt;/span> &lt;span class="nx">required_providers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">aws&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hashicorp/aws&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt; 5.0&amp;#34;&lt;/span>&lt;span class="c1"> # 5.0 이상 버전 사용
&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 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="c1"> # 상태 파일을 S3에 저장하도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;s3&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-state-example-20231125&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;dev/ec2/example/terraform.tfstate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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="c1"> # 상태 파일을 락으로 사용할 DynamoDB 테이블
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">dynamodb_table&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">encrypt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&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="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="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>그리고 역시 &lt;code>dev/ec2/example&lt;/code> 폴더에서 init, plan, apply 명령을 실행합니다.&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; &lt;span class="nb">cd&lt;/span> dev/ec2/example
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has been successfully initialized!
&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">&amp;gt; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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">&amp;gt; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">1&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>dev/ec2/example2/main.tf&lt;/code> 파일을 생성하고 동일한 과정으로 EC2 인스턴스를 추가할 수 있습니다.&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>이제 우리는 각 리소스에 대해 개별적으로 상태 관리를 진행할 수 있습니다. 리소스는 각각의 상태를 가지므로 다른 리소스에 대해 신경쓰지 않고 관리할 수 있고, 특히 새로운 리소스를 추가할 때 역시 기존 리소스의 상태에 대해서 전혀 신경쓰지 않아도 됩니다. 다만 역시나 한 가지 마음에 걸리는 게 있습니다. EC2 인스턴스를 작성 할 때 저희는 동일한 코드를 복붙해서 새로운 인스턴스를 생성했었죠. 사실 지금은 EC2에 별다른 설정을 하지 않아서 뭐 복잡할 건 없습니다만, VPC등의 설정에 따라서는 EC2도 매우 복잡해질 수 있고 저희가 Terraform으로 EC2만 관리할 것도 아니기 때문에 이 부분은 더 나은 방법이 필요해보입니다. 다음 글에서는 모듈을 통해 중복을 줄이는 방법을 알아보겠습니다.&lt;/p>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>How to Terraform(3) - 상태 관리</title><link>https://netscout.github.io/posts/how-to-terraform-3-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC/</link><pubDate>Sat, 18 Nov 2023 15:28:03 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-3-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC/</guid><description>상태가 뭔지 설명하기 전에 간단한 실험을 해보죠! 새로운 폴더를 생성하고 아래 코드를 main.tf에 추가하겠습니다.</description><content>&lt;h2 id="시리즈-목차">시리즈 목차&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남&lt;/a>&lt;/li>
&lt;li>&lt;a href="../How%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5%2FHow%20to%20Terraform%282%29%20-%20%EB%B3%80%EC%88%98%EC%99%80%20%EC%B6%9C%EB%A0%A5.md">How to Terraform(2) - 변수와 출력&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="상태라니-무슨-상태">상태라니 무슨 상태?&lt;/h2>
&lt;p>상태가 뭔지 설명하기 전에 간단한 실험을 해보죠! 새로운 폴더를 생성하고 아래 코드를 &lt;code>main.tf&lt;/code>에 추가하겠습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>그리고 &lt;code>tf init&lt;/code>을 실행해봅시다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>terraform&lt;/code>이라고 매번 입력하는게 어려우니 .zshrc나 .bashrc에 다음과 같이 alias 설정을 해서 &lt;code>tf&lt;/code>로 사용하는 것도 방법입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">alias tf=terraform
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing provider plugins...
&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">Terraform has been successfully initialized!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>맨 처음에 &lt;code>backend&lt;/code>라는 표현이 등장합니다. 이게 웹 어플리케이션도 아닌데 backend는 뭘 말하는 걸까요~? 일단은 궁금증을 뒤로 하고 &lt;code>tf plan&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No changes. Your infrastructure matches the configuration.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has compared your real infrastructure against your configuration and found no differences, so no changes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">are needed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>아무 것도 바꿀 게 없다고 합니다. 리소스를 하나도 선언하지 않았으니 당연하겠죠. 그런데 여기서 &lt;code>tf apply&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">No changes. Your infrastructure matches the configuration.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has compared your real infrastructure against your configuration and found no differences, so no changes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">are needed.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">0&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>바꿀건 하나도 없지만 apply 명령이 완료되었습니다. 그런데 apply 명령을 실행한 폴더를 확인해보시면, &lt;code>terraform.tfstate&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="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="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="nt">&amp;#34;terraform_version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1.6.2&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;serial&amp;#34;&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="nt">&amp;#34;lineage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2e6b0c74-0e92-bfe8-1197-36e5547d9375&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;outputs&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;resources&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;check_results&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&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>이 파일에는 현재 Terraform 코드를 apply해서 구성된 리소스의 상태가 기록되고 있습니다. 즉, 가장 최근에 실행한 apply 명령을 통해 구성된 리소스의 상태(여기서는 aws 클라우드에 생성된 리소스의 상태)가 기록됩니다. 여러분이 plan 명령을 실행 할 때 뭐가 추가되고, 뭐가 수정되며 뭐가 삭제되는지를 이 파일에 기록된 현재 상태를 기준으로 판단하는 거죠. 이 파일에 포함된 속성은 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;code>version&lt;/code>: Terraform의 상태 파일 형식 버전입니다. 현재는 4입니다.&lt;/li>
&lt;li>&lt;code>terraform_version&lt;/code>: Terraform의 버전입니다.&lt;/li>
&lt;li>&lt;code>serial&lt;/code>: 상태 파일이 변경될 때마다 증가하는 숫자입니다. 이 숫자는 상태 파일이 변경될 때마다 증가하며, 이 숫자를 통해 상태 파일이 변경되었는지를 판단합니다.&lt;/li>
&lt;li>&lt;code>lineage&lt;/code>: 상태 파일의 고유 식별자입니다. 처음 생성될 때 부여되고, 그 이후로는 변경되지 않습니다.&lt;/li>
&lt;li>&lt;code>outputs&lt;/code>: Terraform 코드에서 &lt;code>output&lt;/code>으로 정의한 출력 값들이 기록됩니다.&lt;/li>
&lt;li>&lt;code>resources&lt;/code>: Terraform 코드에서 &lt;code>resource&lt;/code>로 정의한 리소스들의 상태가 기록됩니다.&lt;/li>
&lt;li>&lt;code>check_results&lt;/code>: Terraform 코드에서 &lt;code>data&lt;/code>로 정의한 데이터 소스들의 상태가 기록됩니다.&lt;/li>
&lt;/ul>
&lt;p>그렇다면 EC2 인스턴스를 하나 추가해서 상태 파일이 어떻게 바뀌는지 확인해봅시다! &lt;code>main.tf&lt;/code>에 아래 코드를 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>그리고 &lt;code>tf plan&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">with the following symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + create
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">associate_public_ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">availability_zone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cpu_core_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cpu_threads_per_core&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">disable_api_stop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">disable_api_termination&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ebs_optimized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">get_password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">host_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">host_resource_group_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">iam_instance_profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_initiated_shutdown_behavior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_lifecycle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_address_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_addresses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">key_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">monitoring&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">outpost_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">placement_group&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">placement_partition_number&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">primary_network_interface_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">private_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">private_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">secondary_private_ips&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">source_dest_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">spot_instance_request_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">subnet_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">tags_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">tenancy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data_base64&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data_replace_on_change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&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">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">Note: You didn&lt;span class="s1">&amp;#39;t use the -out option to save this plan, so Terraform can&amp;#39;&lt;/span>t guarantee to take exactly these actions
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> you run &lt;span class="s2">&amp;#34;terraform apply&amp;#34;&lt;/span> now.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>현재는 생성된 리소스가 하나도 없기 때문에, 상태 값은 비어있는 상태죠. 그래서 Terraform 코드에 정의된 리소스는 모두 새로 추가됩니다. 이제 &lt;code>tf apply&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">1&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 다시 &lt;code>terraform.tfstate&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="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="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="nt">&amp;#34;terraform_version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1.6.2&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;serial&amp;#34;&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="nt">&amp;#34;lineage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2e6b0c74-0e92-bfe8-1197-36e5547d9375&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;outputs&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;resources&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;managed&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;aws_instance&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;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;example&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;provider&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;provider[\&amp;#34;registry.terraform.io/hashicorp/aws\&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="nt">&amp;#34;instances&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;schema_version&amp;#34;&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="nt">&amp;#34;attributes&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;ami&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&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="err">...&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;sensitive_attributes&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;private&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;...&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;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="nt">&amp;#34;check_results&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&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>방금 전 apply 명령으로 추가된 리소스를 확인할 수 있습니다.&lt;/p>
&lt;p>상태는 Terraform이 리소스를 관리할 때 어떻게 변경해야 할지 판단하는 기준이 됩니다. 그렇기 때문에 상태는 Terraform에서 가장 중요합니다. 상태가 제대로 관리되지 않으면 어떤 리소스를 어떻게 변경해야 할지 Terraform이 제대로 판단할 수 없겠죠.&lt;/p>
&lt;blockquote>
&lt;p>그렇기 때문에 Terraform으로 생성된 리소스를 관리 콘솔에서 직접 변경하면 안됩니다! 상태 파일에 기록된 내용과 실제 리소스의 상태가 달라지기 때문에 Terraform이 제대로 동작하지 않을 수 있습니다.&lt;/p>&lt;/blockquote>
&lt;p>확인이 끝났으므로 destroy 명령으로 생성된 리소스를 모두 제거합니다. 참고로 제거 작업이 끝난 후 상태 파일은 이렇습니다.&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="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="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="nt">&amp;#34;terraform_version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1.6.2&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;serial&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&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;lineage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2e6b0c74-0e92-bfe8-1197-36e5547d9375&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;outputs&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;resources&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;check_results&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&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>serial&lt;/code>을 제외하고는 모두 원래대로 돌아왔죠?&lt;/p>
&lt;h2 id="상태-파일-관리">상태 파일 관리&lt;/h2>
&lt;p>보시다시피 별다른 설정이 없다면, 상태 파일은 현재 Terraform 명령을 실행하는 폴더에 생성됩니다. 실습을 위해 간단하게 테스트를 하는 경우라면 문제가 없겠지만, 계속 상태 파일을 이렇게 사용하는 건 매우 위험합니다. 그 이유는!&lt;/p>
&lt;ul>
&lt;li>output등의 민감한 정보가 상태 파일에 기록되기 때문에 파일에 접근할 수 있다면, 민감한 정보를 보호할 수 없습니다.&lt;/li>
&lt;li>동일한 리소스에 대해, 여러 장소(컴퓨터)에서 Terraform을 실행해야 하는 경우 각 장소마다 동일한 상태 파일을 가지고 있어야 합니다. 그래서 git등의 버전 관리 시스템으로 상태 파일을 관리할 수도 있지만, &lt;code>commit이나 pull을 까먹는다면 무슨 일이 벌어질까요?&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>그래서 실제 Terraform을 사용하려면 상태 파일은 로컬 컴퓨터가 아닌, git같은 버전 관리 시스템도 아닌 어딘가 안전하고 항상 접근 가능한 곳에 저장되어야 합니다. 그리고 가능하면 암호화되어야 겠죠! 이 글 서두에 보셨던 &lt;code>backend&lt;/code>가 바로 이런 상태 파일을 어딘가에서 안전하게 잘 관리해주는 역할을 합니다. 우리는 지금 &lt;code>AWS&lt;/code>를 사용하고 있죠? 그렇다면 이제 &lt;code>S3&lt;/code>를 통해 &lt;code>backend&lt;/code>를 구성해보겠습니다.&lt;/p>
&lt;h2 id="s3-backend-구성">S3 backend 구성&lt;/h2>
&lt;p>&lt;code>S3&lt;/code>로 &lt;code>backend&lt;/code>를 구성하려면 우선 &lt;code>S3&lt;/code>를 생성해야겠죠? 일단 Terraform으로 &lt;code>S3&lt;/code>를 생성하고 그 이후에 &lt;code>backend&lt;/code>를 구성해서 상태를 &lt;code>backend&lt;/code>로 옮기겠습니다. 새로 폴더를 만들고 &lt;code>main.tf&lt;/code>를 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1"># S3 버킷 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;terraform_state&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-state-example-20231118&amp;#34;&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="c1"> # 실수로 버킷이 삭제되지 않도록 하려면 아래 주석을 해제하세요.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # lifecycle {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # prevent_destroy = true
&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 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="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">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket_versioning&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;enabled&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">terraform_state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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">versioning_configuration&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Enabled&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 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="c1"># S3에 저장될 때 암호화 하도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket_server_side_encryption_configuration&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;default&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">terraform_state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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">rule&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">apply_server_side_encryption_by_default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sse_algorithm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;AES256&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;span class="line">&lt;span class="cl">&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="c1"># 모든 public access를 차단(실수로 버킷이 공개되지 않도록 하기 위함)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_s3_bucket_public_access_block&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;public_access&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_s3_bucket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">terraform_state&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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">block_public_acls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">block_public_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ignore_public_acls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">restrict_public_buckets&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1"># 여러 사람이 동시에 상태 파일을 수정하지 못하도록 DynamoDB를 사용한 락 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_dynamodb_table&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">billing_mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;PAY_PER_REQUEST&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">hash_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;LockID&amp;#34;&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">attribute&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;LockID&amp;#34;&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;S&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>aws_s3_bucket&lt;/code> 리소스를 통해 &lt;code>S3&lt;/code> 버킷을 생성하는데요, 이 버킷에 상태 파일을 저장하게 됩니다. 그리고 필요한 경우 상태 파일의 히스토리를 버전 별로 관리할 수 있도록 버전 관리를 활성화 합니다. 버전 관리를 사용하도록 설정한다면 뭔가 상태 파일에 문제가 있을 경우 이전 버전으로 복구할 수 있겠죠?&lt;/p>
&lt;p>그리고 상태 파일은 암호화되어 저장되도록 설정했고, 실수로 버킷이 공개되지 않도록 모든 public access를 차단했습니다. 마지막으로 여러 사람이 동시에 상태 파일을 수정하지 못하도록 DynamoDB를 사용한 락을 설정했습니다. 이제 &lt;code>tf init&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has been successfully initialized!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>tf plan&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">5&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이어서 &lt;code>tf apply&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">5&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>만약 이전글에서 사용했던 것처럼 제한된 IAM 사용자 권한을 사용하는 경우 &amp;ldquo;sts:GetCallerIdentity&amp;rdquo;,&amp;ldquo;s3:CreateBucket&amp;rdquo;,&amp;ldquo;dynamodb:CreateTable&amp;rdquo;,&amp;ldquo;dynamodb:DescribeTable&amp;rdquo;,&amp;ldquo;s3:ListBucket&amp;rdquo;,&amp;ldquo;s3:GetBucketPolicy&amp;rdquo;,&amp;ldquo;s3:GetBucketAcl&amp;rdquo;,&amp;ldquo;s3:GetBucketCORS&amp;rdquo;,&amp;ldquo;s3:GetBucketWebsite&amp;rdquo;,&amp;ldquo;s3:GetBucketVersioning&amp;rdquo;,&amp;ldquo;s3:GetAccelerateConfiguration&amp;rdquo;,&amp;ldquo;s3:GetBucketRequestPayment&amp;rdquo;,&amp;ldquo;s3:GetBucketLogging&amp;rdquo;,&amp;ldquo;s3:GetLifecycleConfiguration&amp;rdquo;,&amp;ldquo;s3:GetReplicationConfiguration&amp;rdquo;,&amp;ldquo;s3:GetEncryptionConfiguration&amp;rdquo;,&amp;ldquo;s3:GetBucketObjectLockConfiguration&amp;rdquo;,&amp;ldquo;s3:GetBucketTagging&amp;rdquo;,&amp;ldquo;s3:PutBucketVersioning&amp;rdquo;,&amp;ldquo;dynamodb:DescribeContinuousBackups&amp;rdquo;,&amp;ldquo;s3:PutBucketPublicAccessBlock&amp;rdquo;,&amp;ldquo;s3:PutEncryptionConfiguration&amp;rdquo;,&amp;ldquo;dynamodb:DescribeTimeToLive&amp;rdquo;,&amp;ldquo;dynamodb:ListTagsOfResource&amp;rdquo;,&amp;ldquo;s3:GetBucketPublicAccessBlock&amp;rdquo;,&amp;ldquo;s3:GetObject&amp;rdquo;,&amp;ldquo;s3:GetObjectVersion&amp;rdquo;,&amp;ldquo;dynamodb:GetItem&amp;rdquo;,&amp;ldquo;dynamodb:PutItem&amp;rdquo;,&amp;ldquo;dynamodb:DeleteItem&amp;rdquo; 등의 권한이 추가로 필요합니다.&lt;/p>&lt;/blockquote>
&lt;p>&lt;code>S3&lt;/code>는 잘 생성되었지만, 여전히 &lt;code>terraform.tfstate&lt;/code>파일은 현재 폴더에 그대로 남아있습니다. 파일 내용을 확인해보면, 방금 생성한 &lt;code>S3&lt;/code>의 정보가 담겨있습니다. 아직 &lt;code>backend&lt;/code>설정을 하지 않았기 때문에 여전히 상태 파일은 로컬 컴퓨터에서 관리하고 있는 거죠. 그렇다면 &lt;code>backend&lt;/code>를 &lt;code>S3&lt;/code>로 설정하겠습니다. &lt;code>main.tf&lt;/code>에서 기존에 작성된 코드 위에 다음 코드를 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Terraform 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">terraform&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="c1">&lt;/span> &lt;span class="nx">required_providers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">aws&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hashicorp/aws&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt; 5.0&amp;#34;&lt;/span>&lt;span class="c1"> # 5.x 버전 사용
&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 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="c1"> # 상태 파일을 S3에 저장하도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;s3&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-state-example-20231118&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;example/terraform.tfstate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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="c1"> # 상태 파일을 락으로 사용할 DynamoDB 테이블
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">dynamodb_table&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform_locks&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">encrypt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&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="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Terraform 블록을 선언하고 사용할 프로바이더와 버전 정보, 그리고 &lt;code>S3&lt;/code>를 &lt;code>backend&lt;/code>로 사용하도록 설정했습니다. 이제 &lt;code>tf init&lt;/code>을 실행하면 로컬에 저장된 상태 파일을 새로 지정된 &lt;code>S3&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Acquiring state lock. This may take a few moments...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you want to copy existing state to the new backend?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pre-existing state was found &lt;span class="k">while&lt;/span> migrating the previous &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span> backend to the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> newly configured &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span> backend. No existing state was found in the newly
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> configured &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span> backend. Do you want to copy this state to the new &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> backend? Enter &lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span> to copy and &lt;span class="s2">&amp;#34;no&amp;#34;&lt;/span> to start with an empty state.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enter a value: yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Releasing state lock. This may take a few moments...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Successfully configured the backend &lt;span class="s2">&amp;#34;s3&amp;#34;&lt;/span>! Terraform will automatically
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use this backend unless the backend configuration changes.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>로컬에 저장된 상태를 &lt;code>S3&lt;/code>로 복사하겠냐는 질문이 나오는데, &lt;code>yes&lt;/code>를 입력하면 로컬에 저장된 상태 파일이 &lt;code>S3&lt;/code>로 복사됩니다. 그리고 &lt;code>terraform.tfstate&lt;/code> 파일이 남아있긴 하지만, 내용을 확인 해보면 텅텅 비어 있습니다. 이제 삭제해도 문제가 없겠죠! 참고로 &lt;code>backend&lt;/code>에서 상태 파일을 로컬로 다운로드 하고 싶다면 &lt;code>terraform state pull&lt;/code> 명령을 사용하면 됩니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>iamlive&lt;/code>를 실행 중이라면 &lt;code>tls: failed to verify certificate: x509&lt;/code> 오류나 &lt;code>request send failed, Post &amp;quot;https://sts.ap-northeast-2.amazonaws.com/&amp;quot;: proxyconnect tcp: dial tcp 127.0.0.1:80&lt;/code> 등의 오류가 발생할 수 있습니다. &lt;code>iamlive&lt;/code>의 실행을 위해 선언해둔 환경 변수 때문인데요, 해당 환경 변수를 주석처리하고 터미널 세션을 다시 시작하거나, &lt;code>unset&lt;/code> 등의 명령어로 현재 터미널 세션에서 환경 변수를 제거할 수 있습니다.&lt;/p>&lt;/blockquote>
&lt;p>그렇다면 이제 간단한 EC2 인스턴스를 추가해서 상태가 잘 관리되는지 확인하겠습니다. &lt;code>main.tf&lt;/code>에 다음 코드를 추가합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&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">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>&lt;code>tf plan&lt;/code>과 &lt;code>tf apply&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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">&amp;gt; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">1&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>더 이상 로컬에 상태 파일이 생성되지 않지만, 원격의 &lt;code>S3&lt;/code>에서 상태를 읽어와서 잘 관리하고 있네요!&lt;/p>
&lt;h1 id="마치며">마치며&lt;/h1>
&lt;p>이제 우리는 상태를 로컬 파일이 아니라 &lt;code>S3&lt;/code>를 통해 안전하고 안정적으로 그리고 어디서나 활용할 수 있도록 변경할 수 있습니다. 하지만 여전히 불안한 점은 남아있습니다. EC2를 추가하면서 plan, apply를 실행해보신 분들은 아시겠지만, 여전히 이미 생성된 &lt;code>S3&lt;/code>에 대한 상태를 매번 비교하고 있습니다. 하나의 상태에 많은 리소스가 연결되면 이렇게 아주 작은 부분만을 바꿀 때도 전체 상태와 비교를 하게 됩니다. 그리고 아무리 리소스 파일이 공유된 상태라고는 하지만, 여러 사람이 동시에 서로 다른 부분에서 코드를 변경하면 예상하지 어려운 상황이 발생하겠죠. 그래서 상태 파일은 가능하면 작게 유지하는 게 좋습니다. 다음 시간에는 상태 파일을 작게 유지하는 방법에 대해 알아보겠습니다.&lt;/p>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>How to Terraform(2) - 변수와 출력</title><link>https://netscout.github.io/posts/how-to-terraform-2-%EB%B3%80%EC%88%98%EC%99%80-%EC%B6%9C%EB%A0%A5/</link><pubDate>Sat, 11 Nov 2023 15:30:01 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-2-%EB%B3%80%EC%88%98%EC%99%80-%EC%B6%9C%EB%A0%A5/</guid><description>이전 글에서 작성한 코드를 다시 한 번 볼까요?</description><content>&lt;h2 id="시리즈-목차">시리즈 목차&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="이전-글-복습">이전 글 복습&lt;/h2>
&lt;p>&lt;a href="../How%20to%20Terraform%281%29%20-%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0%2FHow%20to%20Terraform%281%29%20-%20%ED%85%8C%EB%9D%BC%ED%8F%BC%EA%B3%BC%EC%9D%98%20%EA%B0%80%EB%B2%BC%EC%9A%B4%28%3F%29%20%EC%B2%AB%20%EB%A7%8C%EB%82%A8.md">이전 글&lt;/a>에서 작성한 코드를 다시 한 번 볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p 8080 &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="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>위 코드에는 몇 가지 개선할 점이 있습니다. 만약 EC2 인스턴스를 몇 개 더 만든다고 했을 때 중복될 만한 부분이 있습니다. AMI ID나 인스턴스 타입 등이 그렇죠. 이렇게 중복될 만한 내용은 변수로 선언하여 중복을 줄일 수 있고, 중복을 줄이면 실수를 줄일 수 있습니다. 또한 EC2 인스턴스에 접근할 수 있는 IP를 AWS 콘솔에서 확인하는 번거로움이 있었습니다. 이 또한 출력을 통해 확인할 수 있습니다. 자 그럼 하나씩 해봅시다!&lt;/p>
&lt;h2 id="변수">변수&lt;/h2>
&lt;p>가장 먼저 AMI ID 부터 변수를 선언해볼까요? &lt;code>main.tf&lt;/code> 파일이 있는 위치에 &lt;code>variables.tf&lt;/code> 파일을 만들고 아래와 같이 작성해봅시다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;ami_id&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&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>변수의 이름은 &lt;code>ami_id&lt;/code>이고, 타입은 &lt;code>string&lt;/code>입니다. 그리고 &lt;code>default&lt;/code>에는 별도로 값이 주어지지 않는 경우 사용할 기본값을 설정합니다. 이제 &lt;code>main.tf&lt;/code> 파일에서 &lt;code>ami&lt;/code> 속성을 변수를 통해 입력받도록 변경합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ami_id&lt;/span>&lt;span class="c1"> # 변수 ami_id 참조
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&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="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;blockquote>
&lt;p>VS Code를 사용하신다면 Terraform Extension for Visual Studio Code를 꼭 설치하세요! 선언된 변수나 리소스에 대해서 자동완성 기능과 오류 등을 미리 확인할 수 있게 도와줍니다!&lt;/p>&lt;/blockquote>
&lt;p>기존에 직접 값을 지정했던 ami 속성에 변수 ami_id를 참조하도록 변경했습니다. &lt;code>tf plan&lt;/code>을 통해 스크립트에 이상이 없는지 확인합니다. 참고로 지난 시간에 만든 EC2 인스턴스는 &lt;code>tf destroy&lt;/code>를 통해 삭제되었다고 가정하겠습니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>terraform&lt;/code>이라고 매번 입력하는게 어려우니 .zshrc나 .bashrc에 다음과 같이 alias 설정을 해서 &lt;code>tf&lt;/code>로 사용하는 것도 방법입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">alias tf=terraform
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + create
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&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"> &lt;span class="c1"># aws_security_group.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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">Plan: &lt;span class="m">2&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">Note: You didn&lt;span class="s1">&amp;#39;t use the -out option to save this plan, so Terraform can&amp;#39;&lt;/span>t guarantee to take exactly these actions &lt;span class="k">if&lt;/span> you run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;terraform apply&amp;#34;&lt;/span> now.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ami 속성이 변수에 설정된 기본값으로 설정된 걸 확인할 수 있습니다. 그렇다면 기본값 대신에 변수에 값을 넘겨볼까요?&lt;/p>
&lt;h3 id="커맨드로-변수-지정하기">커맨드로 변수 지정하기&lt;/h3>
&lt;p>&lt;code>tf plan&lt;/code> 또는 &lt;code>tf apply&lt;/code>를 실행할 때 &lt;code>-var&lt;/code> 옵션을 사용하면 변수에 값을 넘길 수 있습니다. &lt;code>-var&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; tf plan -var &lt;span class="s2">&amp;#34;ami_id=ami-09eb4311cbaecf89d&amp;#34;&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-09eb4311cbaecf89d&amp;#34;&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="파일로-변수-지정하기">파일로 변수 지정하기&lt;/h3>
&lt;p>이번에는 &lt;code>terraform.tfvars&lt;/code> 파일을 통해 변수에 값을 지정해보겠습니다. &lt;code>terraform.tfvars&lt;/code> 파일을 만들고 아래와 같이 작성해봅시다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ami_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-09eb4311cbaecf89d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>tf plan&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; tf plan
&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-09eb4311cbaecf89d&amp;#34;&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;/code>&lt;/pre>&lt;/div>&lt;p>파일 이름이 &lt;code>terraform.tfvars&lt;/code> 또는 &lt;code>terraform.tfvars.json&lt;/code>(json 형태로 작성가능)이거나, 파일 이름이 &lt;code>.auto.tfvars&lt;/code> 또는 &lt;code>.auto.tfvars.json&lt;/code>으로 끝나는 경우, Terraform 코드가 실행 될 때 자동으로 파일을 읽어서 변수에 값을 지정합니다. 만약 파일 이름 앞서 설명드린 규칙과 다르다면, 파일 이름을 &lt;code>-var-file&lt;/code> 옵션을 사용하여 파일을 지정할 수 있습니다.&lt;/p>
&lt;p>한 가지 주의 할 점은 &lt;code>terraform.tfvars&lt;/code> 파일을 사용하는 경우, 반드시 .gitignore에 terraform.tfvars를 추가해야 합니다! 참고로 &lt;a href="https://www.toptal.com/developers/gitignore/">gitignore.io&lt;/a>를 통해 Terraform의 .gitignore 파일을 생성해보면 다음과 같습니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Created by https://www.toptal.com/developers/gitignore/api/terraform&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Edit at https://www.toptal.com/developers/gitignore?templates=terraform&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">### Terraform ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Local .terraform directories&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">**/.&lt;/span>&lt;span class="n">terraform&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="c1"># .tfstate files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">*.&lt;/span>&lt;span class="n">tfstate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">*.&lt;/span>&lt;span class="n">tfstate&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="c1"># Crash log files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crash&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">log&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">crash&lt;/span>&lt;span class="o">.*.&lt;/span>&lt;span class="n">log&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"># Exclude all .tfvars files, which are likely to contain sensitive data, such as&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># password, private keys, and other secrets. These should not be part of version&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># control as they are data points which are potentially sensitive and subject&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># to change depending on the environment.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">*.&lt;/span>&lt;span class="n">tfvars&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">*.&lt;/span>&lt;span class="n">tfvars&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&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"># Ignore override files as they are usually used to override resources locally and so&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># are not checked in&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">override&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">override&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">*&lt;/span>&lt;span class="n">_override&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">*&lt;/span>&lt;span class="n">_override&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&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"># Include override files you do wish to add to version control using negated pattern&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># !example_override.tf&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"># Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># example: *tfplan*&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"># Ignore CLI configuration files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>&lt;span class="n">terraformrc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">terraform&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rc&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"># End of https://www.toptal.com/developers/gitignore/api/terraform&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="환경변수로-변수-지정하기">환경변수로 변수 지정하기&lt;/h3>
&lt;p>마지막으로는 환경변수를 지정하는 방법이 있습니다. &lt;code>TF_VAR_&lt;/code>로 시작해서 변수 이름을 적어주면 됩니다. &lt;code>~/.zshrc&lt;/code>등에 환경변수를 적어도 되지만 일단은 확인을 위해 인라인으로 환경변수를 지정해서 &lt;code>tf plan&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; &lt;span class="nv">TF_VAR_ami_id&lt;/span>&lt;span class="o">=&lt;/span>ami-09eb4311cbaecf89d tf plan
&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-09eb4311cbaecf89d&amp;#34;&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;/code>&lt;/pre>&lt;/div>&lt;h2 id="변수-작업을-더-해보자">변수 작업을 더 해보자&lt;/h2>
&lt;p>이제 변수를 사용하는 방법을 알았으니 나머지 값들도 변수로 변경하겠습니다.&lt;/p>
&lt;p>우선 변수를 선언합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;ami_id&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;instance_type&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">variable&lt;/span> &lt;span class="s2">&amp;#34;port&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">number&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">variable&lt;/span> &lt;span class="s2">&amp;#34;cidr_blocks&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">type&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="nb"> list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="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-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ami_id&lt;/span>&lt;span class="c1"> # 변수 ami_id 참조
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">instance_type&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p ${var.port} &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">port&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">cidr_blocks&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>terraform.tfvars&lt;/code> 파일을 사용해서 변수를 지정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ami_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-09eb4311cbaecf89d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>마지막으로 &lt;code>tf plan&lt;/code>을 실행해서 변수가 잘 작동하는지 확인합니다.&lt;/p>
&lt;h2 id="다음은-출력">다음은 출력&lt;/h2>
&lt;p>현재 작성 중인 코드에는 외부에서 EC2 인스턴스에 접근할 수 있는 IP를 확인할 수 있는 방법이 없고, AWS 콘솔에 접속해서 생성된 EC2 인스턴스를 찾아가야만 합니다. 이제 출력값을 설정해서 생성된 EC2 인스턴스의 IP를 확인해 보겠습니다.&lt;/p>
&lt;p>&lt;code>outputs.tf&lt;/code> 파일을 만들고 아래와 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">output&lt;/span> &lt;span class="s2">&amp;#34;public_ip&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">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_instance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">public_ip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sensitive&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">false&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>tf plan&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">2&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes to Outputs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&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;/code>&lt;/pre>&lt;/div>&lt;p>출력값이 추가되었다는 걸 알려주죠? 이 출력값은 EC2 인스턴스가 실제로 만들어진 다음에 확인할 수 있기 때문에 &lt;code>known after apply&lt;/code>라고 표시되었습니다. 이제 &lt;code>tf apply&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">2&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Outputs:
&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="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;13.209.17.62&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>약 1-2분 뒤에 해당 IP의 지정된 포트(기본 값은 8080으로 설정했었죠?)로 접속하면 &lt;code>Hello, World&lt;/code>가 출력되는 걸 확인할 수 있습니다.&lt;/p>
&lt;p>그런데 이런 출력 값이 있다는 건 어디서 알 수 있는 걸까요? &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance">EC2 인스턴스의 공식 문서&lt;/a>를 확인해볼까요?&lt;/p>
&lt;p>이 문서에는 EC2 인스턴스를 Terraform으로 관리할 때 사용할 수 있는 속성들이 아주 많이, 상세하게 정리되어 있습니다. 여기서 우리가 주목해야 할 곳은 문서 우측 상단의 &lt;code>ON THIS PAGE&lt;/code> 영역의 &lt;code>Attribute Reference&lt;/code>입니다.&lt;/p>
&lt;p align="center">&lt;img src="./01.png">&lt;/p>
&lt;p>이 영역에는 생성된 EC2 인스턴스를 통해 확인할 수 있는 속성들이 정리되어 있습니다. &lt;code>public_ip&lt;/code>를 찾아볼까요?&lt;/p>
&lt;p align="center">&lt;img src="./02.png">&lt;/p>
&lt;p>이렇게 필요한 속성을 찾아서 활용하면 됩니다. 물론&amp;hellip; 보시면 알겠지만 속성이 좀 많습니다. 뭐 하다보면 하나씩 눈에 보이는 거죠 하하하하하&amp;hellip;&lt;/p>
&lt;h2 id="내친-김에-ssh-키도-설정해보자">내친 김에 SSH 키도 설정해보자&lt;/h2>
&lt;p>이번에는 EC2 인스턴스에 접속할 때 SSH 키를 사용하도록 설정해보겠습니다. 먼저 &lt;code>main.tf&lt;/code>에서 SSH 키를 생성 및 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&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">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">key_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">aws_key_pair&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ssh&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">key_name&lt;/span>&lt;span class="c1"> # ssh 키 이름 참조
&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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="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="c1"> // ssh 접속을 위한 인바운드 규칙
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">22&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">cidr_blocks&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="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="c1"># ssh 키 쌍(pair) 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;tls_private_key&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;ssh&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">algorithm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;RSA&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rsa_bits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">4096&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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="c1"># ssh 키 쌍(pair) 리소스
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_key_pair&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;ssh&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">key_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">public_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">tls_private_key&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ssh&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">public_key_openssh&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>outputs.tf&lt;/code>에 SSH 키를 출력하도록 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">output&lt;/span> &lt;span class="s2">&amp;#34;tls_private_key&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">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">tls_private_key&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ssh&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">private_key_pem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sensitive&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&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;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>tls_private_key&lt;/code> 리소스는 hashicorp/tls 프로바이더를 통해 제공되므로 &lt;code>tf init&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing provider plugins...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Installing hashicorp/tls v4.0.4...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Installed hashicorp/tls v4.0.4 &lt;span class="o">(&lt;/span>signed by HashiCorp&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>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has been successfully initialized!
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 &lt;code>tf plan&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">3&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes to Outputs:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">tls_private_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>sensitive value&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 &lt;code>tf apply&lt;/code>를 실행합니다. 그런데 만약 이전 글의 내용을 따라 오면서 제한된 IAM이 설정된 계정을 사용하신다면 이런 에러가 발생합니다.&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; tf apply
&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: importing EC2 Key Pair &lt;span class="o">(&lt;/span>terraform-example&lt;span class="o">)&lt;/span>: UnauthorizedOperation: You are not authorized to perform this operation. User: arn:aws:iam::246181472423:user/terraform-user is not authorized to perform: ec2:ImportKeyPair on resource:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 작업을 위해서는 여기서 에러가 발생한 &lt;code>ec2:ImportKeyPair&lt;/code>, 그리고 &lt;code>ec2:DescribeKeyPairs&lt;/code>, &lt;code>ec2:DeleteKeyPair&lt;/code> 권한이 필요합니다. 이 권한을 추가해줍시다. 권한을 추가하고 다시 &lt;code>tf apply&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Apply complete! Resources: &lt;span class="m">2&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">1&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Outputs:
&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="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;15.164.214.233&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">tls_private_key&lt;/span> &lt;span class="o">=&lt;/span> &amp;lt;sensitive&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>tls_private_key&lt;/code>는 민감한 정보를 의미하는 &lt;code>sensitive = true&lt;/code>로 설정되었으므로 터미널을 통해서는 확인할 수 없습니다. 하지만 EC2에 접속하려면 ssh 파일이 필요하죠? Terraform의 상태로 부터 이 파일을 생성합니다.&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; tf output tls_private_key &amp;gt; ~/.ssh/ec2_example.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; chmod &lt;span class="m">400&lt;/span> ~/.ssh/ec2_example.pem &lt;span class="c1"># 권한 설정&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 생성된 파일을 통해 EC2에 접속해봅시다. 그리고 접속이 확인되면 &lt;code>exit&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; ssh -i ~/.ssh/ec2_example.pem ubuntu@15.164.214.233
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Welcome to Ubuntu 20.04.6 LTS &lt;span class="o">(&lt;/span>GNU/Linux 5.15.0-1048-aws x86_64&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">ubuntu@ip-172-31-43-230:~$ &lt;span class="nb">exit&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>모두 확인이 되었다면 &lt;code>tf destroy&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; tf destroy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Destroy complete! Resources: &lt;span class="m">4&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="하나만-더">하나만 더!&lt;/h2>
&lt;p>참고로 지금 우리가 사용한 pem 파일은 로컬 폴더의 &lt;code>terraform.tfstate&lt;/code>에 저장되어 있어서 언제든지 파일로 생성할 수 있는 상태므로, 이 파일은 권한이 없는 사람들이 접근할 수 없도록 관리되어야 합니다. 만약 이 파일에서 pem 파일을 생성하고 싶지 않다면, 별도로 파일을 생성한 뒤에 생성된 파일을 Terraform 코드를 통해 주입하는 방법이 있습니다.&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; ssh-keygen -t rsa -b &lt;span class="m">4096&lt;/span> -f ~/.ssh/ec2_example
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Generating public/private rsa key pair.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter passphrase &lt;span class="o">(&lt;/span>empty &lt;span class="k">for&lt;/span> no passphrase&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter same passphrase again:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your identification has been saved in /Users/onlifecoding/.ssh/ec2_example
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>보안을 위해 passphrase에 원하는 암호를 설정할 수 있으며, 암호를 입력하고 싶지 않은 경우는 그냥 엔터를 누르면 됩니다. 그리고 생성된 파일을 Terraform코드에서 참조하도록 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_key_pair&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;ssh&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">key_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">public_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;~/.ssh/ec2_example.pub&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>이렇게 하면 Terraform의 상태에 키 값을 저장하지 않고도 Terraform 코드를 통해 키 값을 주입할 수 있습니다. 상황에 맞게 적절한 방법을 사용하면 되겠죠!&lt;/p>
&lt;h1 id="마치며">마치며&lt;/h1>
&lt;p>사실 Terraform에서 제일 중요한 걸 한 가지 꼽아보자면, Terraform의 상태를 잘 관리하는 겁니다. 지금까지는 Terraform 상태에 대한 고려 없이 그냥 진행했고, 상태는 로컬의 &lt;code>terraform.tfstate&lt;/code>파일에 저장되어 있습니다. 다음에는 Terraform의 상태를 어떻게 관리하면 좋을지 살펴보겠습니다.&lt;/p>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://ercanermis.com/creating-ssh-keys-for-secure-access-to-aws-ec2-instances-with-terraform/">Creating SSH Keys for Secure Access to AWS EC2 Instances with Terraform&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>Terraform으로 기존 Azure 리소스 임포트하기</title><link>https://netscout.github.io/posts/terraform%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EC%A1%B4-azure-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%9E%84%ED%8F%AC%ED%8A%B8%ED%95%98%EA%B8%B0/</link><pubDate>Mon, 31 Jul 2023 16:27:57 +0900</pubDate><guid>https://netscout.github.io/posts/terraform%EC%9C%BC%EB%A1%9C-%EA%B8%B0%EC%A1%B4-azure-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%9E%84%ED%8F%AC%ED%8A%B8%ED%95%98%EA%B8%B0/</guid><description>Terraform은 DevOps의 필수 구성 요소로 자리 잡고 있습니다. Terraform을 통해 서비스를 지탱하는 인프라에 대한 정의, 배포, 업데이트 그리고 삭제를 코드로 정의할 수 있으며 이러한 개념을 IaC(infrastructure as code)라고 합니다.</description><content>&lt;p>Terraform은 DevOps의 필수 구성 요소로 자리 잡고 있습니다. Terraform을 통해 서비스를 지탱하는 인프라에 대한 정의, 배포, 업데이트 그리고 삭제를 코드로 정의할 수 있으며 이러한 개념을 IaC(infrastructure as code)라고 합니다.&lt;/p>
&lt;blockquote>
&lt;p>DevOps는 소프트웨어 배포를 빠르고 효율적으로 할 수 있게 해주는 프로세스, 아이디어, 테크닉을 말한다.&lt;/p>&lt;/blockquote>
&lt;p>그런데, 인프라를 코드로 작성하면 무슨 장점이 있는 걸까요?&lt;/p>
&lt;ul>
&lt;li>&lt;strong>자동화&lt;/strong>: 인프라를 코드로 작성할 수 있다면 자동화할 수 있고, 필요할 때 언제든 즉시 실행 가능합니다.&lt;/li>
&lt;li>&lt;strong>실수 방지&lt;/strong>: 자동화를 통해 수작업에서 발생하는 오류를 줄일 수 있고, 몇 번이고 반복해서 실행해도 안심할 수 있죠.&lt;/li>
&lt;li>&lt;strong>문서화&lt;/strong>: 또한 코드는 인프라에 대한 문서로 볼 수도 있습니다. 누군가의 머리 속에만 존재하지 않고, 누구나 볼 수 있는 코드에 인프라의 정보가 담기는 셈이죠.&lt;/li>
&lt;li>&lt;strong>버전 관리&lt;/strong>: 그리고 매우 큰 장점이 또 있는데요, 마치 소스 코드를 git으로 버전 관리 하듯이 인프라의 구성 역시 코드로써 버전 관리 할 수 있습니다. 뭔가 잘 못 됐을 때, 빠르게 커밋 로그를 통해 변경점을 찾을 수 있고 이전 버전으로 돌아갈 수도 있죠! 😊&lt;/li>
&lt;/ul>
&lt;p>그런데 Terraform으로 관리를 시작하면 누구나 거치는 어려움이 있습니다. 그건 바로 모든 리소스를 Terraform을 통해서만 관리해야 한다는 점입니다. Terraform으로 리소스를 만들어 놓고 나서, 나도 모르게 습관적으로 포털에 들어가서 마우스를 클릭클릭해서 설정을 변경하고 싶은 유혹에 빠집니다. 수작업을 추가하는 순간 Terraform의 상태 관리에서 벗어나기 시작하므로 피치 못할 사정이 아니면 Terraform을 통해서 상태를 변경해야 합니다.&lt;/p>
&lt;blockquote>
&lt;p>물론 어떤 기술이나 다 그렇듯이 Terraform으로 100% 다 제어가 불가능한 경우도 있습니다. Terraform의 한계이거나 Terraform 프로바이더의 문제 등으로 100% 제어가 다 안되는 경우가 종종 발생하긴 합니다. 그럴 때는 리소스의 생성 및 삭제만 Terraform으로 관리하고 나머지는 수작업으로 관리하는 수 밖에 없습니다. 😊 물론 제가 몰라서 그럴 가능성도 충분히&amp;hellip;쓰읍 😭&lt;/p>&lt;/blockquote>
&lt;p>다음으로 겪는 어려움은 Terraform을 사용하기 전에 이미 배포된 리소스입니다. 가장 이상적인 방법은 물론 기존 리소스를 지우고 Terraform으로 새로 배포하는 겠지만 현실은 전혀 그렇지 않죠! 😭&lt;/p>
&lt;p>그래서 Terraform은 기존에 이미 배포된 리소스를 임포트 할 수 있는 방법을 제공해왔습니다. 특히 최근 1.5 버전 부터 &lt;a href="https://developer.hashicorp.com/terraform/language/import/generating-configuration">import 블록&lt;/a>을 제공합니다.&lt;/p>
&lt;h2 id="예시">예시&lt;/h2>
&lt;p>import 블록을 통해 실제 배포된 인스턴스의 Terraform 코드를 생성해서 가져와보고자 합니다. 현재 작업 중인 솔루션은 대부분의 운영 리소스가 Terraform으로 배포되어 Azure에서 운영되고 있습니다. 딱 한 가지 Container Instance로 동작 중인 서버들이 수작업으로 배포되어 있는데요, 이 서버를 import 블록으로 가져오고자 합니다.&lt;/p>
&lt;h3 id="준비">준비&lt;/h3>
&lt;p>우선 가져오고자 하는 리소스의 Terraform 문서를 확인합니다. Container Instance는 &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_group">여기&lt;/a> 에서 확인할 수 있습니다.&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="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;example-resources&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;West Europe&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;example-continst&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">location&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ip_address_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Public&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">dns_name_label&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;aci-label&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&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">container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hello-world&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;mcr.microsoft.com/azuredocs/aci-helloworld:latest&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cpu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0.5&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;1.5&amp;#34;&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">ports&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="o">=&lt;/span> &lt;span class="mi">443&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;TCP&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sidecar&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;mcr.microsoft.com/azuredocs/aci-tutorial-sidecar&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cpu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0.5&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;1.5&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tags&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">environment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;testing&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>다행히도 복잡하지 않네요. 😊 리소스 그룹과 container instance 딱 2개만 신경쓰면 됩니다!&lt;/p>
&lt;h3 id="maintf-작성하기">main.tf 작성하기&lt;/h3>
&lt;p>import를 진행 할 폴더를 생성하고(ex: tf-import-test), main.tf 파일을 생성합니다.&lt;/p>
&lt;p>그리고 다음과 같이 Terraform 버전과 프로바이더를 설정합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">terraform&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">required_version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;= 1.0.0, &amp;lt; 2.0.0&amp;#34;&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">required_providers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">azurerm&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hashicorp/azurerm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt;3.0&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;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">provider&lt;/span> &lt;span class="s2">&amp;#34;azurerm&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">features&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>terraform 버전은 1.x.x 버전을 사용한다고 지정했고, Azure 클라우드 사용을 위해 azurerm 프로바이더를 지정했습니다.&lt;/p>
&lt;blockquote>
&lt;p>terraform 1.0 부터는 &lt;a href="https://developer.hashicorp.com/terraform/language/v1-compatibility-promises">v1-compatibility-promises&lt;/a>가 적용되는데요, 1.0 부터 향후 출시될 1.x 버전까지는 코드 변경 없이 동작한다는 걸 보장합니다. 그래서 위 main.tf에서 terraform의 버전을 1.0이상, 2.0 미만으로 설정해두었습니다. 😊&lt;/p>&lt;/blockquote>
&lt;h3 id="importtf-작성">import.tf 작성&lt;/h3>
&lt;p>이제 임포트 해오고 싶은 리소스를 import 블록으로 작성해 볼 차례입니다. 앞서 azurerm의 container instance의 예시 코드를 확인했었는데요, 거기에 맞춰 다음과 같이 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// import resource group
&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// import container instance
&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&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>2개의 import 블록은 각각 리소스 그룹과 리소스 그룹 안에 있는 container instance를 임포트합니다. 이제 구체적으로 각 import 블록이 어떤 리소스를 임포트 해와야 하는지 id를 적어줄 차례입니다. id는 각 클라우드 서비스에 따라 확인하는 방법이 다른데요 Azure는 다음과 같이 확인할 수 있습니다.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>az cli 사용하기&lt;/strong>
az resource show 명령을 통해 id를 확인할 수 있습니다.&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; az resource show -g &lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}&lt;/span> —name &lt;span class="o">{&lt;/span>리소스 이름&lt;span class="o">}&lt;/span> —resource-type “&lt;span class="o">{&lt;/span>리소스 타입&lt;span class="o">}&lt;/span>” &lt;span class="p">|&lt;/span> grep id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>여기서는 container instance를 임포트하므로 리소스 타입은 Microsoft.ContainerInstance 입니다. Azure의 경우 리소스 타입은 &lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-services-resource-providers">여기&lt;/a>에서 확인할 수 있습니다.
&lt;br>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>포털에서 확인하기&lt;/strong>
포털에 접근 가능한 경우 포털에서 해당 리소스를 찾고, 상단 우측의 &lt;strong>JSON 보기&lt;/strong>를 클릭합니다.&lt;/p>
&lt;p align="center">&lt;img src="./image01.png">&lt;/p>
화면 우측에 나타나는 정보창에서 리소스 ID를 확인 및 복사할 수 있습니다.
&lt;/li>
&lt;/ul>
&lt;p>이렇게 확인한 리소스 ID를 각각의 import 블록에 채워 넣습니다.&lt;/p>
&lt;h3 id="임포트-플랜-생성하기">임포트 플랜 생성하기&lt;/h3>
&lt;p>다음으로 &lt;strong>-generate-config-out&lt;/strong> 옵션을 통해 임포트 플랜을 생성합니다.&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; terraform init
&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; terraform plan -generate-config-out imported.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_resource_group.rg: Preparing import... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_container_group.container: Preparing import... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}&lt;/span>/providers/Microsoft.ContainerInstance/containerGroups/&lt;span class="o">{&lt;/span>리소스 이름&lt;span class="o">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_resource_group.rg: Refreshing state... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_container_group.container: Refreshing state... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}&lt;/span>/providers/Microsoft.ContainerInstance/containerGroups/&lt;span class="o">{&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">Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-/+ destroy and &lt;span class="k">then&lt;/span> create replacement
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># azurerm_container_group.container must be replaced&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># (imported from &amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Warning: this will destroy the imported resource&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-/+ resource &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">exposed_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span> &lt;span class="c1"># forces replacement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">fqdn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ~ &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;&lt;/span> -&amp;gt; &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ip_address_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Public&amp;#34;&lt;/span> &lt;span class="c1"># forces replacement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">network_profile_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">restart_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Always&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">subnet_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">zones&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ~ container &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">commands&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">cpu&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 class="nv">cpu_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{도커 이미지}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">memory&lt;/span> &lt;span class="o">=&lt;/span> 1.5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">memory_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">secure_environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>sensitive value&lt;span class="o">)&lt;/span> -&amp;gt; null
&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"> image_registry_credential &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">server&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">username&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&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 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="c1"># azurerm_resource_group.rg will be imported&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># (config will be generated)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> resource &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rg&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">2&lt;/span> to import, &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">1&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">╷
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Warning: Config generation is experimental
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>import.tf에 설정한 대로 리소스 그룹, container instance가 각각 &lt;strong>azurerm_resource_group.rg&lt;/strong>, &lt;strong>azurerm_container_group.container&lt;/strong> 블록으로 매핑되었습니다. 그리고 plan 실행 결과 2개의 리소스를 임포트 할 수 있고, 그 중에 1개는 replace 된다(1 to destroy, 1 to add)고 합니다. 어… 기존에 이미 동작하는 리소스를 가져오는 거라서 replace되면 좀 곤란하긴 하지만 그건 조금 이따가 해결해보겠습니다.&lt;/p>
&lt;p>우선 imported.tf를 열어서 생성된 코드를 확인합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">__generated__&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="nx">Terraform&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">Please&lt;/span> &lt;span class="nx">review&lt;/span> &lt;span class="nx">these&lt;/span> &lt;span class="nx">resources&lt;/span> &lt;span class="nx">and&lt;/span> &lt;span class="nx">move&lt;/span> &lt;span class="nx">them&lt;/span> &lt;span class="nx">into&lt;/span> &lt;span class="nx">your&lt;/span> &lt;span class="nx">main&lt;/span> &lt;span class="nx">configuration&lt;/span> &lt;span class="nx">files&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="err">#&lt;/span> &lt;span class="nx">__generated__&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rg&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">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">managed_by&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tags&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">#&lt;/span> &lt;span class="nx">__generated__&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&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">dns_name_label&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">exposed_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ip_address_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key_vault_key_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">restart_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Always&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">subnet_ids&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">tags&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">zones&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">container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">commands&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">cpu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cpu_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">environment_variables&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">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{도커 이미지}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memory_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">secure_environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">sensitive&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">image_registry_credential&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">sensitive&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">server&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">user_assigned_identity_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">username&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&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>이 container instance는 외부에서 접근할 수 있는 ip나 포트가 없고, 혼자서 자기 할 일을 하는 리소스입니다. 따라서 &lt;strong>ip_address_type&lt;/strong>등의 값이 null로 설정된 걸 볼 수 있습니다. 그런데 방금 전의 plan 결과를 다시 한번 볼까요?&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>&lt;/span>&lt;span class="line">&lt;span class="cl">-/+ resource &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">exposed_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span> &lt;span class="c1"># forces replacement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">fqdn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ~ &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;&lt;/span> -&amp;gt; &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ip_address_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Public&amp;#34;&lt;/span> &lt;span class="c1"># forces replacement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>ip_address_type&lt;/strong>은 분명 null인데 Public으로 설정된다고 하고, &lt;strong>exposed_port&lt;/strong> 역시 apply 실행 후에 얻을 수 있다고 하면서 강제로 이 리소스가 교체될 거라고(&lt;strong>forces replacement&lt;/strong>) 합니다. 음… 왜 그런지 모르겠지만 일단 이 값들의 변경을 무시하도록 처리해서 해결해보겠습니다.&lt;/p>
&lt;p>imported.tf의 &lt;strong>azurerm_container_group.container&lt;/strong> 에 &lt;strong>lifecycle&lt;/strong> 속성을 추가합니다.&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">resource &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> lifecycle &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">ignore_changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>exposed_port, ip_address_type&lt;span class="o">]&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 class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 다시 plan을 실행합니다.&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">❯ terraform plan
&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">Terraform will perform the following actions:
&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"># azurerm_container_group.container will be imported&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> resource &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">restart_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Always&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">subnet_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">zones&lt;/span> &lt;span class="o">=&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"> container &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">commands&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">cpu&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 class="nv">cpu_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{도커 이미지}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">memory&lt;/span> &lt;span class="o">=&lt;/span> 1.5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">memory_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">secure_environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>sensitive value&lt;span class="o">)&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"> image_registry_credential &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">server&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">username&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&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 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="c1"># azurerm_resource_group.rg will be imported&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> resource &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rg&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Plan: &lt;span class="m">2&lt;/span> to import, &lt;span class="m">0&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>오! 무시무시한 force replacement 가 사라졌습니다! 😀 이제 apply 해볼까요?&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; terraform apply
&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">Plan: &lt;span class="m">2&lt;/span> to import, &lt;span class="m">0&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you want to perform these actions?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Terraform will perform the actions described above.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Only &lt;span class="s1">&amp;#39;yes&amp;#39;&lt;/span> will be accepted to approve.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enter a value: yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_resource_group.rg: Importing... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_resource_group.rg: Import &lt;span class="nb">complete&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_container_group.container: Importing... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}&lt;/span>/providers/Microsoft.ContainerInstance/containerGroups/&lt;span class="o">{&lt;/span>리소스 이름&lt;span class="o">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">azurerm_container_group.container: Import &lt;span class="nb">complete&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/&lt;span class="o">{&lt;/span>리소스 그룹&lt;span class="o">}&lt;/span>/providers/Microsoft.ContainerInstance/containerGroups/&lt;span class="o">{&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">Apply complete! Resources: &lt;span class="m">2&lt;/span> imported, &lt;span class="m">0&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>임포트가 끝났습니다! 임포트된 상태 값은 같은 폴더의 &lt;strong>terraform.tfstate&lt;/strong> 파일에 저장되어 있는데요, Terraform 상태는 &lt;strong>backend&lt;/strong> 설정을 통해 원격 상태 저장소(Azure Storage, AWS S3)에 저장하는 게 일반적이죠.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Azure
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">terraform&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">// Store Terraform state in Azure Storage
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;azurerm&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">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">storage_account_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{스토리지 계정 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">container_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{스토리지 컨테이너 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{경로}/terraform.tfstate&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">terraform&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">// Store Terraform state in AWS S3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;s3&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">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{버킷 이름}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{경로}/terraform.tfstate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&amp;#34;&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">// lock을 사용하는 경우
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">dynamodb_table&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{테이블 이름}&amp;#34;&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">encrypt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">true&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>이렇게 backend를 설정하고 나서 &lt;strong>terraform init&lt;/strong> 을 실행하면, 로컬에 저장되어 있는 상태 값을 원격 저장소로 옮기게 됩니다.&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; terraform init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you want to copy existing state to the new backend?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pre-existing state was found &lt;span class="k">while&lt;/span> migrating the previous &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span> backend to the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> newly configured &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span> backend. No existing state was found in the newly
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> configured &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span> backend. Do you want to copy this state to the new &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> backend? Enter &lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span> to copy and &lt;span class="s2">&amp;#34;no&amp;#34;&lt;/span> to start with an empty state.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enter a value: yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Successfully configured the backend &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span>! Terraform will automatically
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use this backend unless the backend configuration changes.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="심화-모듈화-까지-진행해보기">심화: 모듈화 까지 진행해보기&lt;/h2>
&lt;p>그런데 사실 임포트 하고자 하는 container instance는 1대가 아니었습니다. 스테이징, 운영 환경에서 동일한 설정으로 각가 1대씩 운영 중인 상황입니다.&lt;/p>
&lt;p>동일한 방법으로 추가로 1대를 더 임포트 했다 치고 모듈화해봅시다!&lt;/p>
&lt;h3 id="모듈-작성하기">모듈 작성하기&lt;/h3>
&lt;p>우선 모듈을 저장하기 위한 폴더를 생성합니다. 여기서는 &lt;strong>xxx-modules&lt;/strong> 폴더에 작성합니다. 모듈은 종류에 따라서 구분되는 게 바람직한데요, 지금 작성하는 모듈은 container instance이므로 &lt;strong>xxx-modules/modules/services/container-instance&lt;/strong> 폴더를 생성하고 &lt;strong>main.tf&lt;/strong> 를 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">locals&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">vm_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${var.vm_name}-${var.env_alias}&amp;#34;&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">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${var.vm_name}&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">data&lt;/span> &lt;span class="s2">&amp;#34;azurerm_container_registry&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;acr&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container_registry_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container_registry_rg&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">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rg&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">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">location&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">managed_by&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">local&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">resource_group_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tags&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&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">dns_name_label&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">exposed_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ip_address_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key_vault_key_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">location&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">local&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">vm_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">restart_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Always&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">subnet_ids&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">tags&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">zones&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">container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">commands&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">cpu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cpu_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">environment_variables&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">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">vm_image_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">memory_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">local&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">vm_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">secure_environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">sensitive&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">image_registry_credential&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="err">#&lt;/span> &lt;span class="nx">sensitive&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">server&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">azurerm_container_registry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">login_server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">user_assigned_identity_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">username&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">azurerm_container_registry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">admin_username&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">// acr의 스펠링 변경도 무시할 수 있도록 image_registry_credential 추가
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">lifecycle&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ignore_changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">exposed_port&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ip_address_type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">image_registry_credential&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;strong>imported.tf&lt;/strong> 를 복사해온 다음에 필요한 정보를 추가하고, 모듈화 하기 위해서 하드코딩 되어 있던 내용을 변수로 추출했습니다. 추출된 변수는 &lt;strong>variables.tf&lt;/strong> 에 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//---------------------------Required Variables---------------------------//
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">variable&lt;/span> &lt;span class="nx">vm_name&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="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;name of the vm&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">variable&lt;/span> &lt;span class="nx">env_alias&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="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;alias of the environment&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">validation&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">condition&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">contains&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;stg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;prod&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;g&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="kd">var&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env_alias&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">error_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;env_alias must be any of dev, stg or prod, g&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//---------------------------Optional Variables---------------------------//
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">variable&lt;/span> &lt;span class="nx">location&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="nx">string&lt;/span>
&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 class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;location of the resource group&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">variable&lt;/span> &lt;span class="s2">&amp;#34;vm_image_name&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">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;name of the vm image&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">variable&lt;/span> &lt;span class="nx">container_registry_name&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="nx">string&lt;/span>
&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 class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;name of the container registry&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">variable&lt;/span> &lt;span class="nx">container_registry_rg&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="nx">string&lt;/span>
&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 class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;name of the resource group for the container registry&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>에를 들어서 &lt;strong>vm_name&lt;/strong>을 &lt;strong>some-vm&lt;/strong>, &lt;strong>env_alias&lt;/strong>를 &lt;strong>dev&lt;/strong>로 지정하면 리소스 그룹의 이름은 &lt;strong>some-vm&lt;/strong>, container instance의 이름은 &lt;strong>some-vm-dev&lt;/strong>가 되는 셈이죠. 개인적으로 리소스 그룹은 리소스의 이름과 일치시키는 걸 선호하지만 현재 배포된 상황이 같은 리소스 그룹에 2개의 container instance가 들어있기 때문에 어쩔 수 없이 일단 이렇게 작성했습니다.&lt;/p>
&lt;h3 id="모듈-사용하기">모듈 사용하기&lt;/h3>
&lt;p>그리고 이 모듈을 사용해서 리소스를 관리하기 위한 Terraform 코드를 작성할 차례입니다. 여기서는 &lt;strong>xxx-live/live/stg/services/container-instance/some-vm&lt;/strong> 에 작성합니다.&lt;/p>
&lt;p>&lt;strong>main.tf&lt;/strong> 부터 작성을 시작해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">terraform&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">required_version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;= 1.0.0, &amp;lt; 2.0.0&amp;#34;&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">required_providers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">azurerm&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hashicorp/azurerm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt;3.0&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="nx">Store&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">Azure&lt;/span> &lt;span class="nx">Storage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;azurerm&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">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">storage_account_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">container_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stg/services/container-instance/some-vm/terraform.tfstate&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">provider&lt;/span> &lt;span class="s2">&amp;#34;azurerm&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">features&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="s2">&amp;#34;some-vm&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">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;../../../../../../xxx-modules/modules/services/container-instance&amp;#34;&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">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&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">container_registry_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">container_registry_rg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&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">vm_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;some-vm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">env_alias&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">vm_image_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&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>앞서 작성한 모듈을 &lt;strong>source&lt;/strong> 속성으로 참조하였고 필요한 파라미터를 지정해주었습니다. 이제 앞서 import 했던 terraform.tfstate 파일을 main.tf 파일과 동일한 폴더에 복사합니다. 그리고 terraform init 명령을 실행합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">init&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">Initializing&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">backend&lt;/span>&lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Do&lt;/span> &lt;span class="nx">you&lt;/span> &lt;span class="nx">want&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">copy&lt;/span> &lt;span class="nx">existing&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">backend&lt;/span>&lt;span class="o">?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Pre&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">existing&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">was&lt;/span> &lt;span class="nx">found&lt;/span> &lt;span class="k">while&lt;/span> &lt;span class="nx">migrating&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">previous&lt;/span> &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">the&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newly&lt;/span> &lt;span class="nx">configured&lt;/span> &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span> &lt;span class="nx">backend&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">No&lt;/span> &lt;span class="nx">existing&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">was&lt;/span> &lt;span class="nx">found&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">newly&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">configured&lt;/span> &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span> &lt;span class="nx">backend&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">Do&lt;/span> &lt;span class="nx">you&lt;/span> &lt;span class="nx">want&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">copy&lt;/span> &lt;span class="k">this&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">backend&lt;/span>&lt;span class="o">?&lt;/span> &lt;span class="nx">Enter&lt;/span> &lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">copy&lt;/span> &lt;span class="nx">and&lt;/span> &lt;span class="s2">&amp;#34;no&amp;#34;&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">start&lt;/span> &lt;span class="kd">with&lt;/span> &lt;span class="nx">an&lt;/span> &lt;span class="nx">empty&lt;/span> &lt;span class="nx">state&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">Enter&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">yes&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">Successfully&lt;/span> &lt;span class="nx">configured&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="s2">&amp;#34;azurerm&amp;#34;&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">automatically&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">use&lt;/span> &lt;span class="k">this&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="nx">unless&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="nx">configuration&lt;/span> &lt;span class="nx">changes&lt;/span>&lt;span class="p">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Initializing&lt;/span> &lt;span class="nx">modules&lt;/span>&lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="nx">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="p">..&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">xxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">modules&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">modules&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">services&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">container&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">instance&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">Initializing&lt;/span> &lt;span class="nx">provider&lt;/span> &lt;span class="nx">plugins&lt;/span>&lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="nx">Finding&lt;/span> &lt;span class="nx">hashicorp&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">azurerm&lt;/span> &lt;span class="nx">versions&lt;/span> &lt;span class="nx">matching&lt;/span> &lt;span class="s2">&amp;#34;~&amp;gt; 3.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="o">-&lt;/span> &lt;span class="nx">Installing&lt;/span> &lt;span class="nx">hashicorp&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">azurerm&lt;/span> &lt;span class="nx">v3&lt;/span>&lt;span class="mf">.67.0&lt;/span>&lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="nx">Installed&lt;/span> &lt;span class="nx">hashicorp&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">azurerm&lt;/span> &lt;span class="nx">v3&lt;/span>&lt;span class="mf">.67.0&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">signed&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="nx">HashiCorp&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">Terraform&lt;/span> &lt;span class="nx">has&lt;/span> &lt;span class="nx">created&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="nx">lock&lt;/span> &lt;span class="nx">file&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nx">terraform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lock&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hcl&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">record&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">provider&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">selections&lt;/span> &lt;span class="nx">it&lt;/span> &lt;span class="nx">made&lt;/span> &lt;span class="nx">above&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">Include&lt;/span> &lt;span class="k">this&lt;/span> &lt;span class="nx">file&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">your&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="nx">control&lt;/span> &lt;span class="nx">repository&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">so&lt;/span> &lt;span class="nx">that&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">can&lt;/span> &lt;span class="nx">guarantee&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">make&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">same&lt;/span> &lt;span class="nx">selections&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">when&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">you&lt;/span> &lt;span class="nx">run&lt;/span> &lt;span class="s2">&amp;#34;terraform init&amp;#34;&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">future&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">Terraform&lt;/span> &lt;span class="nx">has&lt;/span> &lt;span class="nx">been&lt;/span> &lt;span class="nx">successfully&lt;/span> &lt;span class="nx">initialized&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="nx">You&lt;/span> &lt;span class="nx">may&lt;/span> &lt;span class="nx">now&lt;/span> &lt;span class="nx">begin&lt;/span> &lt;span class="nx">working&lt;/span> &lt;span class="kd">with&lt;/span> &lt;span class="nx">Terraform&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">Try&lt;/span> &lt;span class="nx">running&lt;/span> &lt;span class="s2">&amp;#34;terraform plan&amp;#34;&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">see&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">any&lt;/span> &lt;span class="nx">changes&lt;/span> &lt;span class="nx">that&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">required&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="nx">your&lt;/span> &lt;span class="nx">infrastructure&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">All&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">commands&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">should&lt;/span> &lt;span class="nx">now&lt;/span> &lt;span class="nx">work&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">If&lt;/span> &lt;span class="nx">you&lt;/span> &lt;span class="nx">ever&lt;/span> &lt;span class="nx">set&lt;/span> &lt;span class="nx">or&lt;/span> &lt;span class="nx">change&lt;/span> &lt;span class="nx">modules&lt;/span> &lt;span class="nx">or&lt;/span> &lt;span class="nx">backend&lt;/span> &lt;span class="nx">configuration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="nx">Terraform&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">rerun&lt;/span> &lt;span class="k">this&lt;/span> &lt;span class="nx">command&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">reinitialize&lt;/span> &lt;span class="nx">your&lt;/span> &lt;span class="nx">working&lt;/span> &lt;span class="nx">directory&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">If&lt;/span> &lt;span class="nx">you&lt;/span> &lt;span class="nx">forget&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">other&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">commands&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">detect&lt;/span> &lt;span class="nx">it&lt;/span> &lt;span class="nx">and&lt;/span> &lt;span class="nx">remind&lt;/span> &lt;span class="nx">you&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="nx">so&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">necessary&lt;/span>&lt;span class="p">.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>모듈과 필요한 프로바이더를 설치하였고, import 로 생성했던 상태 역시 새로 지정된 backend로 이관되었습니다. 그러면 plan을 실행해볼까요?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">plan&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_registry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Reading&lt;/span>&lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Refreshing&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">...&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="err">/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Refreshing&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">...&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="err">/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}]&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_registry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acr&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Read&lt;/span> &lt;span class="nx">complete&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">acr&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="nx">Terraform&lt;/span> &lt;span class="nx">used&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">selected&lt;/span> &lt;span class="nx">providers&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">generate&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">following&lt;/span> &lt;span class="nx">execution&lt;/span> &lt;span class="nx">plan&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">Resource&lt;/span> &lt;span class="nx">actions&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">indicated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">with&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">following&lt;/span> &lt;span class="nx">symbols&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> &lt;span class="nx">create&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">destroy&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">Terraform&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">perform&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">following&lt;/span> &lt;span class="nx">actions&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="err">#&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">because&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="nx">is&lt;/span> &lt;span class="nx">not&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">configuration&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&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="o">-&lt;/span> &lt;span class="nx">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;id=/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/{리소스 그룹}/providers/Microsoft.ContainerInstance/containerGroups/{리소스 이름}&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 그룹}&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">restart_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Always&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">subnet_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">zones&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&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 class="nx">container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">commands&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">cpu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">cpu_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1.5&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">memory_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;{리소스 이름}&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">secure_environment_variables&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">sensitive&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&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="o">-&lt;/span> &lt;span class="nx">image_registry_credential&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">server&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">username&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&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="err">#&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">because&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span> &lt;span class="nx">is&lt;/span> &lt;span class="nx">not&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">configuration&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rg&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="o">-&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="nx">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">null&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="err">#&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_container_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;container&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="o">+&lt;/span> &lt;span class="nx">dns_name_label_reuse_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Unsecure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">exposed_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">known&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="nx">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">fqdn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">known&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="nx">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">known&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="nx">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">known&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="nx">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">ip_address_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Public&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;some-vm-stg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">network_profile_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">known&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="nx">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">os_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">resource_group_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;some-vm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">restart_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Always&amp;#34;&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 class="nx">container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">commands&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="o">+&lt;/span> &lt;span class="nx">cpu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">cpu_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">image&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">memory_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;some-vm-stg&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">image_registry_credential&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">server&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">username&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;...&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="s2">&amp;#34;azurerm_resource_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;rg&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="o">+&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">known&lt;/span> &lt;span class="nx">after&lt;/span> &lt;span class="nx">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">location&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;koreacentral&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">+&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;some-vm&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Plan&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">add&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">change&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">destroy&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="err">─────────────────────────────────────────────────────────────────────────────────────────────────────────────────&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">Note&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">You&lt;/span> &lt;span class="nx">didn&lt;/span>&lt;span class="s1">&amp;#39;t use the -out option to save this plan, so Terraform can&amp;#39;&lt;/span>&lt;span class="nx">t&lt;/span> &lt;span class="nx">guarantee&lt;/span> &lt;span class="nx">to&lt;/span> &lt;span class="nx">take&lt;/span> &lt;span class="nx">exactly&lt;/span> &lt;span class="nx">these&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">actions&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">you&lt;/span> &lt;span class="nx">run&lt;/span> &lt;span class="s2">&amp;#34;terraform apply&amp;#34;&lt;/span> &lt;span class="nx">now&lt;/span>&lt;span class="p">.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>음…. 기존 리소스를 모두 삭제하고 새로 만들겠다고 합니다. 분명 terraform.tfstate 파일을 가져와서 backend로 이관 했음에도 불구하고 삭제 하겠다는 군요! 문제가 되는 부분이 바로 이 부분입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">because&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="nx">is&lt;/span> &lt;span class="nx">not&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">configuration&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="err">#&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">#&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">because&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span> &lt;span class="nx">is&lt;/span> &lt;span class="nx">not&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nx">configuration&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>그러니까 현재 작성중인 Terraform 코드에 &lt;strong>azurerm_container_group.container&lt;/strong>, &lt;strong>azurerm_resource_group.rg&lt;/strong> 가 없다는 말입니다!&lt;/p>
&lt;p>지금 우리는 모듈을 정의하고 모듈을 사용해서 Terraform 코드를 작성했죠? 그래서 사실 각 리소스의 이름은 이렇게 변경됩니다.&lt;/p>
&lt;ul>
&lt;li>azurerm_container_group.container → module.some-vm.azurerm_container_group.container&lt;/li>
&lt;li>azurerm_resource_group.rg → module.some-vm.azurerm_resource_group.rg&lt;/li>
&lt;/ul>
&lt;p>따라서 기존 상태에 있는 리소스를 새 이름으로 import 해주고 기존 리소스는 상태에서 제거해야 합니다! 😤
우선 import로 기존 리소스를 새로운 이름으로 가져옵니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="kr">import&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="nx">subscriptions&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">xxxxxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxxxxxxxxxx&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">resourceGroups&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">리소스&lt;/span> &lt;span class="nx">그룹&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">providers&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">Microsoft&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ContainerInstance&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">containerGroups&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">리소스&lt;/span> &lt;span class="nx">이름&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">Import&lt;/span> &lt;span class="nx">successful&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="nx">The&lt;/span> &lt;span class="nx">resources&lt;/span> &lt;span class="nx">that&lt;/span> &lt;span class="nx">were&lt;/span> &lt;span class="nx">imported&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">shown&lt;/span> &lt;span class="nx">above&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">These&lt;/span> &lt;span class="nx">resources&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">now&lt;/span> &lt;span class="k">in&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">your&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">and&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">henceforth&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">managed&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="nx">Terraform&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="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="kr">import&lt;/span> &lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="nx">subscriptions&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">xxxxxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxx&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">xxxxxxxxxxxx&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">resourceGroups&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">리소스&lt;/span> &lt;span class="nx">그룹&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">Import&lt;/span> &lt;span class="nx">successful&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="nx">The&lt;/span> &lt;span class="nx">resources&lt;/span> &lt;span class="nx">that&lt;/span> &lt;span class="nx">were&lt;/span> &lt;span class="nx">imported&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">shown&lt;/span> &lt;span class="nx">above&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">These&lt;/span> &lt;span class="nx">resources&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">now&lt;/span> &lt;span class="k">in&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">your&lt;/span> &lt;span class="nx">Terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">and&lt;/span> &lt;span class="nx">will&lt;/span> &lt;span class="nx">henceforth&lt;/span> &lt;span class="nx">be&lt;/span> &lt;span class="nx">managed&lt;/span> &lt;span class="nx">by&lt;/span> &lt;span class="nx">Terraform&lt;/span>&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-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_registry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acr&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&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="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">rm&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Removed&lt;/span> &lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Successfully&lt;/span> &lt;span class="nx">removed&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="nx">instance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&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="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">rm&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Removed&lt;/span> &lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Successfully&lt;/span> &lt;span class="nx">removed&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="nx">instance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&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="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="nx">list&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_registry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">acr&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_container_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">container&lt;/span>
&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">some&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">azurerm_resource_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">rg&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 마지막으로 plan 을 실행해 봅시다!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="err">❯&lt;/span> &lt;span class="nx">terraform&lt;/span> &lt;span class="nx">plan&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">No&lt;/span> &lt;span class="nx">changes&lt;/span>&lt;span class="p">.&lt;/span> &lt;span class="nx">Your&lt;/span> &lt;span class="nx">infrastructure&lt;/span> &lt;span class="nx">matches&lt;/span> &lt;span class="nx">the&lt;/span> &lt;span class="nx">configuration&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">Terraform&lt;/span> &lt;span class="nx">has&lt;/span> &lt;span class="nx">compared&lt;/span> &lt;span class="nx">your&lt;/span> &lt;span class="nx">real&lt;/span> &lt;span class="nx">infrastructure&lt;/span> &lt;span class="nx">against&lt;/span> &lt;span class="nx">your&lt;/span> &lt;span class="nx">configuration&lt;/span> &lt;span class="nx">and&lt;/span> &lt;span class="nx">found&lt;/span> &lt;span class="nx">no&lt;/span> &lt;span class="nx">differences&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">so&lt;/span> &lt;span class="nx">no&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">changes&lt;/span> &lt;span class="nx">are&lt;/span> &lt;span class="nx">needed&lt;/span>&lt;span class="p">.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Wow! 이제 드디어 import 해온 상태와 현재 제가 원하는 상태가 일치합니다! 그리고 모듈화도 성공했죠! 👍&lt;/p>
&lt;blockquote>
&lt;p>여기서는 간단한 예시를 위해서 terraform을 이용했지만, terragrunt를 사용하면 위처럼 이름을 바꾸는 임포트 작업을 할 필요가 없습니다. 모듈을 사용할 때도 원래 이름 그대로 가져오기 때문입니다! 😊&lt;/p>&lt;/blockquote>
&lt;h1 id="참고자료">참고자료&lt;/h1>
&lt;ul>
&lt;li>terraform up and running 3rd edition&lt;/li>
&lt;li>&lt;a href="https://jakewalsh.co.uk/testing-terraform-import-in-v-1-5-0-with-azure-resources/">Testing Terraform Import in v.1.5.0+ with Azure Resources&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남</title><link>https://netscout.github.io/posts/how-to-terraform-1/</link><pubDate>Wed, 15 Mar 2023 09:00:00 +0900</pubDate><guid>https://netscout.github.io/posts/how-to-terraform-1/</guid><description>Terraform은 DevOps의 필수 구성 요소로 자리 잡고 있습니다. Terraform을 통해 서비스를 지탱하는 인프라에 대한 정의, 배포, 업데이트 그리고 삭제를 코드로 정의할 수 있으며 이러한 개념을 IaC(infrastructure as code)라고 합니다.</description><content>&lt;h2 id="terraform이란">Terraform이란?&lt;/h2>
&lt;p>Terraform은 DevOps의 필수 구성 요소로 자리 잡고 있습니다. Terraform을 통해 서비스를 지탱하는 인프라에 대한 정의, 배포, 업데이트 그리고 삭제를 코드로 정의할 수 있으며 이러한 개념을 IaC(infrastructure as code)라고 합니다.&lt;/p>
&lt;blockquote>
&lt;p>DevOps는 소프트웨어 배포를 빠르고 효율적으로 할 수 있게 해주는 프로세스, 아이디어, 테크닉을 말한다.&lt;/p>&lt;/blockquote>
&lt;p>그런데, 인프라를 코드로 작성하면 무슨 장점이 있는 걸까요?&lt;/p>
&lt;ul>
&lt;li>&lt;strong>자동화&lt;/strong>: 인프라를 코드로 작성할 수 있다면 자동화할 수 있고, 필요할 때 언제든 즉시 실행 가능합니다.&lt;/li>
&lt;li>&lt;strong>실수 방지&lt;/strong>: 자동화를 통해 수작업에서 발생하는 오류를 줄일 수 있고, 몇 번이고 반복해서 실행해도 안심할 수 있죠.&lt;/li>
&lt;li>&lt;strong>문서화&lt;/strong>: 또한 코드는 인프라에 대한 문서로 볼 수도 있습니다. 누군가의 머리 속에만 존재하지 않고, 누구나 볼 수 있는 코드에 인프라의 정보가 담기는 셈이죠.&lt;/li>
&lt;li>&lt;strong>버전 관리&lt;/strong>: 그리고 매우 큰 장점이 또 있는데요, 마치 소스 코드를 git으로 버전 관리 하듯이 인프라의 구성 역시 코드로써 버전 관리 할 수 있습니다. 뭔가 잘 못 됐을 때, 빠르게 커밋 로그를 통해 변경점을 찾을 수 있고 이전 버전으로 돌아갈 수도 있죠! 😊&lt;/li>
&lt;/ul>
&lt;p>그런데 Terraform으로 관리를 시작하면 누구나 거치는 어려움이 있습니다. 그건 바로 모든 리소스를 Terraform을 통해서만 관리해야 한다는 점입니다. Terraform으로 리소스를 만들어 놓고 나서, 나도 모르게 습관적으로 포털에 들어가서 마우스를 클릭클릭해서 설정을 변경하고 싶은 유혹에 빠집니다. 수작업을 추가하는 순간 Terraform의 상태 관리에서 벗어나기 시작하므로 피치 못할 사정이 아니면 Terraform을 통해서 상태를 변경해야 합니다.&lt;/p>
&lt;blockquote>
&lt;p>오래된 습관은 아주 무섭습니다. 문제를 해결하려다 보면 저도 모르게 포털에 들어가서 설정을 바꾸곤 합니다&amp;hellip;😅&lt;/p>&lt;/blockquote>
&lt;h2 id="terraform-설치하기">Terraform 설치하기&lt;/h2>
&lt;p>Terraform을 설치하는 방법은 여려가지가 있지만, 저는 &lt;code>tfenv&lt;/code>를 추천합니다. &lt;code>tfenv&lt;/code>는 Terraform의 버전을 관리해주는 도구로, 여러 버전의 Terraform을 설치하고 각각의 버전을 쉽게 전환할 수 있습니다. 예를 들어 1.4.0 버전의 Terraform을 사용하던 중에 1.5.0이 출시되었다고 해보죠. 1.5.0을 사용해보다가 뭔가 문제가 있어서 다시 1.4.0으로 돌아오고 싶을 수도 있습니다. 그런 경우에 1.4.0을 삭제하고 1.5.0을 설치하고&amp;hellip; 다시 1.5.0을 삭제하고 1.4.0을 설치하고&amp;hellip; 하는 번거로운 작업을 해야 하지만, &lt;code>tfenv&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"># tfenv 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; brew install tfenv
&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">&amp;gt; tfenv list-remote
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.7.0-alpha20231025
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-rc1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-beta3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-beta2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-beta1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-alpha20230816
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-alpha20230802
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.6.0-alpha20230719
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.5.7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.5.6
&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="c1"># 현재 설치된 버전 확인(terraform을 alias인 tf로 등록해서 사용하고 있습니다.)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tf version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform v1.5.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">on darwin_arm64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your version of Terraform is out of date! The latest version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">is 1.6.2. You can update by downloading from https://www.terraform.io/downloads.html
&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.6.2 버전 설치하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tfenv install 1.6.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Installing Terraform v1.6.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Installation of terraform v1.6.2 successful. To make this your default version, run &lt;span class="s1">&amp;#39;tfenv use 1.6.2&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 class="c1"># 1.6.2 버전으로 전환하기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; tfenv use 1.6.2
&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">&amp;gt; tf version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform v1.6.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">on darwin_arm64
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;code>terraform&lt;/code>이라고 매번 입력하는게 어려우니 .zshrc나 .bashrc에 다음과 같이 alias 설정을 해서 &lt;code>tf&lt;/code>로 사용하는 것도 방법입니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">alias tf=terraform
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&lt;blockquote>
&lt;p>terraform 1.0 부터는 &lt;a href="https://developer.hashicorp.com/terraform/language/v1-compatibility-promises">v1-compatibility-promises&lt;/a>가 적용되는데요, 1.0 부터 향후 출시될 1.x 버전까지는 코드 변경 없이 동작한다는 걸 보장합니다. 따라서 1.x 버전에서는 최신 버전을 사용하지 않을 이유가 없을지도&amp;hellip;?&lt;/p>&lt;/blockquote>
&lt;h2 id="aws-iam-사용자-생성하기">AWS IAM 사용자 생성하기&lt;/h2>
&lt;p>Terraform이 AWS의 리소스를 관리하기 위해서는 Terraform이 사용할 수 있는 AWS IAM 사용자가 필요합니다. AWS IAM 사용자를 생성하고 Terraform이 사용할 수 있는 권한을 부여해보겠습니다.&lt;/p>
&lt;h3 id="iam-사용자-생성하기">IAM 사용자 생성하기&lt;/h3>
&lt;p>먼저 AWS 계정이 필요하겠죠? 계정을 생성하고 로그인하면 AWS 콘솔에 접속할 수 있습니다. AWS 콘솔에서 IAM을 검색하고 IAM을 선택합니다.&lt;/p>
&lt;p align="center">&lt;img src="./01.png">&lt;/p>
&lt;p>좌측의 &lt;code>엑세스 관리&lt;/code> &amp;gt; &lt;code>사용자&lt;/code> &amp;gt; &lt;code>사용자 생성&lt;/code>을 클릭합니다. 이어지는 단계에서 사용자 이름을 입력하고 다음으로 이동합니다.&lt;/p>
&lt;p align="center">&lt;img src="./02.png">&lt;/p>
&lt;p>우선 &lt;code>사용자 그룹 생성&lt;/code>을 눌러 사용자 그룹의 이름을 설정하고 &lt;code>권한 정책&lt;/code>에서는 일단 모든 AWS 리소스에 대한 권한을 가지는 &lt;code>AdministratorAccess&lt;/code>를 선택하고 &lt;code>사용자 그룹 생성&lt;/code>을 누릅니다.&lt;/p>
&lt;p align="center">&lt;img src="./03.png">&lt;/p>
&lt;blockquote>
&lt;p>전체 권한이 부여된 계정이 잘못 사용되면 큰 문제를 일으킬 수 있으므로 필요한 권한만을 부여하는 게 좋습니다. 필요한 만큼 권한을 부여하는 방법에 대해서는 추후 다시 설명하겠습니다.&lt;/p>&lt;/blockquote>
&lt;p>사용자 그룹이 생성되면 다음과 같이 생성된 그룹을 선택하고 &lt;code>다음&lt;/code>을 누릅니다.&lt;/p>
&lt;p align="center">&lt;img src="./04.png">&lt;/p>
&lt;p>그리고 마지막으로 &lt;code>검토 및 생성&lt;/code>단계에서 사용자 정보를 확인하고 &lt;code>사용자 생성&lt;/code>을 누릅니다. 이제 사용자 목록에서 생성된 사용자를 확인할 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./05.png">&lt;/p>
&lt;p>사용자 이름을 눌러 세부 정보 페이지로 이동하고 우측 상단의 &lt;code>액세스 키 만들기&lt;/code>를 클릭하고, 목록에서 &lt;code>Command Line Interface(CLI)&lt;/code>를 선택하고 &lt;code>다음&lt;/code>을 누릅니다.&lt;/p>
&lt;p align="center">&lt;img src="./06.png">&lt;/p>
&lt;p>&lt;code>설명 태그 설정&lt;/code>에서 태그 이름을 작성(ex: Terraform-Access)하고 &lt;code>액세스 키 만들기&lt;/code>를 누릅니다. 이제 액세스 키 ID와 비밀 액세스 키를 확인할 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./07.png">&lt;/p>
&lt;p>비밀 액세스 키를 잃어버리면 다시 발급해야 하므로 잘 보관해두시기 바랍니다.&lt;/p>
&lt;h2 id="terraform-계정-정보-설정하기">Terraform 계정 정보 설정하기&lt;/h2>
&lt;p>앞서 Terraform이 사용할 계정을 생성하고 해당 계정의 액세스 키도 생성을 마쳤습니다. 이제 Terraform이 해당 계정을 사용할 수 있도록 환경 변수에 계정 정보를 설정해보겠습니다.&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"># zsh을 사용하는 경우&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; vi ~/.zshrc
&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"># bash를 사용하는 경우&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; vi ~/.bashrc
&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">AWS_ACCESS_KEY_ID&lt;/span>&lt;span class="o">={&lt;/span>액세스 ID&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">AWS_SECRET_ACCESS_KEY&lt;/span>&lt;span class="o">={&lt;/span>비밀 액세스 키&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>참고로 vi로 파일을 열고 &lt;code>i&lt;/code>를 누르면 편집 모드로 전환되고, &lt;code>esc&lt;/code>를 누르면 편집 모드에서 빠져나올 수 있습니다. &lt;code>:wq&lt;/code>를 입력하면 저장하고 빠져나올 수 있습니다.
그리고 환경 변수를 설정한 후에는 &lt;code>source ~/.zshrc&lt;/code> 또는 &lt;code>source ~/.bashrc&lt;/code>를 입력해서 환경 변수를 적용할 수 있습니다.&lt;/p>&lt;/blockquote>
&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; &lt;span class="nb">echo&lt;/span> &lt;span class="nv">$AWS_ACCESS_KEY_ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>액세스 ID&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="첫-terraform-코드-작성하기">첫 Terraform 코드 작성하기&lt;/h2>
&lt;p>이제 Terraform을 사용할 준비가 끝났습니다. 터미널을 열고 다음과 같이 작업할 공간을 생성합니다.&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">~/src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; mkdir how-to-terraform
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~/src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">cd&lt;/span> how-to-terraform
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~/src/how-to-terraform
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; code .
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>이 시리즈에서는 Visual Studio Code를 사용합니다.&lt;/p>&lt;/blockquote>
&lt;p>에디터에서 &lt;code>chapter01&lt;/code> 폴더를 생성하고 폴더 안에 &lt;code>main.tf&lt;/code> 파일을 생성합니다. 그리고 다음과 같이 코드를 작성합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p 8080 &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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>AWS에서 제공하는 프로바이더를 선언하였고, AWS의 ap-northeast-2(서울)리전을 대상으로 Terraform 코드를 실행하도록 설정하였습니다. 그리고 Ubuntu 20.04로 동작하는 EC2 인스턴스를 하나 생성하였습니다. 그리고 최초 인스턴스가 생성 될 때 &lt;code>user_data&lt;/code>에 지정한 스크립트가 실행되면서 웹에서 접속하면 &lt;code>Hello, World&lt;/code>를 출력하는 웹 서버가 실행되도록 설정하였습니다.&lt;/p>
&lt;h2 id="terraform-초기화-및-실행-계획-확인하기">Terraform 초기화 및 실행 계획 확인하기&lt;/h2>
&lt;p>&lt;code>main.tf&lt;/code> 파일에 우클릭하고 &lt;code>통합 터미널에서 열기&lt;/code>를 선택하여 &lt;code>main.tf&lt;/code>파일이 위치한 경로에서 터미널을 실행합니다.&lt;/p>
&lt;p align="center">&lt;img src="./08.png">&lt;/p>
&lt;p>그리고 다음과 같이 Terraform을 초기화합니다.&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; tf init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing the backend...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Initializing provider plugins...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Finding latest version of hashicorp/aws...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Installing hashicorp/aws v5.23.1...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Installed hashicorp/aws v5.23.1 &lt;span class="o">(&lt;/span>signed by HashiCorp&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">Terraform has created a lock file .terraform.lock.hcl to record the provider
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">selections it made above. Include this file in your version control repository
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">so that Terraform can guarantee to make the same selections by default when
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">you run &lt;span class="s2">&amp;#34;terraform init&amp;#34;&lt;/span> in the future.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform has been successfully initialized!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">You may now begin working with Terraform. Try running &lt;span class="s2">&amp;#34;terraform plan&amp;#34;&lt;/span> to see
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">any changes that are required &lt;span class="k">for&lt;/span> your infrastructure. All Terraform commands
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">should now work.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">If you ever &lt;span class="nb">set&lt;/span> or change modules or backend configuration &lt;span class="k">for&lt;/span> Terraform,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rerun this &lt;span class="nb">command&lt;/span> to reinitialize your working directory. If you forget, other
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">commands will detect it and remind you to &lt;span class="k">do&lt;/span> so &lt;span class="k">if&lt;/span> necessary.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>AWS 프로바이더 5.23.1 버전이 설치되었고, Terraform이 정상적으로 초기화 되었음을 알 수 있습니다. 그렇다면, 이제 Terraform 코드로 인프라가 어떻게 구성될지 미리 살펴볼까요?&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; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform used the selected providers to generate the following execution plan. Resource
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">actions are indicated with the following symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + create
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">associate_public_ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">availability_zone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cpu_core_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cpu_threads_per_core&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">disable_api_stop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">disable_api_termination&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ebs_optimized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">get_password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">host_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">host_resource_group_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">iam_instance_profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_initiated_shutdown_behavior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_lifecycle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_address_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_addresses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">key_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">monitoring&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">outpost_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">placement_group&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">placement_partition_number&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">primary_network_interface_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">private_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">private_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">secondary_private_ips&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">source_dest_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">spot_instance_request_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">subnet_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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 class="nv">tags_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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 class="nv">tenancy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;c765373c563b260626d113c4a56a46e8a8c5ca33&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data_base64&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data_replace_on_change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&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">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">Note: You didn&lt;span class="s1">&amp;#39;t use the -out option to save this plan, so Terraform can&amp;#39;&lt;/span>t guarantee to take
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">exactly these actions &lt;span class="k">if&lt;/span> you run &lt;span class="s2">&amp;#34;terraform apply&amp;#34;&lt;/span> now.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>tf plan&lt;/code>명령을 실행하면 Terraform이 현재 인프라의 상태와 코드에 정의된 인프라를 비교해서 어떻게 인프라에 반영할 것인지 미리 확인해볼 수 있습니다. 추가될 요소는 앞에 &lt;code>+&lt;/code>가 붙고 삭제된 요소는 앞에 &lt;code>-&lt;/code>가 붙습니다. 그리고 코드에서 지정해준 인스턴스의 ami, instance_type, user_data 등을 제외하면 모두 &lt;code>known after apply&lt;/code>라고 표시되어 있습니다. 이런 정보는 인스턴스가 실제로 생성되고 나서 확인할 수 있다는 의미입니다.&lt;/p>
&lt;p>이렇게 코드가 반영되면 어떻게 인프라가 변경될지 미리 상세하게 확인해볼 수 있구요, 맨 마지막 줄에는 요약 정보가 표시됩니다. 코드의 실행 게획을 미리 짜본 결과 1개의 리소스가 추가되고, 변경될 리소스는 0개, 삭제될 리소스 역시 0개라고 합니다. Terraform 코드를 처음 실행하는 거니까 당연한 결과겠죠?&lt;/p>
&lt;h2 id="terraform-코드-실행하기">Terraform 코드 실행하기&lt;/h2>
&lt;p>실행 계획을 확인해보고 문제가 없으면 코드를 실행해서 인프라를 변경할 수 있습니다. 다음과 같이 &lt;code>tf apply&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform used the selected providers to generate the following execution plan. Resource
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">actions are indicated with the following symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + create
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># aws_instance.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">associate_public_ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">availability_zone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cpu_core_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cpu_threads_per_core&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">disable_api_stop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">disable_api_termination&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ebs_optimized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">get_password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">host_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">host_resource_group_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">iam_instance_profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_initiated_shutdown_behavior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_lifecycle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_address_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_addresses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">key_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">monitoring&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">outpost_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">placement_group&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">placement_partition_number&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">primary_network_interface_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">private_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">private_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">secondary_private_ips&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">source_dest_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">spot_instance_request_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">subnet_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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 class="nv">tags_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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 class="nv">tenancy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;c765373c563b260626d113c4a56a46e8a8c5ca33&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data_base64&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">user_data_replace_on_change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&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">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you want to perform these actions?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Terraform will perform the actions described above.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Only &lt;span class="s1">&amp;#39;yes&amp;#39;&lt;/span> will be accepted to approve.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enter a value:
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>코드를 실행하면 앞서 확인한 것 처럼 실행 계획을 한 번 더 확인합니다. 그리고 정말 이 코드를 실행하려면 &lt;code>yes&lt;/code>를 입력하라고 합니다. &lt;code>yes&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">aws_instance.example: Creating...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Still creating... &lt;span class="o">[&lt;/span>10s elapsed&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Still creating... &lt;span class="o">[&lt;/span>20s elapsed&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Creation &lt;span class="nb">complete&lt;/span> after 22s &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b&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">Apply complete! Resources: &lt;span class="m">1&lt;/span> added, &lt;span class="m">0&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>참고로 실행 계획을 생성할 때, 실행 계획을 파일로 출력하여 코드를 실행할 때 실행 계획대로 바로 실행되도록 할 수도 있습니다. &lt;code>tf plan -out plan.out&lt;/code>을 입력하면 실행 계획이 &lt;code>plan.out&lt;/code> 파일로 저장되고, &lt;code>tf apply plan.out&lt;/code>을 입력하면 &lt;code>plan.out&lt;/code> 파일에 저장된 실행 계획대로 코드가 실행됩니다.&lt;/p>&lt;/blockquote>
&lt;p>코드 실행 결과로 1개의 리소스가 생성되었습니다. 포털에서 EC2로 검색하여 EC2 포털로 들어가면 &lt;code>인스턴스(실행 중)&lt;/code> 항목이 &lt;code>1&lt;/code>인 걸 확인할 수 있으며, 해당 항목을 누르면 실행 중인 EC2를 확인할 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./09.png">&lt;/p>
&lt;p>인스턴스 ID를 눌러 상세 페이지로 들어가면 우측 상단에서 &lt;code>퍼블릭 IPv4 주소&lt;/code>, &lt;code>퍼블릭 IPv4 DNS&lt;/code> 등을 확인할 수 있지만, 해당 주소로 접속은 불가합니다.&lt;/p>
&lt;p>&lt;code>main.tf&lt;/code> 코드에 다음 항목을 추가해서 외부에서의 접속을 허용합니다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-terraform" data-lang="terraform">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// AWS 프로바이더 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&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"> // ap-northeast-2 리전의 Ubuntu 20.04 AMI
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&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="c1">&lt;/span> &lt;span class="nx">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&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="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">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;-EOF&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;Hello, World&amp;#34; &amp;gt; index.html
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> nohup busybox httpd -f -p 8080 &amp;amp;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="o">EOF&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">tags&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">Name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&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 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="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">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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="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">ingress&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;0.0.0.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="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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; tf plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Refreshing state... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b&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">Terraform used the selected providers to generate the following execution plan. Resource
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">actions are indicated with the following symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + create
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ~ update in-place
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># aws_instance.example will be updated in-place&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ~ resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;i-00229af6743e0ba4b&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&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 class="nv">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;sg-0908fdbf1d8c6156e&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span> -&amp;gt; &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># (31 unchanged attributes hidden)&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"># (8 unchanged blocks hidden)&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="c1"># aws_security_group.example will be created&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + resource &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Managed by Terraform&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">egress&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ingress&lt;/span> &lt;span class="o">=&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="s2">&amp;#34;0.0.0.0/0&amp;#34;&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 class="nv">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">ipv6_cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">prefix_list_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">self&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&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 class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">name_prefix&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">owner_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">revoke_rules_on_delete&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">tags_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> + &lt;span class="nv">vpc_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>known after apply&lt;span class="o">)&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">Plan: &lt;span class="m">1&lt;/span> to add, &lt;span class="m">1&lt;/span> to change, &lt;span class="m">0&lt;/span> to destroy.
&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>&lt;/span>&lt;span class="line">&lt;span class="cl">Note: You didn&lt;span class="s1">&amp;#39;t use the -out option to save this plan, so Terraform can&amp;#39;&lt;/span>t guarantee to take
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">exactly these actions &lt;span class="k">if&lt;/span> you run &lt;span class="s2">&amp;#34;terraform apply&amp;#34;&lt;/span> now.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>새로운 리소스(시큐리티 그룹)이 추가되고 시큐리티 그룹을 통해 외부에서의 접속을 허용하도록 EC2 인스턴스가 변경되는 것을 확인할 수 있습니다. 이제 코드를 실행합니다.&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_security_group.example: Creating...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_security_group.example: Creation &lt;span class="nb">complete&lt;/span> after 1s &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>sg-057069d47b3e7f650&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Modifying... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Modifications &lt;span class="nb">complete&lt;/span> after 2s &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b&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">Apply complete! Resources: &lt;span class="m">1&lt;/span> added, &lt;span class="m">1&lt;/span> changed, &lt;span class="m">0&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이제 다시 &lt;code>퍼블릭 IPv4 주소&lt;/code>, &lt;code>퍼블릭 IPv4 DNS&lt;/code>를 통해 8080번 포트로 접속하면 &lt;code>Hello, World&lt;/code>를 확인할 수 있습니다.&lt;/p>
&lt;p align="center">&lt;img src="./10.png">&lt;/p>
&lt;h2 id="리소스-삭제하기">리소스 삭제하기&lt;/h2>
&lt;p>Terraform 코드로 생성된 리소스는 &lt;code>destroy&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; tf destroy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_security_group.example: Refreshing state... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>sg-057069d47b3e7f650&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Refreshing state... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b&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">Terraform used the selected providers to generate the following execution plan. Resource
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">actions are indicated with the following symbols:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - destroy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Terraform will perform the following actions:
&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"># aws_instance.example will be destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">ami&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ami-0c6e5afdd23291f73&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;arn:aws:ec2:ap-northeast-2:246181472423:instance/i-00229af6743e0ba4b&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">associate_public_ip_address&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">availability_zone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ap-northeast-2c&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">cpu_core_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">1&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">cpu_threads_per_core&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">1&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">disable_api_stop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">disable_api_termination&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">ebs_optimized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">get_password_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">hibernation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;i-00229af6743e0ba4b&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">instance_initiated_shutdown_behavior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stop&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">instance_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;running&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">instance_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;t2.micro&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">ipv6_address_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">ipv6_addresses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">monitoring&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">placement_partition_number&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">primary_network_interface_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;eni-06474393b32e2d8fc&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">private_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ip-172-31-45-210.ap-northeast-2.compute.internal&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">private_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;172.31.45.210&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">public_dns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ec2-3-39-10-27.ap-northeast-2.compute.amazonaws.com&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">public_ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;3.39.10.27&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">secondary_private_ips&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">source_dest_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">subnet_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;subnet-03d036291a1b8a65a&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tags_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tenancy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;c765373c563b260626d113c4a56a46e8a8c5ca33&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">user_data_replace_on_change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">vpc_security_group_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;sg-057069d47b3e7f650&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - capacity_reservation_specification &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">capacity_reservation_preference&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;open&amp;#34;&lt;/span> -&amp;gt; null
&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"> - cpu_options &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">core_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">1&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">threads_per_core&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">1&lt;/span> -&amp;gt; null
&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"> - credit_specification &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">cpu_credits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;standard&amp;#34;&lt;/span> -&amp;gt; null
&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"> - enclave_options &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&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"> - maintenance_options &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">auto_recovery&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span> -&amp;gt; null
&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"> - metadata_options &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">http_endpoint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;enabled&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">http_protocol_ipv6&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;disabled&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">http_put_response_hop_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">1&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">http_tokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;optional&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">instance_metadata_tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;disabled&amp;#34;&lt;/span> -&amp;gt; null
&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"> - private_dns_name_options &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">enable_resource_name_dns_a_record&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">enable_resource_name_dns_aaaa_record&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">hostname_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ip-name&amp;#34;&lt;/span> -&amp;gt; null
&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"> - root_block_device &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">delete_on_termination&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">device_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/dev/sda1&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">encrypted&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">iops&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">100&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">throughput&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">0&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">volume_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;vol-0f74f2dbc1d7880c1&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">volume_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">volume_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;gp2&amp;#34;&lt;/span> -&amp;gt; null
&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 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="c1"># aws_security_group.example will be destroyed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - resource &lt;span class="s2">&amp;#34;aws_security_group&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;arn:aws:ec2:ap-northeast-2:246181472423:security-group/sg-057069d47b3e7f650&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Managed by Terraform&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">egress&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sg-057069d47b3e7f650&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">ingress&lt;/span> &lt;span class="o">=&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="s2">&amp;#34;0.0.0.0/0&amp;#34;&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 class="nv">description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">from_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">ipv6_cidr_blocks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">prefix_list_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tcp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">self&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">to_port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8080&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 class="o">]&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;terraform-example&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">owner_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;246181472423&amp;#34;&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">revoke_rules_on_delete&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">tags_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{}&lt;/span> -&amp;gt; null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - &lt;span class="nv">vpc_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;vpc-089687c07f7572271&amp;#34;&lt;/span> -&amp;gt; null
&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">Plan: &lt;span class="m">0&lt;/span> to add, &lt;span class="m">0&lt;/span> to change, &lt;span class="m">2&lt;/span> to destroy.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you really want to destroy all resources?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Terraform will destroy all your managed infrastructure, as shown above.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> There is no undo. Only &lt;span class="s1">&amp;#39;yes&amp;#39;&lt;/span> will be accepted to confirm.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Enter a value: yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Destroying... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Still destroying... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b, 10s elapsed&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Still destroying... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>i-00229af6743e0ba4b, 20s elapsed&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Destruction &lt;span class="nb">complete&lt;/span> after 30s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_security_group.example: Destroying... &lt;span class="o">[&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="o">=&lt;/span>sg-057069d47b3e7f650&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_security_group.example: Destruction &lt;span class="nb">complete&lt;/span> after 0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Destroy complete! Resources: &lt;span class="m">2&lt;/span> destroyed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>만약 특정한 리소스만 삭제하고 싶다면 &lt;code>tf destroy -target {리소스 타입}.{리소스 이름}&lt;/code>을 입력하면 됩니다.
리소스 이름을 어떻게 확인하냐구요? 좋은 질문입니다! 리소스 이름은 &lt;code>tf state list&lt;/code>를 입력하면 확인할 수 있습니다!&lt;/p>&lt;/blockquote>
&lt;h2 id="iam-권한-제한하기">IAM 권한 제한하기&lt;/h2>
&lt;p>IAM에 딱 필요한 권한 허용하려면, 우선 어떤 권한이 필요한지 알아야 합니다. 그런데&amp;hellip;. 그걸 어떻게 알 수 있을까요??&lt;/p>
&lt;p>여기서 &lt;a href="https://github.com/iann0036/iamlive">iamlive&lt;/a>이 등장할 차례입니다!&lt;/p>
&lt;p>설명보다는 iamlive 사이트에서 제공하는 예시를 한 번 보시죠.&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/iann0036/iamlive/assets/iamlive.gif" />&lt;br />&lt;/p>
&lt;p>감이 오시나요?&lt;/p>
&lt;p>aws를 대상으로 뭔가 작업을 진행할 때, 그 작업에서 요구되는 IAM을 바로 확인할 수 있게 해줍니다!&lt;/p>
&lt;h3 id="iamlive-설정하기">iamlive 설정하기&lt;/h3>
&lt;p>iamlive는 homebrew등으로 직접 설치할 수도 있지만, 설치하지 않고 &lt;a href="https://meirg.co.il/2021/04/23/determining-aws-iam-policies-according-to-terraform-and-aws-cli/">docker를 이용하는 방법&lt;/a>도 있습니다.&lt;/p>
&lt;p>여기서는 docker를 통해 iamlive를 실행하겠습니다. 우선, docker로 해당 이미지를 실행합니다.&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; docker run &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -p 80:10080 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -p 443:10080 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --name iamlive-test &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -it unfor19/iamlive-docker &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --mode proxy &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --bind-addr 0.0.0.0:10080 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --force-wildcard-resource &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --output-file &lt;span class="s2">&amp;#34;/app/iamlive.log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>처음 실행 하는 거기 때문에 이미지를 다운로드 받고 실행이 됩니다. 실행이 됐을 때 별도로 메세지가 표시되지는 않기 때문에 에러 없이 대기 상태에 있다면 정상적으로 실행된 것입니다.&lt;/p>
&lt;blockquote>
&lt;p>만약 iamlive 컨테이너를 실행 중에 ctrl+c를 눌러서 종료하면, 다음에 실행하고 싶을 때 &lt;code>docker start iamlive-test&lt;/code>를 입력하면 됩니다. 그리고 &lt;code>docker attach iamlive-test&lt;/code>를 입력하면 실행 중인 컨테이너에 접속할 수 있습니다.&lt;/p>&lt;/blockquote>
&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; docker cp iamlive-test:/home/appuser/.iamlive ~/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>proxy 모드로 실행되는 iamlive에 접속하기 위한 키 파일을 프로필 루트 디렉토리로 복사했습니다. 다음 명령을 통해서 제대로 복사되었는지 확인할 수 있습니다.&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; ls ./.iamlive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ca.key ca.pem
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="iamlive을-통해-권한-확인하기">iamlive을 통해 권한 확인하기&lt;/h3>
&lt;p>원문에서 권장하는 방법은 권한이 하나도 없는 사용자를 만들고, 계속 명령어를 실행하면서 꼭 필요한 권한만 하나씩 복사하는 방식입니다. 그런데 이렇게 하면 필요한 권한이 많은 수록 여러번 명령을 반복적으로 실행하면서 권한을 하나씩 확인하고, 하나씩 추가해야 합니다. 꽤나 번거로운 작업이죠. 그래서 저는 그냥 어드민 권한이 있는 계정으로 테스트해서 전체 권한을 확인하고, 필요한 권한을 한 번에 할당하는 방식으로 진행하겠습니다.&lt;/p>
&lt;p>앞서 작성한 코드를 대상으로 &lt;code>terraform apply&lt;/code>를 실행하여 인프라를 배포합니다. 그리고 iamlive가 실행 중인 터미널을 잘 살펴보세요. 그러면 뭐가 막 지나가는 데 마지막 줄이 핵심입니다. 마지막 줄에는 다음과 같은 권한이 표시됩니다.&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Version&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;2012-10-17&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Statement&amp;#34;&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Effect&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;Allow&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Action&amp;#34;&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;sts:GetCallerIdentity&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:CreateSecurityGroup&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeSecurityGroups&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeNetworkInterfaces&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DeleteSecurityGroup&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:RevokeSecurityGroupEgress&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:AuthorizeSecurityGroupIngress&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:RunInstances&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstances&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstanceTypes&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeTags&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeVpcs&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstanceAttribute&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeVolumes&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstanceCreditSpecifications&amp;#34;&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 class="s2">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;*&amp;#34;&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 class="o">]&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>즉, 현재까지 작성된 terraform 코드를 실행해서 인프라를 구성하기 위해서는 이런 권한이 모두 필요하다는 거죠! 그럼 &lt;code>tf destroy&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="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Version&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;2012-10-17&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Statement&amp;#34;&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>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Effect&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;Allow&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Action&amp;#34;&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;sts:GetCallerIdentity&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:CreateSecurityGroup&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeSecurityGroups&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeNetworkInterfaces&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DeleteSecurityGroup&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:RevokeSecurityGroupEgress&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:AuthorizeSecurityGroupIngress&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:RunInstances&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstances&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstanceTypes&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeTags&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeVpcs&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstanceAttribute&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeVolumes&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:DescribeInstanceCreditSpecifications&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:ModifyInstanceAttribute&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ec2:TerminateInstances&amp;#34;&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 class="s2">&amp;#34;Resource&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;*&amp;#34;&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 class="o">]&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;blockquote>
&lt;p>이렇게 이쁘게 표시되지는 않습니다만&amp;hellip; &lt;a href="https://jsonformatter.org/json-pretty-print">JSON Formatter&lt;/a>등의 사이트를 통해 이쁘게 변환하시면 됩니다!&lt;/p>&lt;/blockquote>
&lt;p>네, 그럼 새로운 사용자를 만들어서 이 권한을 부여하겠습니다. 앞서 계정을 생성했던 것과 동일한 절차로 사용자 계정을 새로 생성합니다. 사용자 이름과 그룹 이름은 &lt;code>terraform-user&lt;/code>로 설정하고, &lt;code>그룹에는 아무런 권한을 주지 않습니다!&lt;/code>&lt;/p>
&lt;p>&lt;code>다시 한번 말씀드리면 그룹에는 아무런 권한을 주지 마세요!&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>혹시 잊으실까봐 그러는데, 액세스 키와 비밀 액세스 키는 따로 저장해두셔야 합니다.&lt;/p>&lt;/blockquote>
&lt;p>자 그럼 그룹 설정에서 &lt;code>권한&lt;/code>탭을 확인합니다. 현재 아무런 권한이 없는데요, 여기서 &lt;code>권한 추가&lt;/code> -&amp;gt; &lt;code>인라인 정책 생성&lt;/code>을 선택합니다.&lt;/p>
&lt;p align="center">&lt;img src="./11.png">&lt;/p>
&lt;p>그리고 &lt;code>권한 지정&lt;/code> 페이지에서 &lt;code>JSON&lt;/code>탭을 선택하고, 앞서 iamlive에서 확인한 권한을 복사해서 붙여넣습니다.&lt;/p>
&lt;p align="center">&lt;img src="./12.png">&lt;/p>
&lt;p>그리고 &lt;code>다음&lt;/code>을 선택하고,&lt;/p>
&lt;p align="center">&lt;img src="./13.png">&lt;/p>
&lt;p>&lt;code>정책 이름&lt;/code>을 &lt;code>terraform-user&lt;/code>로 설정하고, &lt;code>정책 생성&lt;/code>을 선택합니다. &lt;code>정책 생성&lt;/code> 또는 &lt;code>변경 사항 저장&lt;/code>(수정할 때)을 꼭 눌러주셔야만 반영됩니다.&lt;/p>
&lt;p>자 그러면 새로 만든 유저로 동일한 코드를 실행해볼까요?&lt;/p>
&lt;p>설정 파일(~/.zshrc)을 열어서 다음 내용을 새 유저의 액세스 키와 비밀 액세스 키로 변경합니다.&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"># 기존 유저 액세스 키&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># export AWS_ACCESS_KEY_ID=...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># export AWS_SECRET_ACCESS_KEY=...&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="nb">export&lt;/span> &lt;span class="nv">AWS_ACCESS_KEY_ID&lt;/span>&lt;span class="o">=&lt;/span>...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">AWS_SECRET_ACCESS_KEY&lt;/span>&lt;span class="o">=&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="nb">source&lt;/span> ~/.zshrc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>자 그리고 다시 apply 해봅시다!&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; tf apply
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aws_instance.example: Creating...
&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: creating EC2 Instance: UnauthorizedOperation: You are not authorized to perform this operation. User: arn:aws:iam::246181472423:user/terraform-user is not authorized to perform: ec2:CreateTags on resource: arn:aws:ec2:ap-northeast-2:246181472423:instance/* because no identity-based policy allows the ec2:CreateTags action. Encoded authorization failure message: -Ebof-TaMnw5iPJ_dVKP00sOBTScilmv_M3-TgALYm6r48Fi4oAIxOOMWghh0LZkPXrH1yqS5XIR9dsIgCLrHl2lua-CgXDXXWc0uq-QBmKupfCnhtGx2Sdu0-IqDwlImA5bmNOIlktmOj0WUUak8G4OtphB2zL0Yi7WnfDAruVd_CHJdYrkTEIW1are7RDvgX753V5h96s9_8zn7A8KVR479FT5qLrMy3lFAN1sfauVatYkbl8EztWa9qBC7zQ7goAq682lc-A0IbGrgQ3whJMqGaLwvK3Q5kL7QyvkJ-VXe5dq58YpZnMlvsc5uH76QVKiie_tdjMy28xpQ2EmRI3nqa9-1uvGIO6-1MKhwBbAeHQdz7kgmcp2aTI2BjBurhyDxQKWUKJqQCrY7-FTx4d2xdxyBaDM-LCujL2O-oFx2NBNYYBsRKO1E22gxcSakpwNqjg8omJ8-7X-8JhBGebfT6WM4ociL1T8OxIr6JG8dEnJWRzma4feKGZVCmkLxJbHPQW0IPF3FGtGbTFyir9YALbQDPX9yUwz4_w09slZdG7y-8aWBrlrCHccmcOmm8hpsthiNJbgtjTPxjZdHNWglMiUJ8PmiazOK04QcBLv5N71icCWdogluVBsCxmehFnPNWYq3BzWSdEhLtdQIK5sokmMEY2KFsJQ32cnBg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ status code: 403, request id: 88a0f4a9-c313-47b6-9677-7862747f9fb9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ with aws_instance.example,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ on main.tf line 7, in resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 7: resource &lt;span class="s2">&amp;#34;aws_instance&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;example&amp;#34;&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그런데 오류가 발생하네요..! iamlive가 &lt;code>ec2:CreateTags&lt;/code> 권한을 제대로 캐치를 하지 못해서 그렇습니다. 이 권한을 계정에 추가해주고 다시 확인해보면 생성과 삭제 모두 문제 없이 진행되는 걸 확인할 수 있습니다. 물론 저 권한을 하나씩 확인하면서 추가했다면 꽤나 복잡했겠죠? 그러다가 &lt;code>아 ** 안해&lt;/code>하게 됩니다. 하하하하하&amp;hellip;.개발이 다 그렇죠 뭐&amp;hellip;&lt;/p>
&lt;h2 id="참고자료">참고자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/iann0036/iamlive">https://github.com/iann0036/iamlive&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://meirg.co.il/2021/04/23/determining-aws-iam-policies-according-to-terraform-and-aws-cli/">Determining AWS IAM Policies According To Terraform And AWS CLI&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.terraformupandrunning.com/">Terraform Up and Running, 3rd Edition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="license">License&lt;/h1>
&lt;p>&lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/kr/">저작자표시-비영리-변경금지 2.0 대한민국 (CC BY-NC-ND 2.0 KR)&lt;/a>&lt;/p></content></item><item><title>Github Copilot First Look - 첫 인상과 React 개발 일주일 사용후기</title><link>https://netscout.github.io/posts/github-copilot-first-look-%EC%B2%AB-%EC%9D%B8%EC%83%81%EA%B3%BC-react-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A3%BC%EC%9D%BC-%EC%82%AC%EC%9A%A9%ED%9B%84%EA%B8%B0/</link><pubDate>Mon, 03 Oct 2022 09:52:29 +0900</pubDate><guid>https://netscout.github.io/posts/github-copilot-first-look-%EC%B2%AB-%EC%9D%B8%EC%83%81%EA%B3%BC-react-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A3%BC%EC%9D%BC-%EC%82%AC%EC%9A%A9%ED%9B%84%EA%B8%B0/</guid><description>지난 6월에 Github에서 Copliot을 정식으로 출시하여 이제 일반 사용자들도 사용해볼 수 있게 되었습니다.(출시한지가 언젠데 이제서야&amp;hellip;😞) 하도 주변에서 copliot에 대한 찬사를 하고, 심지어 이제 AI가 코딩을 더 잘한다는 소리까지 듣게 되니 좀 많이 …</description><content>&lt;p>지난 6월에 Github에서 Copliot을 정식으로 출시하여 이제 일반 사용자들도 사용해볼 수 있게 되었습니다.(출시한지가 언젠데 이제서야&amp;hellip;😞) 하도 주변에서 copliot에 대한 찬사를 하고, 심지어 이제 AI가 코딩을 더 잘한다는 소리까지 듣게 되니 좀 많이 궁금해지더군요! 하지만 궁금함이 귀찮음을 이기지 못하던 차에 한 뉴스를 접하게 됐습니다. AI가 그린 그림이 한 공모전에서 1등을 했다는 뉴스였는데요. 그림 분야에서 AI의 위협이 무시하지 못할 수준이라는 건 알았지만, &amp;lsquo;copilot이 과연 개발자의 일을 뺏아갈 수 있을까?&amp;rsquo; 하는 의문이 들었습니다. 드디어 호기심이 귀찮음을 뛰어넘을 수 있는 기회가 왔기에 한 번 찍먹 해보기로 했습니다. 근데 찍먹해보기 전에 방금 전해드린 AI 뉴스부터 한 번 보고갈까요?&lt;/p>
&lt;h2 id="정말-ai가-내-직업을-뺏을까">정말 AI가 내 직업을 뺏을까?&lt;/h2>
&lt;p>AI의 발전은 갈수록 그 속도가 더 빨라지고 있습니다. 최근에 미국에서는 AI가 그린 그림이 공모전 1등을 해서 논란이 일었습니다. 보도에 따르면 출품 당시 &amp;lsquo;미드 저니&amp;rsquo;라는 AI를 통해 그린 것임을 미리 밝혔지만, 일부 심사위원은 AI가 그린 그림인지 인지하지 못할 정도였다고 합니다. 어느 정도 였길래 1등을 먹었을까요?&lt;/p>
&lt;p align="center">&lt;img src="./115301737.1.jpg">&lt;/p>
(출처: 동아일보, https://www.donga.com/news/article/all/20220905/115301579/1)
&lt;br />&lt;br />
확실히 이 정도면 그림을 잘 모르는 제가 봐도 "와 쩐다" 싶은 느낌이 들긴합니다. 그리고 이런 논란이 있기 한참 전 부터 각종 커뮤니티에서는 키워드만 입력해주면 일러스트나 캐릭터를 그려주는 사이트에 대한 이야기가 많이 올라오곤 했습니다. 생각난 김에 저도 [DALL·E2](https://labs.openai.com/) 로 그림을 하나 그려보기로 했습니다.
&lt;br />&lt;br />
&lt;p align="center">&lt;img src="./dalle2.png">&lt;/p>
&lt;p>&lt;strong>korean programmer&lt;/strong> 라고 치자 제법 그럴듯한 이미지들이 몇 초만에 생성됩니다. 세세하게 살펴보면 얼굴의 눈가 쪽과 손가락이 엉성한 느낌이 들긴 하지만, 만약 제가 그림을 그리는 입장이라면 좀 민감하게 받아들일 것 같습니다.🥺&lt;/p>
&lt;p align="center">
&lt;img src="./183a731e20b3915fe_AdobeExpress.gif" width="300">&lt;br />
이럴 수가... AI 네 이놈...
&lt;/p>
&lt;p>그런데 말입니다&amp;hellip;.&lt;/p>
&lt;p>과연 개발자들은 안전한 걸까요? 아래 영상을 보면 그런 생각이 들기도 합니다&amp;hellip;&lt;/p>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=R-lIeSR8-hE">코딩접어야겠습니다 만원짜리 AI가 더 코딩 잘함&lt;/a>&lt;/p>
&lt;h2 id="그래서-직접-테스트를-해봤습니다">그래서 직접 테스트를 해봤습니다&lt;/h2>
&lt;p>남이 만들어 놓은 영상만 보고 판단하는 건 현명하지 못한 생각이겠죠? 그래서 직접 테스트를 해보기로 하고 copilot을 신청하고 환경을 세팅했습니다.&lt;/p>
&lt;p>아주 간단한 것 부터 테스트를 시작했습니다.🧐&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-29-32.gif">&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="kr">const&lt;/span> &lt;span class="nx">caculateSum&lt;/span> &lt;span class="o">=&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">=&amp;gt;&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;/code>&lt;/pre>&lt;/div>&lt;p>조금 더 복잡한 코드를 요구해볼까요?&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-31-32.gif">&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="kr">const&lt;/span> &lt;span class="nx">reverseString&lt;/span> &lt;span class="o">=&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">=&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">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;/code>&lt;/pre>&lt;/div>&lt;p align="center">&lt;img src="./그럴싸한데.jpg">&lt;br />호오... 그럴싸 한데??&lt;/p>
&lt;p>좀 더 복잡한 코드를 테스트 해보죠!&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-35-40.gif">&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * implement queue using two stacks
&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">class&lt;/span> &lt;span class="nx">Queue&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">constructor&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack1&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack2&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">item&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">item&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">dequeue&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="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack2&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">0&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">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack1&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="mi">0&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pop&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="k">return&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stack2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pop&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="kr">const&lt;/span> &lt;span class="nx">queue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Queue&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">enqueue&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="nx">queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">enqueue&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">queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">enqueue&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="nx">queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dequeue&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="nx">queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dequeue&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="nx">queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dequeue&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>내가 원하는 코드가 나오지 않을 때는 &lt;strong>Alt + &amp;ldquo;[&amp;rdquo; or &amp;ldquo;]&amp;rdquo;&lt;/strong> 맥에서는 &lt;strong>option + &amp;ldquo;[&amp;rdquo; or &amp;ldquo;]&amp;rdquo;&lt;/strong> 를 통해서 제안되는 코드를 변경할 수 있습니다. 그리고 제안을 받고 싶을 때는 &lt;strong>Alt + &amp;ldquo;\&amp;rdquo;&lt;/strong> 를 누릅니다.&lt;/p>&lt;/blockquote>
&lt;p>확실히 기대했던 것 보다는 훨씬 똑똑합니다.&lt;/p>
&lt;p align="center">&lt;img src="./lee-duk-hwa-satisfaction.gif" width="300">&lt;br />조아.. 아주 조아!!!&lt;/p>
&lt;p>&lt;strong>와&amp;hellip; 이거 쩌는데?&lt;/strong> 하는 생각과 함께 계속 테스트를 이어나갔습니다. 이번에는 사용자에게 숫자를 입력받아서 소수인지 출력하는 코드를 요청해보기로 했습니다.&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-45-00.gif">&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="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">isPrime&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">{&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">num&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&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="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="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">2&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="nx">num&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">num&lt;/span> &lt;span class="o">%&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="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 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>&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="nx">isPrime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// true
&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="nx">isPrime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// true
&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="nx">isPrime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// false
&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="nx">isPrime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// true
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p align="center">&lt;img src="./270D203E578F3B232B.jpg" width="300">&lt;br />이거 아닌데...&lt;/p>
&lt;p>코드를 추천해주기는 하지만 제가 원하는 코드는 아닙니다. 사용자가 숫자를 입력할 수 있는 부분이 없거든요. 다시!&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-46-41.gif">&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * 사용자에게 &amp;#34;숫자를 입력하세요: &amp;#34; 라는 메시지를 출력하고
&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">var&lt;/span> &lt;span class="nx">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">prompt&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="kd">var&lt;/span> &lt;span class="nx">isPrime&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">var&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&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="nx">num&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">num&lt;/span> &lt;span class="o">%&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">isPrime&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="k">break&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">isPrime&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="nx">num&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>&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가 아닌 브라우저에서 동작하는 코드죠. 물론 &lt;strong>prompt&lt;/strong> 함수를 추가해서 동작하게 만들 수도 있겠지만요. 다시!&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-52-33.gif">&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * node.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * &amp;#34;숫자를 입력하세요:&amp;#34; 숫자 입력받아서 소수 판별
&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">var&lt;/span> &lt;span class="nx">readline&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;readline&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">var&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="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">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="o">:&lt;/span> &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>&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">rl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">question&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="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">answer&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="kd">var&lt;/span> &lt;span class="nx">num&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">answer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">result&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="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kd">var&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&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="nx">num&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">num&lt;/span> &lt;span class="o">%&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="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="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="k">break&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">result&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;소수입니다.&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>&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">rl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">close&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>node.js&lt;/strong> 키워드를 추가하자 이제 &lt;strong>node.js&lt;/strong> 에서 실행할 수 있는 코드를 제시해줍니다. 오오오&amp;hellip; 근데 계속 탭키 누르면서 다음줄로 가는 것도 좀 귀찮긴 하죠. 그래서 이번엔 제가 어떤 코드를 원하는지 살짝 힌트를 주기로 했습니다.&lt;/p>
&lt;p align="center">&lt;img src="./Oct-03-2022%2008-54-23.gif">&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="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * node.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * &amp;#34;숫자를 입력하세요:&amp;#34; 숫자 입력받아서 소수 판별
&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">main&lt;/span> &lt;span class="o">=&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="kr">const&lt;/span> &lt;span class="nx">readline&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;readline&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">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="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">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="o">:&lt;/span> &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>&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">rl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">question&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="p">(&lt;/span>&lt;span class="nx">answer&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="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="nx">isPrime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">answer&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">rl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">close&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="kr">const&lt;/span> &lt;span class="nx">isPrime&lt;/span> &lt;span class="o">=&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="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">num&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &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="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">2&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="nx">num&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">num&lt;/span> &lt;span class="o">%&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="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="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>&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">main&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>제가 코드를 쭉 늘어놓지 말고 함수로 제안해달라고 힌트를 주자 거기에 맞춰서 코드를 제안해줍니다. 오호&amp;hellip;😆&lt;/p>
&lt;p align="center">&lt;img src="./20190424221842_alwsmiwa.jpg" width="300">&lt;br />아 이제 대충 사용법은 마스터 한듯 ㅋ&lt;/p>
&lt;h2 id="이제는-실전이다-react로-일주일-써본-후기">이제는 실전이다. React로 일주일 써본 후기&lt;/h2>
&lt;p>Github Copliot의 첫 인상은 똑똑하긴 한데, 알아듣게 설명을 해줘야만 일을 잘하는 친구였습니다. 그래서 일단 React 작업을 하면서 능력을 확인해보기로 했죠.&lt;/p>
&lt;h3 id="첫-번째-케이스--good">첫 번째 케이스 : Good!&lt;/h3>
&lt;p>다음 영상은 Redux 스토어의 reducer에 코드를 작성하는 장면입니다.&lt;/p>
&lt;p align="center">&lt;img src="./Oct-06-2022 13-48-17.gif">&lt;/p>
&lt;p>상단에 &lt;strong>Todo&lt;/strong> 타입이 명시되어 있고, 상태 값인 &lt;strong>todos&lt;/strong>는 &lt;strong>Todo[]&lt;/strong> 타입입니다. 그래서 &lt;strong>todos&lt;/strong>에 새 항목을 추가하는 코드를 작성하자 copilot이 눈치를 까고 타입에 맞는 적절한 값을 추천해줍니다.&lt;/p>
&lt;h3 id="두-번째-케이스--good">두 번째 케이스 : Good!&lt;/h3>
&lt;p>다음은 할 일 목록을 표시하고 그 아래에 새 할일 입력 칸을 추가하는 장면입니다.&lt;/p>
&lt;p align="center">&lt;img src="./Oct-06-2022 13-48-55.gif">&lt;/p>
&lt;p>역시 copliot이 눈치까고 &lt;strong>useState&lt;/strong>로 선언된 &lt;strong>todo&lt;/strong> 값을 이용해서 새 할 일 제목을 입력받을 수 있는 &lt;strong>input&lt;/strong> 태크를 완성해줍니다.&lt;/p>
&lt;h3 id="세-번째-케이스--excellent">세 번째 케이스 : Excellent!&lt;/h3>
&lt;p>마지막 장면은 커스텀 hook을 만들고 사용하는 상황입니다.&lt;/p>
&lt;p align="center">&lt;img src="./Oct-06-2022 13-49-34.gif">&lt;/p>
&lt;p>우선 훅을 작성하고 마지막에 리턴 타입을 추가하는데요, 100% 정확하지는 않지만 리턴하는 항목의 타입을 유추해서 hook의 리턴 타입을 추천해줍니다.&lt;/p>
&lt;p>그리고 이 훅을 사용하는 장면입니다.&lt;/p>
&lt;p align="center">&lt;img src="./Oct-06-2022 13-49-59.gif">&lt;/p>
&lt;p>아무런 힌트를 주지 않았지만 당연히 hook을 사용하겠지? 하면서 아까 만든 hook을 사용하는 코드를 추천해줍니다.&lt;/p>
&lt;p align="center">&lt;img src="./1577863941_7110_Screenshot_20200101_163200_NAVER.jpg" width="300">&lt;br />오 이건 기대 이상인 듯&lt;/p>
&lt;h3 id="네-번째-케이스--what-the">네 번째 케이스 : What the&amp;hellip;?&lt;/h3>
&lt;p>뭘 하려고 하는 건지 나도 모르고 AI도 모를 땐 아무말 대잔치가 벌어집니다.&lt;/p>
&lt;p align="center">&lt;img src="./1665184864717.gif">&lt;/p>
&lt;p>이럴 땐 copilot 에게 물어보기 전에 어떤 작업을 할지 생각을 먼저 해야 겠죠&amp;hellip;&lt;/p>
&lt;p align="center">&lt;img src="./images.jpg">&lt;br />코딩하기 전에 생각했나요?&lt;/p>
&lt;h2 id="결론--아는-만큼-쓸-수-있다">결론 : 아는 만큼 쓸 수 있다.&lt;/h2>
&lt;p>아주 짧은 시간 copilot을 써보고 든 생각은 &lt;strong>아는 만큼 쓸 수 있다&lt;/strong>는 거였습니다. 그 이유는 다음과 같습니다.&lt;/p>
&lt;h3 id="1-ai도-우문현답은-못한다">1. AI도 우문현답은 못한다.&lt;/h3>
&lt;p>| 우문현답(愚問賢答): 어리석은 질문에 현명한 대답&lt;/p>
&lt;p>이건 AI가 아닌 사람을 놓고 생각해도 마찬가지인데요, 내가 뭘 모르는지 정확하게 알아야 정확한 답변을 얻을 수 있습니다.&lt;/p>
&lt;p>사람은 &amp;lsquo;아 이 사람이 이걸 물어보는 건가?&amp;lsquo;하고 예측을 할 수 도 있고, 질문과 답변을 이어가면서 좀 더 정확하게 알려줄 수도 있죠.(물론 아 말해줘도 못 알아먹겠구나 싶은 느낌도 받을 수 있습니다.) 근데 AI라고 별 수 있을까요? &lt;strong>내가 뭘 짜려고 하는지 최소한의 구조를 알고 있고 어떻게 진행되어야 하는 흐름을 알아야 적합한 코드를 제안받을 수 있습니다.&lt;/strong>&lt;/p>
&lt;p>생각해보면 이런 경우가 아닐까요?&lt;/p>
&lt;ul>
&lt;li>잘 기억은 안 나는데 그거 어떻게 하더라?(ex: node.js에서 readline)&lt;/li>
&lt;li>새로운 SDK나 API 사용법에 대해서 힌트를 얻고 싶을 때(ex: dynamodb 쿼리 어떻게 날렸더라?)&lt;/li>
&lt;li>알고리즘 구현 방법을 알고 싶을 때(ex: 소수 어떻게 구하더라?)&lt;/li>
&lt;/ul>
&lt;p>사실 서두에 소개해드렸던 &lt;a href="https://www.youtube.com/watch?v=R-lIeSR8-hE">코딩접어야겠습니다 만원짜리 AI가 더 코딩 잘함&lt;/a> 영상을 한 번 더 자세히 보시면, 이건 AI 도움 없이도 만들 줄 아는 사람이 이런 저런 테스트를 거쳐서 copilot에게 일을 잘 시키는 방법을 찾아낸 겁니다. 유투브니까 제목이 자극적인건 이해 해야겠죠. 🤔&lt;/p>
&lt;h3 id="2-제안-받은-코드가-맞는-코드인지-확인할-수-있어야-한다">2. 제안 받은 코드가 맞는 코드인지 확인할 수 있어야 한다.&lt;/h3>
&lt;p>역시 AI를 빼놓고 생각해도 당연한 이야기 입니다. Stackoverflow에서 검색할 때도 결국은 이게 무슨 코드인지 이해할 수 있어야 제대로 활용할 수 있습니다. Stackoverflow는 다른 사용자들이 답변에 대해서 검증도 해주고 다양한 의견을 남겨주기 때문에 답변에 대한 크로스 체크가 들어가는 경우가 많지만, copliot은 여러분이 직접 체크해야만 합니다. github 역시 공식 문서에서 &lt;a href="https://docs.github.com/en/copilot/overview-of-github-copilot/about-github-copilot#using-github-copilot">이 점을 분명하게 해두고 있습니다.&lt;/a>&lt;/p>
&lt;h3 id="3-아예-제안할-방법이-없는-경우도-있다">3. 아예 제안할 방법이 없는 경우도 있다.&lt;/h3>
&lt;p>공개된 코드를 기반으로 학습하고 코드를 제안하기 때문에, 아직 공개되지 않은 코드나 공개된지 얼마 되지 않은 코드는 copliot이 제안할 방법이 없습니다. 막 SDK 새 버전이 나와서 적용해야 할 때, 아마도 copliot은 계속 옛날 버전의 코드를 제안할 겁니다. 자기도 본 적이 없으니까요 😱&lt;/p>
&lt;p>그리고 이 외에도 여러가지 문제점이 있긴 합니다. 관심이 있는 분들은 이 글 &lt;a href="https://www.fast.ai/posts/2021-07-19-copilot.html">Is GitHub Copilot a blessing, or a curse?&lt;/a> 을 참고하세요.&lt;/p>
&lt;h2 id="마치며--1-개발자-1-ai-시대의-예감">마치며 : 1 개발자 1 AI 시대의 예감&lt;/h2>
&lt;p>아직은 장점도 단점도 분명합니다. 개발자의 직업을 위협할 정도가 되려면 한참 멀었습니다. 그리고 자동완성에 너무 의존하게 되면, 개발자 스스로 생각하는 능력이 떨어지지 않을까 하는 걱정이 들기도 했습니다.&lt;/p>
&lt;p>하지만 이제 사람들은 AI와 공존하며 살아가야 하는 방법을 찾아야 하는 시대가 된 것 같습니다. 아마도 곧 AI의 도움을 받아 코딩하는 게 일상적인 일이 되겠죠. 그렇다면 AI에게 허드렛일을 맡겨 빨리 처리하고 개발자는 좀 더 고차원적인 고민을 하는 연습을 해야 할 것 같습니다.&lt;/p>
&lt;p>copilot도 계속 발전하고 다른 AI 기술도 등장해서 경쟁도 이루어지겠죠? 그에 맞춰 개발자의 역할도 계속 변화에 적응해야 한다고 생각합니다. 더 발전할 copilot을 기대하며 이만 마치겠습니다.&lt;/p>
&lt;h2 id="참고-자료">참고 자료&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://realpython.com/github-copilot-python/">GitHub Copilot: Fly With Python at the Speed of Thought&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.fast.ai/posts/2021-07-19-copilot.html">Is GitHub Copilot a blessing, or a curse?&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://yozm.wishket.com/magazine/detail/1634/">&amp;ldquo;세상 참 좋아졌네&amp;rdquo;&amp;hellip;깃허브 코파일럿 리뷰&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.donga.com/news/article/all/20220905/115301579/1">美 미술 공모전서 AI가 그린 그림이 1등상 받아 논란 “예술의 죽음” vs “AI도 사람이 작동”&lt;/a>&lt;/li>
&lt;/ul></content></item></channel></rss>