<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>김신건의 로그</title><description>개발자 김신건(koa)의 개발 블로그, Python, Django, Astro, 알고리즘(백준/PS), 웹 개발, 데이터베이스를 기록하는 글·노트·위키.</description><link>https://shinkeonkim.com/</link><language>ko-KR</language><item><title>파일 디스크립터부터 소켓까지, 하나로 이어지는 흐름</title><link>https://shinkeonkim.com/posts/2026-07-15-file-descriptor-to-socket/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-07-15-file-descriptor-to-socket/</guid><description>파일도 소켓도 결국 fd 하나로 다뤄진다. open, read, write, close, socket, send 까지 하나의 흐름으로 이어보는 Unix I/O 의 통합 추상화.</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;시작-의문&quot;&gt;시작 의문&lt;/h2&gt;
&lt;p&gt;Unix 계열 (Linux, macOS, BSD) 에서 파일도, 소켓도, 파이프도, 심지어 터미널도 같은 함수로 읽는다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, buf, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf));&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 파일이든 소켓이든 이걸로 다 읽힘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fd&lt;/code&gt; 자리에 어떤 자원이 오든 동작한다. 처음 보면 좀 신기하다. 파일은 디스크에 있고 소켓은 네트워크를 지나오는 완전히 다른 물건인데 왜 같은 API 가 통할까.&lt;/p&gt;
&lt;p&gt;답은 세 계층을 순서대로 이해하면 자연스럽게 풀린다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;파일 디스크립터 (fd)&lt;/strong&gt;, 사용자 공간이 커널 자원을 가리키는 정수 번호표.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저수준 파일 I/O&lt;/strong&gt;, fd 로 커널에 요청하는 시스템 콜 (&lt;code&gt;open&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;lseek&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;소켓&lt;/strong&gt;, 네트워크 연결이지만 결국 fd 하나로 감싸져 있어 위의 API 가 그대로 통하는 것.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 순서로 하나씩 풀어보자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-파일-디스크립터-정수-하나로-자원을-가리키다&quot;&gt;1. 파일 디스크립터, 정수 하나로 자원을 가리키다&lt;/h2&gt;
&lt;p&gt;프로세스마다 자기만의 “표” 를 가지고 있다. &lt;strong&gt;파일 디스크립터 테이블 (file descriptor table)&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;각 슬롯은 정수 인덱스 (0, 1, 2, 3, …) 이고, 커널이 관리하는 자원 (파일, 소켓, 파이프, …) 을 가리키는 포인터가 들어있다. 사용자 공간에서는 그 정수만 보인다.&lt;/p&gt;
&lt;p&gt;프로세스가 시작될 때 3개는 이미 열려 있다.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;fd&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;이름&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;기본 연결&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;0&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;stdin&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;키보드 (또는 리다이렉트된 입력)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;stdout&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;터미널 (또는 리다이렉트된 출력)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;stderr&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;터미널 (에러용)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;open()&lt;/code&gt; 을 호출하면 커널이 &lt;strong&gt;가장 작은 빈 슬롯&lt;/strong&gt; 을 골라 새 fd 를 할당하고 그 번호를 돌려준다. 그래서 첫 &lt;code&gt;open&lt;/code&gt; 은 보통 fd 3 을 준다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;애니메이션에서 왼쪽 상자가 프로세스의 fd 테이블, 오른쪽이 커널이 관리하는 자원이다. &lt;code&gt;open&lt;/code&gt; 이 호출되면 커널이 File Object 를 만들고 fd 3 슬롯에 그 참조를 꽂아준다. 사용자 코드는 그 정수 3 만 들고 다니면서 &lt;code&gt;read(3, ...)&lt;/code&gt; 같은 시스템 콜을 부른다.&lt;/p&gt;
&lt;h3 id=&quot;왜-그냥-포인터가-아니라-정수인가&quot;&gt;왜 그냥 포인터가 아니라 정수인가?&lt;/h3&gt;
&lt;p&gt;두 가지 이유가 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;격리&lt;/strong&gt;. 사용자 공간이 커널 포인터를 직접 만지면 위험하다. 잘못 건드리면 커널 메모리를 오염시킬 수 있다. 정수 인덱스는 그런 위험 없이 “너의 3번 슬롯” 을 안전하게 가리킨다. 커널은 시스템 콜이 들어올 때마다 fd 를 인덱스로 자기 테이블을 찾아본다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;fork/exec 상속과 리다이렉트&lt;/strong&gt;. 자식 프로세스는 부모의 fd 테이블을 복사받는다. 슬롯 번호까지 그대로. 이 규칙 덕에 셸의 &lt;code&gt;ls &gt; out.txt&lt;/code&gt; 가 우아하게 동작한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. 셸이 fork()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 자식이 fd 1 (stdout) 을 close()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. 자식이 open(&quot;out.txt&quot;, O_WRONLY|O_CREAT|O_TRUNC)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   → open 이 &quot;가장 작은 빈 슬롯&quot; 규칙으로 fd 1 을 할당&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. 자식이 exec(&quot;ls&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   → ls 는 자기가 stdout 에 쓴다고 생각하지만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     실제로는 out.txt 로 나간다&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ls&lt;/code&gt; 는 자기가 리다이렉트됐다는 걸 전혀 모른다. 파일에 쓴다는 것도, 원래 터미널에 나갈 데이터라는 것도 신경 안 쓴다. &lt;strong&gt;그냥 fd 1 에 write 할 뿐이다.&lt;/strong&gt; 이 무지 (無知) 가 곧 Unix 파이프라인의 재사용성이다.&lt;/p&gt;
&lt;h3 id=&quot;fd-는-프로세스-지역이다&quot;&gt;fd 는 프로세스 지역이다&lt;/h3&gt;
&lt;p&gt;fd 3 은 &lt;strong&gt;어느 프로세스의 fd 3&lt;/strong&gt; 이라는 문맥이 있어야 의미가 있다. 프로세스 A 의 fd 3 과 프로세스 B 의 fd 3 은 완전히 다른 자원이다.&lt;/p&gt;
&lt;p&gt;커널은 각 프로세스마다 별도의 fd 테이블을 유지한다 (&lt;code&gt;task_struct.files&lt;/code&gt; 안의 &lt;code&gt;fdtable&lt;/code&gt;). 그래서 &lt;code&gt;/proc/&amp;#x3C;pid&gt;/fd/&lt;/code&gt; 를 살펴보면 그 프로세스가 무엇을 열고 있는지 다 보인다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -l&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /proc/self/fd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwx------&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/pts/1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwx------&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/pts/1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwx------&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/pts/1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lr-x------&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; me&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /proc/12345/fd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-저수준-파일-io-다섯-개의-시스템-콜&quot;&gt;2. 저수준 파일 I/O, 다섯 개의 시스템 콜&lt;/h2&gt;
&lt;p&gt;fd 하나만 있으면 파일을 다룰 수 있는 시스템 콜은 사실상 다섯 개다. &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;lseek&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;open-fd-얻기&quot;&gt;open(), fd 얻기&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#x3C;fcntl.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/tmp/data.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, O_RDONLY);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (fd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    perror&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;성공: 새 fd (&gt;= 3 이면 사용자가 연 것)&lt;/li&gt;
&lt;li&gt;실패: -1, &lt;code&gt;errno&lt;/code&gt; 세팅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;플래그로 열기 모드를 지정한다.&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;플래그&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_RDONLY&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;읽기 전용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_WRONLY&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;쓰기 전용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_RDWR&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;읽기/쓰기&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_CREAT&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;없으면 생성 (mode 인자 필요)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_APPEND&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;쓰기가 항상 파일 끝에&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_TRUNC&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;열면서 파일 내용을 0 으로 잘라냄&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;O_NONBLOCK&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;논블로킹 모드 (뒤에서 다시 나온다)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;O_CREAT&lt;/code&gt; 를 쓸 땐 파일 권한을 명시해야 한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;out.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, O_WRONLY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; O_CREAT &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; O_TRUNC, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;644&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;read-write-실제-io&quot;&gt;read(), write(), 실제 IO&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ssize_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; read&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, buf, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// n &gt; 0: 읽은 바이트 수&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// n == 0: EOF (더 읽을 것 없음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// n &amp;#x3C; 0: 에러, errno 확인&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ssize_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; written &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;hello&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 자주 실수하는 함정 하나. &lt;strong&gt;read/write 는 요청한 만큼 다 처리한다는 보장이 없다.&lt;/strong&gt; 짧게 반환하는 게 오히려 흔하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파일: 대부분 요청한 만큼 오지만, 시그널이 끼어들면 짧게 반환할 수 있다.&lt;/li&gt;
&lt;li&gt;소켓/파이프: 자주 짧게 반환한다. TCP 는 스트림이라 “여기까지 왔음” 이 임의다.&lt;/li&gt;
&lt;li&gt;논블로킹 fd: 지금 당장 처리 가능한 만큼만.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 실무 코드는 loop 를 돌려야 한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ssize_t&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; write_all&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; fd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; buf;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    size_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; remaining &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; len;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (remaining &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        ssize_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, p, remaining);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (errno &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EINTR) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 시그널이 끼어들면 재시도&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        remaining &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; len;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;read&lt;/code&gt; 도 마찬가지 패턴이 필요하다.&lt;/p&gt;
&lt;h3 id=&quot;close-fd-반환&quot;&gt;close(), fd 반환&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fd 슬롯을 해제한다. 커널 안의 자원 (File Object) 은 참조 카운트가 0 이 되면 함께 해제된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;close&lt;/code&gt; 를 안 부르면 어떻게 될까? &lt;strong&gt;fd leak&lt;/strong&gt; 이다. 프로세스마다 열 수 있는 fd 개수에 상한이 있다 (&lt;code&gt;ulimit -n&lt;/code&gt;, 보통 1024 에서 65535 사이). 장시간 도는 서버가 매 요청마다 fd 를 열고 닫지 않으면 결국 &lt;code&gt;open&lt;/code&gt; 이 &lt;code&gt;EMFILE&lt;/code&gt; (Too many open files) 로 실패하기 시작한다. 파일뿐 아니라 소켓 leak 도 같은 이유로 서버를 죽인다.&lt;/p&gt;
&lt;h3 id=&quot;lseek-파일-커서&quot;&gt;lseek(), 파일 커서&lt;/h3&gt;
&lt;p&gt;파일은 “지금 어디까지 읽었는지” 를 나타내는 커서를 가진다. 커널이 File Object 안에 offset 을 유지한다. &lt;code&gt;read&lt;/code&gt; 하면 그만큼 앞으로 나가고, &lt;code&gt;write&lt;/code&gt; 하면 그만큼 앞으로 나가면서 덮어쓴다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lseek&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, SEEK_SET);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // 처음으로&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lseek&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, SEEK_END);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // 끝으로 (파일 크기 반환값으로 얻음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lseek&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fd, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, SEEK_CUR);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 현재 위치에서 100 앞으로&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;strong&gt;소켓/파이프에는 커서가 없다.&lt;/strong&gt; &lt;code&gt;lseek&lt;/code&gt; 를 소켓에 호출하면 &lt;code&gt;ESPIPE&lt;/code&gt; (Illegal seek) 로 실패한다. 파일이라는 “위치가 있는 자원” 에만 의미가 있는 연산.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;stdio-fopenfread-는-위의-wrapper-다&quot;&gt;stdio (fopen/fread/…) 는 위의 wrapper 다&lt;/h3&gt;
&lt;p&gt;C 표준 라이브러리의 &lt;code&gt;fopen&lt;/code&gt;, &lt;code&gt;fread&lt;/code&gt;, &lt;code&gt;fwrite&lt;/code&gt;, &lt;code&gt;fclose&lt;/code&gt; 는 위 저수준 API 위에 &lt;strong&gt;버퍼링&lt;/strong&gt; 을 얹은 것이다. &lt;code&gt;FILE*&lt;/code&gt; 는 실체가 이렇게 생겼다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FILE {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int fd;               // 안에 파일 디스크립터가 있음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    char *buffer;         // 사용자 공간 버퍼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    size_t buf_pos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    int mode;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;버퍼 크기만큼 시스템 콜을 모아서 한 번에 처리하므로 매번 &lt;code&gt;write&lt;/code&gt; 하는 것보다 빠르다. 필요할 때 &lt;code&gt;fileno(fp)&lt;/code&gt; 로 안의 fd 를 꺼낼 수도 있다.&lt;/p&gt;
&lt;p&gt;버퍼링의 함정은 &lt;strong&gt;flush 를 안 하면 데이터가 아직 커널로 안 갔을 수 있다&lt;/strong&gt; 는 점. &lt;code&gt;printf&lt;/code&gt; 뒤에 크래시가 나면 그 로그가 안 남기도 한다. &lt;code&gt;fflush(stdout)&lt;/code&gt; 또는 &lt;code&gt;setvbuf&lt;/code&gt; 로 버퍼 정책을 바꿀 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-소켓-결국-fd-다&quot;&gt;3. 소켓, 결국 fd 다&lt;/h2&gt;
&lt;p&gt;여기서 이 글의 핵심 반전이 나온다. 네트워크 소켓도 결국 fd 하나다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#x3C;sys/socket.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sfd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; socket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(AF_INET, SOCK_STREAM, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;socket()&lt;/code&gt; 은 새 파일 디스크립터를 반환한다. 커널이 내부적으로 &lt;code&gt;sock&lt;/code&gt; 구조체를 만들고, 프로세스 fd 테이블의 빈 슬롯에 그 참조를 꽂는다. 파일 열 때와 완전히 같은 패턴.&lt;/p&gt;
&lt;p&gt;인자는 세 개.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AF_INET&lt;/code&gt;: 주소 체계 (IPv4). IPv6 는 &lt;code&gt;AF_INET6&lt;/code&gt;, 로컬 유닉스 소켓은 &lt;code&gt;AF_UNIX&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SOCK_STREAM&lt;/code&gt;: 연결 기반 스트림 (TCP). 데이터그램은 &lt;code&gt;SOCK_DGRAM&lt;/code&gt; (UDP).&lt;/li&gt;
&lt;li&gt;프로토콜: 대부분 0 (기본값) 이면 알아서.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;클라이언트-흐름&quot;&gt;클라이언트 흐름&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sfd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; socket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(AF_INET, SOCK_STREAM, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sockaddr_in addr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .sin_family &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AF_INET,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .sin_port &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; htons&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;inet_pton&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(AF_INET, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;93.184.216.34&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;addr.sin_addr);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sfd, (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sockaddr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;addr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(addr));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 여기서부터 파일이랑 100% 동일한 인터페이스&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sfd, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;GET / HTTP/1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Host: example.com&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4096&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ssize_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; read&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sfd, buf, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sfd);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;connect&lt;/code&gt; 로 TCP 3-way handshake 가 끝나고 나면 그 뒤엔 그냥 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;/&lt;code&gt;close&lt;/code&gt;. 파일과 다른 API 를 배울 필요가 없다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;send&lt;/code&gt;/&lt;code&gt;recv&lt;/code&gt; 라는 소켓 전용 함수도 있지만, 인자에 플래그 하나 더 붙는 것 (&lt;code&gt;MSG_DONTWAIT&lt;/code&gt;, &lt;code&gt;MSG_NOSIGNAL&lt;/code&gt; 등) 말고는 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; 와 사실상 같은 함수다.&lt;/p&gt;
&lt;h3 id=&quot;서버-흐름&quot;&gt;서버 흐름&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; listener &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; socket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(AF_INET, SOCK_STREAM, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(listener, ...);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       // 로컬 주소:포트 잡기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(listener, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     // 백로그 큐 크기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; accept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(listener, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ← 새 fd 하나 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(client, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;HTTP/1.1 200 OK&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;hi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;22&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    close&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(client);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;accept()&lt;/code&gt; 가 새 클라이언트 연결마다 새 fd 를 하나씩 만들어준다. &lt;code&gt;listener&lt;/code&gt; fd 는 그대로 두고, 각 클라이언트는 별도의 fd 로 다룬다. 100 클라이언트가 동시에 붙어있으면 fd 가 101 개 열려 있는 상태 (listener 1 + 클라이언트 100).&lt;/p&gt;
&lt;p&gt;여기서 아까 얘기한 &lt;strong&gt;fd 상한&lt;/strong&gt; 이 서버의 동시 접속 상한을 결정한다. &lt;code&gt;ulimit -n 65535&lt;/code&gt; 같은 튜닝이 필요한 이유.&lt;/p&gt;
&lt;h3 id=&quot;왜-파일-api-가-소켓에-그대로-통하나&quot;&gt;왜 파일 API 가 소켓에 그대로 통하나?&lt;/h3&gt;
&lt;p&gt;커널이 fd 뒤의 자원을 &lt;strong&gt;다형적&lt;/strong&gt; 으로 다루기 때문이다. Linux 커널에서는 각 자원 (파일, 소켓, 파이프, …) 이 자기만의 함수 테이블을 갖는다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 개념적 스케치 (실제 Linux 커널의 struct file_operations)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; file_operations {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ssize_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;read)(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; file &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; __user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;loff_t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ssize_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;write)(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; file &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; __user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;loff_t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;release)(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inode &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; file &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // close&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;파일이면 ext4/xfs 의 read 함수, 소켓이면 소켓 서브시스템의 read 함수, 파이프면 파이프의 read 함수가 이 테이블에 꽂혀 있다. 시스템 콜 &lt;code&gt;read(fd, ...)&lt;/code&gt; 가 들어오면 커널은:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;현재 프로세스의 fd 테이블에서 fd 번호로 &lt;code&gt;struct file *&lt;/code&gt; 을 찾는다.&lt;/li&gt;
&lt;li&gt;그 &lt;code&gt;file-&gt;f_op-&gt;read&lt;/code&gt; 를 부른다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사용자 공간에서 봤을 때 인터페이스가 하나 (&lt;code&gt;read&lt;/code&gt;) 지만, 커널 내부에서는 자원마다 다른 구현이 호출된다. 객체지향의 다형성 (polymorphism) 이 C 로 구현된 셈. 이게 Unix 의 &lt;strong&gt;“Everything is a File”&lt;/strong&gt; 철학의 실체다.&lt;/p&gt;
&lt;p&gt;“모든 게 파일이다” 는 은유가 아니라 &lt;strong&gt;“모든 자원을 fd 로 접근한다, 그리고 fd 뒤에는 read/write/close 같은 공통 인터페이스를 구현한 커널 객체가 있다”&lt;/strong&gt; 는 뜻이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-통합의-보너스-select--poll--epoll&quot;&gt;4. 통합의 보너스, select / poll / epoll&lt;/h2&gt;
&lt;p&gt;fd 가 통합돼 있어서 얻는 아주 실용적인 보너스가 하나 있다. &lt;strong&gt;여러 종류의 자원을 한꺼번에 감시할 수 있다는 것.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;한 프로세스가 다음 세 개를 동시에 봐야 한다고 하자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;표준 입력에서 사용자 명령이 들어오는지 (fd 0)&lt;/li&gt;
&lt;li&gt;파일 하나가 갱신됐는지 (fd 3)&lt;/li&gt;
&lt;li&gt;소켓 하나에 응답이 도착했는지 (fd 4)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;원래 문제였다면 세 개 감시 API 를 따로 배웠어야 한다. 그런데 셋 다 fd 이므로 하나의 API 로 다룰 수 있다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#x3C;poll.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pollfd &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;fds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    { .fd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,       .events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; POLLIN },&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // stdin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    { .fd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; file_fd, .events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; POLLIN },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    { .fd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sock_fd, .events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; POLLIN },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; poll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fds, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 셋 중 하나라도 read 준비되면 즉시 리턴&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;fds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i].revents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; POLLIN) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        char&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        read&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;fds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i].fd, buf, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf));&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // fd 로 다형 dispatch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // ... 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linux 의 &lt;code&gt;epoll&lt;/code&gt;, BSD 의 &lt;code&gt;kqueue&lt;/code&gt;, 오래된 &lt;code&gt;select&lt;/code&gt; 도 정확히 같은 원리로 fd 를 감시한다. &lt;strong&gt;fd 통합이 없었다면 이 API 들이 존재할 수 없다.&lt;/strong&gt; nginx, Node.js, Nginx event loop, Go 의 netpoller, 전부 이 위에 세워진 구조다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-정리-한-그림으로&quot;&gt;5. 정리, 한 그림으로&lt;/h2&gt;
&lt;p&gt;여섯 문장으로 요약한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프로세스는 &lt;strong&gt;fd 테이블&lt;/strong&gt; 을 갖는다. 각 슬롯은 정수 인덱스이고, 커널이 관리하는 자원을 가리킨다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt; 은 파일을 열고 &lt;strong&gt;가장 작은 빈 fd&lt;/strong&gt; 를 돌려준다. 0/1/2 는 이미 stdin/stdout/stderr 에 쓰인다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저수준 파일 I/O&lt;/strong&gt; (&lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;lseek&lt;/code&gt;) 는 그 fd 하나로 파일을 다룬다. 언제나 짧은 반환에 대비한 loop 가 필요하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket()&lt;/code&gt; 도 새 fd 를 준다. &lt;code&gt;connect&lt;/code&gt; 로 연결한 뒤엔 파일과 완전히 같은 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;/&lt;code&gt;close&lt;/code&gt; API 가 통한다.&lt;/li&gt;
&lt;li&gt;이게 가능한 이유는 커널이 fd 뒤에 &lt;strong&gt;다형적 함수 테이블&lt;/strong&gt; (파일이면 ext4 함수, 소켓이면 소켓 함수) 을 두고 시스템 콜을 dispatch 하기 때문이다.&lt;/li&gt;
&lt;li&gt;통합의 보너스로 &lt;code&gt;poll&lt;/code&gt;/&lt;code&gt;epoll&lt;/code&gt; 이 파일, 소켓, 파이프, 터미널을 한 번에 감시할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;fopen(&quot;data.txt&quot;)&lt;/code&gt; 한 줄, &lt;code&gt;socket(AF_INET, SOCK_STREAM, 0)&lt;/code&gt; 한 줄 뒤에는 이 fd 테이블이 조용히 움직이고 있다. Python 의 &lt;code&gt;open&lt;/code&gt;, Go 의 &lt;code&gt;os.File&lt;/code&gt;, Node.js 의 &lt;code&gt;fs.createReadStream&lt;/code&gt;, Java 의 &lt;code&gt;FileInputStream&lt;/code&gt; 도 결국 안에서는 저수준 시스템 콜을 통해 fd 를 열고 그 정수를 감싼 객체를 돌려준다. 언어가 다를 뿐 커널이 보는 그림은 같다.&lt;/p&gt;
&lt;p&gt;그러니 다음에 &lt;code&gt;write(1, &quot;hi\n&quot;, 3)&lt;/code&gt; 를 볼 때, “저 1 이 fd 1, 즉 stdout 이고, 커널의 file 객체를 거쳐 실제 터미널 드라이버로 흘러가는구나” 하고 한 걸음 뒤로 보면 좋겠다. 그 관점 하나가 파일, 네트워크, 파이프, 리다이렉트, 이벤트 루프까지 전부 같은 언어로 설명해준다.&lt;/p&gt;</content:encoded><category>file-descriptor</category><category>io</category><category>socket</category><category>unix</category><category>linux</category><category>system-call</category><category>kernel</category><category>network</category><author>koa (김신건)</author></item><item><title>지식 그래프가 너무 빽빽했다: D3 force simulation 재튜닝</title><link>https://shinkeonkim.com/posts/2026-07-02-graph-density-tuning/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-07-02-graph-density-tuning/</guid><description>/graph/ 페이지 노드가 서로 겹쳐 보이는 문제를, 파라미터 스케일링과 초기 zoom-to-fit, 라벨 가시성 게이팅으로 다시 잡은 과정.</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;/graph/&lt;/code&gt; 페이지를 열 때마다 신경이 쓰였다. 노드들이 화면 가운데에 몰려서 라벨이 서로 겹치고, 400개까지 잘라 보여주는데도 “이게 지식 그래프인지 별똥별인지” 같은 상태가 자주 나왔다. 그날 하루를 잡고 원인을 정리하면서 파라미터부터 초기 뷰까지 손봤다. 그 과정을 기록해 둔다.&lt;/p&gt;
&lt;h2 id=&quot;어떤-상태였나&quot;&gt;어떤 상태였나&lt;/h2&gt;
&lt;p&gt;측정을 시작할 시점의 콘텐츠 규모.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;컬렉션&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;개수&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;wiki&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;853&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;posts&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;79&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;notes&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;10&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;tags (파생 노드)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;상당수&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;전체 노드는 940개를 넘고, 여기에 태그 노드가 붙는다. &lt;code&gt;/graph/&lt;/code&gt; 페이지는 &lt;code&gt;MAX_GRAPH_NODES = 400&lt;/code&gt; 로 잘라서 연결도 상위 400개만 표시한다. 그래도 400개가 560px 세로 캔버스 안에 힘 시뮬레이션으로 배치되니, 라벨이 나란히 뜨면 서로 물리는 게 당연했다.&lt;/p&gt;
&lt;p&gt;밀도만 다시 계산해 봐도 답이 나온다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;캔버스 넓이 ~ 1024 × 560 = 573,440 px²&lt;/li&gt;
&lt;li&gt;노드당 평균 면적 = 1,433 px²&lt;/li&gt;
&lt;li&gt;√ 값 ≈ 38 px&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;노드 하나가 차지할 정사각형이 대략 38 × 38 픽셀. 원 크기(반경 7 px)만 놓고 보면 여유가 있어 보이지만, 라벨 문자열은 짧게는 60 px, 길면 200 px 을 넘는다. &lt;strong&gt;라벨을 고려하는 순간, 최적 배치라 해도 서로 밀 수밖에 없는 크기&lt;/strong&gt;다.&lt;/p&gt;
&lt;h2 id=&quot;진단-원래-파라미터-뽑아-보기&quot;&gt;진단: 원래 파라미터 뽑아 보기&lt;/h2&gt;
&lt;p&gt;[[D3|d3-force]] 시뮬레이션의 기존 설정은 다음과 같았다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceSimulation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SimNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(simNodes)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;alpha&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;link&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceLink&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SimNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SimLink&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(simLinks)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; d.id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;distance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;strength&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;charge&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceManyBody&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SimNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;strength&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (d.kind &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;tag&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;260&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;160&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;distanceMax&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;center&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceCenter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(width &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, height &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;strength&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.05&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;collide&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceCollide&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SimNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;radius&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (d.kind &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;tag&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 28&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) simulation.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tick&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 상태에서 밀도 문제가 나오는 요인을 하나씩 꼽아 봤다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;linkDistance = 80&lt;/code&gt;&lt;/strong&gt; 이 400개 노드에 비해 짧다. 링크로 연결된 두 노드는 80 px에 안착하려 하고, 링크가 많을수록 서로를 끌어당긴다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;chargeStrength = -160&lt;/code&gt;&lt;/strong&gt; 는 노드 수가 커질수록 상대적으로 약해진다. 반발력을 나눠 갖는 대상이 늘어나기 때문이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;distanceMax = 400&lt;/code&gt;&lt;/strong&gt; 이 반발 사거리를 제한한다. 400 px 밖의 노드는 서로 밀지 않으니, 큰 그래프에서는 국소 뭉치를 없애기 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;forceCenter(cx, cy).strength(0.05)&lt;/code&gt;&lt;/strong&gt; 는 이름과 달리 “무게중심을 정확히 (cx, cy) 로 옮기는” 힘이다. &lt;code&gt;chargeStrength&lt;/code&gt; 로 벌려 놓은 걸 매 tick 마다 다시 안쪽으로 눌러 담는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;collideRadius = 28&lt;/code&gt;&lt;/strong&gt; (문서 노드) 은 원 반경 7 px 대비 넉넉해 보이지만, 라벨 폭을 반영하지 못한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;preTicks = 30&lt;/code&gt;&lt;/strong&gt; 은 초기 정렬에 부족하다. 시뮬레이션이 자리를 못 잡은 상태로 첫 프레임이 나오면 사용자는 그 첫인상을 기억한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 여섯 개 중, 사실상 &lt;strong&gt;모두&lt;/strong&gt; 대형 그래프에서 밀도를 키우는 방향으로 걸려 있었다.&lt;/p&gt;
&lt;h2 id=&quot;후보-해법-네-가지&quot;&gt;후보 해법 네 가지&lt;/h2&gt;
&lt;p&gt;머릿속에서 굴려본 접근이 몇 가지 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A. 파라미터를 노드 수에 따라 스케일링한다.&lt;/strong&gt;
50개용과 400개용을 하나의 상수 세트로 맞추기는 어렵다. &lt;code&gt;n&lt;/code&gt; 이 커지면 자동으로 &lt;code&gt;linkDistance&lt;/code&gt;, &lt;code&gt;charge&lt;/code&gt;, &lt;code&gt;collide&lt;/code&gt; 를 키우는 함수를 두는 게 자연스럽다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;B. 캔버스를 키운다.&lt;/strong&gt;
&lt;code&gt;height = 560&lt;/code&gt; 을 700, 800 로 늘리면 여유는 생긴다. 하지만 그건 문제 자체를 옮길 뿐, 700 개 노드가 들어오면 다시 좁아진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C. 노드를 더 잘라낸다.&lt;/strong&gt;
&lt;code&gt;MAX_GRAPH_NODES = 400&lt;/code&gt; 을 250 정도로 낮추면 시야가 확 밝아지긴 한다. 대신 그래프의 원래 목적 (연결의 전체 지형을 보여주기)이 훼손된다. 최후 수단으로 남겨 둔다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;D. 초기 뷰포트를 바깥으로 잡는다.&lt;/strong&gt;
시뮬레이션이 얼추 자리 잡으면, 그때 bounding box 를 잡아서 그 안이 화면에 들어오도록 zoom transform 을 걸어 준다. 사용자는 처음부터 “전체가 다 보이는” 상태로 시작한다.&lt;/p&gt;
&lt;p&gt;A 와 D 를 조합하고, 라벨 가시성을 zoom 레벨에 걸어 두는 방향으로 진행했다. B 는 필요할 때 열어 두기로 하고, C 는 손대지 않았다.&lt;/p&gt;
&lt;h2 id=&quot;파라미터를-함수로-바꾸기&quot;&gt;파라미터를 함수로 바꾸기&lt;/h2&gt;
&lt;p&gt;먼저 노드 수에 따라 힘을 재조정할 함수를 추가했다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; computeForceParams&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sizeFactor&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sqrt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    linkDistance:  Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sizeFactor)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    docCharge:    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;220&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sizeFactor)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    tagCharge:    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;330&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sizeFactor)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    docCollide:    Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;28&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sizeFactor))),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    centerStrength: Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.03&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.08&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sizeFactor),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    preTicks:      Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기준을 &lt;code&gt;n = 60&lt;/code&gt; 근처로 잡은 이유는, 개인 위키 페이지 하나의 인접 그래프(LocalGraph)가 대략 그 규모이기 때문이다. 그 아래에서는 기존 값과 거의 같게, 그 이상에서는 &lt;code&gt;√(n / 60)&lt;/code&gt; 로 완만하게 늘어난다.&lt;/p&gt;
&lt;p&gt;몇 가지 노드 수에 대한 결과를 계산해 봤다.&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;right&quot;&gt;n&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;linkDistance&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;docCharge&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;docCollide&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;preTicks&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;80&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-220&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;28&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;35&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;100&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;80&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-284&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;31&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;40&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;200&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;110&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-402&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;44&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;50&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;400&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;155&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;-568&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;70&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;400&lt;/code&gt; 노드 케이스에서 링크가 두 배 가까이 벌어지고 반발력이 3배 넘게 세진다. &lt;code&gt;preTicks&lt;/code&gt; 도 두 배로 늘려서 첫 프레임의 안정성을 확보한다.&lt;/p&gt;
&lt;h2 id=&quot;forcecenter-대신-forcex--forcey&quot;&gt;forceCenter 대신 forceX / forceY&lt;/h2&gt;
&lt;p&gt;파라미터 다음으로 손댈 곳은 &lt;strong&gt;어떻게 가운데로 모을 것인가&lt;/strong&gt; 다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;forceCenter&lt;/code&gt; 는 매 tick 마다 노드들의 무게중심을 정확히 (cx, cy) 로 옮기는 병진 힘이다. 개별 노드가 어디에 있는지와 상관없이 통째로 밀어서 위치를 맞춘다. 겉으로는 “중앙 정렬” 처럼 보이지만, 반발력으로 벌려 놓은 뭉치를 무게중심 기준으로 다시 원위치로 데려온다. 결과적으로 뭉침이 잘 안 풀린다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;forceX(cx).strength(s)&lt;/code&gt; + &lt;code&gt;forceY(cy).strength(s)&lt;/code&gt; 는 성격이 다르다. 각 노드에 대해 “네 좌표를 (cx, cy) 방향으로 살짝 밀어라” 라고만 얘기한다. 강도가 낮을수록 자유롭게 벌어질 수 있고, 반발력과 정면 충돌하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;x&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceX&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(width &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;strength&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(params.centerStrength))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;y&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forceY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(height &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;strength&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(params.centerStrength))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;centerStrength&lt;/code&gt; 도 &lt;code&gt;n&lt;/code&gt; 이 커질수록 약해진다 (&lt;code&gt;0.08 / sizeFactor&lt;/code&gt;). 큰 그래프일수록 중앙으로 덜 당기고, 자연스럽게 퍼지도록 놓아 준다.&lt;/p&gt;
&lt;h2 id=&quot;charge-사거리-제한을-풀기&quot;&gt;charge 사거리 제한을 풀기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.distanceMax(400)&lt;/code&gt; 은 반발력의 사거리를 제한한다. Barnes-Hut 근사가 이미 성능을 잡아 주고 있으니, 이 제한을 굳이 둘 필요가 없다. 제거하니 서로 반대편에 있는 뭉치도 아주 약하게나마 서로를 인식하고, 두 뭉치가 겹쳐 뜨는 현상이 줄었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.theta(0.9)&lt;/code&gt; 로 근사 강도만 명시해 뒀다. 기본값과 같지만 향후 튜닝 지점을 눈에 띄게 해 둔다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;forceCollide&lt;/code&gt; 는 &lt;code&gt;strength(0.9)&lt;/code&gt; + &lt;code&gt;iterations(2)&lt;/code&gt; 로 조금 세게 밀었다. 라벨을 완전히 겹치지 않게 만드는 건 어렵지만, 원 자체는 겹치지 않도록.&lt;/p&gt;
&lt;h2 id=&quot;초기-layout-을-넓게-뿌리기&quot;&gt;초기 layout 을 넓게 뿌리기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;seedInitialLayout&lt;/code&gt; 은 원래 반지름 &lt;code&gt;0.4 × min(width, height)&lt;/code&gt; 안에 골든 스파이럴로 노드를 뿌려 준다. 여기에 밀도 스케일을 추가했다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; densityScale&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sqrt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; maxRadius&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(width, height) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.45&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; densityScale;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;n = 400&lt;/code&gt; 이면 &lt;code&gt;densityScale ≈ 2.24&lt;/code&gt;, 즉 초기 뿌림 반지름이 두 배 이상 넓어진다. 노드가 처음부터 화면 바깥까지 나가는 게 오히려 좋다. 시뮬레이션이 안쪽으로 압축하기보다 바깥에서 안쪽으로 정리하는 방향으로 작동한다.&lt;/p&gt;
&lt;h2 id=&quot;zoom-to-fit-첫-프레임을-다-보이는-상태로&quot;&gt;Zoom-to-fit: 첫 프레임을 “다 보이는” 상태로&lt;/h2&gt;
&lt;p&gt;파라미터를 아무리 튜닝해도, 초기 뷰포트가 노드 구름의 일부만 잡고 있으면 사용자는 여전히 “복잡하다” 고 느낀다. 그래서 &lt;code&gt;preTicks&lt;/code&gt; 로 안정화한 뒤 bounding box 를 잡아 초기 zoom transform 을 계산해서 넣어 줬다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (simNodes.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 노드 좌표의 min/max 로 bbox 계산&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; minX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, maxX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, minY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, maxY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; node&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; simNodes) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; x&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; node.x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; y&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; node.y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; minX) minX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maxX) maxX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; minY) minY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maxY) maxY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; bboxW&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maxX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; minX;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; bboxH&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maxY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; minY;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (bboxW &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bboxH &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; labelOverhang&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 160&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; padding&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 32&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; kx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (width &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; padding &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (bboxW &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; labelOverhang);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ky&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (height &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; padding &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bboxH;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; k&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.25&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Math.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, kx, ky));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; centerX&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (minX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maxX) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; centerY&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (minY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maxY) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; tx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; width &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; centerX &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ty&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; height &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; centerY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; initialTransform&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; d3.zoomIdentity.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;translate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(tx, ty).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;scale&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(k);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      svg.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(zoom.transform, initialTransform);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;labelOverhang = 160&lt;/code&gt; 은 문서 노드 라벨이 노드 중심에서 오른쪽으로 얼마나 뻗는지를 반영한 값이다. 최대 라벨 길이를 재서 나온 실측 근사치.&lt;/li&gt;
&lt;li&gt;최소 스케일을 &lt;code&gt;0.25&lt;/code&gt; 로 잡아서 아주 큰 그래프에서도 완전히 짓눌리진 않게 했다. &lt;code&gt;scaleExtent&lt;/code&gt; 하한도 &lt;code&gt;0.3 → 0.2&lt;/code&gt; 로 살짝 넓혀 뒀다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if (k &amp;#x3C; 1)&lt;/code&gt; 로 감쌌기 때문에 작은 그래프 (LocalGraph 처럼 20 개 이하) 에서는 zoom 이 걸리지 않고 자연 크기로 뜬다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;라벨은-zoom-이-낮으면-숨긴다&quot;&gt;라벨은 zoom 이 낮으면 숨긴다&lt;/h2&gt;
&lt;p&gt;zoom 이 낮은 상태에서 400개 라벨이 다 떠 있으면 여전히 어지럽다. 그래서 라벨 가시성을 스케일에 걸어 두었다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; LABEL_ZOOM_THRESHOLD&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.55&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; zoom&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; d3.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;zoom&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SVGSVGElement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;scaleExtent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;zoom&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    root.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;attr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;transform&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, event.transform.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; showLabels&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event.transform.k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; LABEL_ZOOM_THRESHOLD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;selectAll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SVGTextElement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SimNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.graph-nodes text&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;style&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;display&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, showLabels &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;none&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0.55&lt;/code&gt; 미만이면 라벨을 감춰서 구조 (원, 색, 링크) 만 보이게 한다. 사용자가 궁금한 영역으로 zoom-in 하면 라벨이 자연스럽게 나타난다. &lt;strong&gt;초기 zoom-to-fit 이 이 임계값 밑이라면 곧바로 라벨 없는 상태로 시작&lt;/strong&gt;하므로, 첫인상이 훨씬 깨끗해진다.&lt;/p&gt;
&lt;p&gt;이 방식의 좋은 점은 코드 한 조각으로 두 가지를 동시에 잡는다는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대형 그래프의 초기 뷰: 라벨 없이 지형만&lt;/li&gt;
&lt;li&gt;사용자의 zoom-in: 관심 영역만 라벨 노출&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;전역 상태를 따로 두지 않고 zoom 이벤트 하나로 처리했다.&lt;/p&gt;
&lt;h2 id=&quot;3d-뷰도-살짝&quot;&gt;3D 뷰도 살짝&lt;/h2&gt;
&lt;p&gt;3D &lt;code&gt;Graph3D&lt;/code&gt; 는 Fibonacci sphere 로 초기 위치를 잡는다. 반지름 스케일이 &lt;code&gt;Math.max(80, 14 × ∛n)&lt;/code&gt; 이었는데, 400개면 반지름 103. 3D 인 만큼 부피 배분이 유리하지만 그래도 좁았다. 계수를 올려서 &lt;code&gt;Math.max(120, 22 × ∛n)&lt;/code&gt; 로 바꿨고, 400개에서 반지름은 162 정도가 된다. 3D 는 원래 시각적으로 여유가 있어서 이 정도만으로 눈에 띄게 깔끔해졌다.&lt;/p&gt;
&lt;h2 id=&quot;되돌아보기&quot;&gt;되돌아보기&lt;/h2&gt;
&lt;p&gt;이 튜닝은 전형적인 “d3 힘 시뮬레이션은 마법이 아니다” 이야기다. 잘 안 되면 원인은 대부분 몇 가지로 좁혀진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;링크 거리와 반발력이 노드 수에 비해 작다.&lt;/li&gt;
&lt;li&gt;중앙 정렬 힘이 반발력과 정면으로 싸운다.&lt;/li&gt;
&lt;li&gt;초기 뷰포트가 노드 구름 대비 좁다.&lt;/li&gt;
&lt;li&gt;라벨을 다 보여 주려고 하다가 시각 정보가 오히려 사라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;n&lt;/code&gt; 기반 스케일링 + &lt;code&gt;forceX&lt;/code&gt;/&lt;code&gt;forceY&lt;/code&gt; + zoom-to-fit + zoom 게이팅. 이 네 가지는 개인 지식 그래프 스케일에서는 거의 항상 통하는 조합이었다. 다음에 또 비슷한 문제를 만나면 아마 같은 순서로 열게 될 것 같다.&lt;/p&gt;
&lt;p&gt;남은 아이디어는 두 가지.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;연결도 기반 라벨 필터&lt;/strong&gt;: 저 zoom 상태에서도 “허브 노드” 라벨은 남겨서 지형 파악을 돕는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커뮤니티 감지 후 클러스터 색상&lt;/strong&gt;: 카테고리가 아니라 실제 연결 구조에 따라 뭉치별로 색을 부여.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;둘 다 재미있지만 이번 문제의 표층은 대체로 해결됐으니, 그 두 개는 다음 반복으로 넘긴다.&lt;/p&gt;</content:encoded><category>d3</category><category>graph</category><category>visualization</category><category>force-simulation</category><category>astro</category><category>tuning</category><author>koa (김신건)</author></item><item><title>Rails 에서의 Read/Write Database 분리</title><link>https://shinkeonkim.com/posts/2026-06-29-rails-read-write-database-split/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-29-rails-read-write-database-split/</guid><description>Rails 의 Multiple Databases 기능으로 HTTP 메서드에 따라 read/write DB 를 자동 분리하는 패턴, GET 인데 쓰기가 필요할 때 ForceWrite concern 으로 role 을 일시 승격시키는 방법, 그리고 replication lag 등 실전 함정까지 정리합니다.</description><pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;시작-의문&quot;&gt;시작 의문&lt;/h2&gt;
&lt;p&gt;서비스 트래픽이 늘어나면 가장 먼저 부담을 느끼는 곳은 데이터베이스입니다. 특히 읽기 쿼리가 무거운 서비스라면, 단일 인스턴스로 모든 부하를 감당하는 것이 점점 어려워집니다. 이때 자연스럽게 떠오르는 대안은 &lt;strong&gt;읽기와 쓰기를 다른 인스턴스로 분리&lt;/strong&gt;하는 것입니다.&lt;/p&gt;
&lt;p&gt;Rails 는 6.0 부터 이 패턴을 &lt;a href=&quot;https://guides.rubyonrails.org/active_record_multiple_databases.html&quot;&gt;공식 기능으로 지원&lt;/a&gt;합니다. 기본 정책은 단순합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GET&lt;/code&gt; / &lt;code&gt;HEAD&lt;/code&gt; 요청&lt;/strong&gt;은 &lt;code&gt;reading&lt;/code&gt; role 로 라우팅 → replica 에 SELECT 가 발행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;POST&lt;/code&gt; / &lt;code&gt;PATCH&lt;/code&gt; / &lt;code&gt;PUT&lt;/code&gt; / &lt;code&gt;DELETE&lt;/code&gt; 요청&lt;/strong&gt;은 &lt;code&gt;writing&lt;/code&gt; role 로 라우팅 → primary 에 INSERT/UPDATE/DELETE 가 발행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;직관적입니다. 하지만 실제 서비스를 운영하다 보면 곧 다음과 같은 질문에 부딪힙니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GET 요청인데 데이터를 써야 한다면? 예를 들어 조회수 카운터를 올리거나, OAuth 콜백을 GET 으로 받아 토큰을 저장해야 한다면 어떻게 해야 할까요?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POST 직후의 GET 은 replica 에서 stale 한 데이터를 읽지 않을까요? Replication lag 때문에 방금 만든 레코드가 안 보이는 경우는?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;force_write { ... }&lt;/code&gt; 같은 블록을 코드 곳곳에 흩뿌리지 않고 깔끔하게 추상화할 방법은 없을까요?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;한 줄로 답하면 “DatabaseSelector 미들웨어 + ForceWrite concern” 조합으로 풀립니다. 다만 &lt;em&gt;왜&lt;/em&gt; 그 조합이 되는지를 이해하려면 한 단계 더 들어가야 합니다. 이 글에서는 &lt;strong&gt;읽기 부하 분산이 가능한 이유&lt;/strong&gt;, &lt;strong&gt;HTTP 메서드 기반 자동 라우팅의 동작&lt;/strong&gt;, &lt;strong&gt;GET 안에서 쓰기가 필요한 케이스의 패턴화&lt;/strong&gt;, 그리고 &lt;strong&gt;실전에서 마주치는 함정들&lt;/strong&gt;을 차례로 풀어 보겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 가장 기초적인 곳부터 시작하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;한-단계-뒤로-왜-읽기와-쓰기를-분리하는가&quot;&gt;한 단계 뒤로, 왜 읽기와 쓰기를 분리하는가&lt;/h2&gt;
&lt;h3 id=&quot;primary-replica-복제-구조&quot;&gt;Primary-Replica 복제 구조&lt;/h3&gt;
&lt;p&gt;DB 를 분리한다는 말은 보통 다음 구조를 의미합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Primary&lt;/strong&gt;: 쓰기(INSERT/UPDATE/DELETE) 와 읽기 모두 가능한 메인 인스턴스입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replica&lt;/strong&gt;: primary 의 변경 사항을 비동기적으로 받아 동기화하는 읽기 전용 복제본입니다. PostgreSQL 이라면 WAL streaming, MySQL 이라면 binlog replication 으로 동작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;App 서버는 쓰기 트래픽을 primary 에 보내고, 읽기 트래픽을 replica 에 분산합니다. Replica 는 여러 개 둘 수 있어 수평 확장이 가능합니다.&lt;/p&gt;
&lt;h3 id=&quot;분리해서-얻는-것&quot;&gt;분리해서 얻는 것&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;읽기 부하 분산&lt;/strong&gt;: 리포트 / 검색 / 통계처럼 무거운 SELECT 를 replica 로 보내면 primary 부하가 줄어듭니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;장애 격리&lt;/strong&gt;: replica 가 죽어도 쓰기는 살아 있고, primary 가 죽어도 읽기는 살아 있어 부분적으로 서비스를 유지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;장기 트랜잭션 격리&lt;/strong&gt;: 분석성 쿼리가 primary 의 락을 잡지 않으므로 OLTP 성능이 안정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;분리해서-잃는-것&quot;&gt;분리해서 잃는 것&lt;/h3&gt;
&lt;p&gt;공짜는 아닙니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Replication lag&lt;/strong&gt;: primary 에서 쓰기가 완료된 직후 replica 에는 아직 그 변경이 반영되지 않은 상태가 있습니다. 보통 수십 ms 에서 길게는 수 초까지 갈 수 있습니다. 이 동안 같은 데이터를 replica 에서 읽으면 &lt;strong&gt;stale read&lt;/strong&gt; 가 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;일관성 모델 복잡화&lt;/strong&gt;: 트랜잭션이 두 연결에 걸치면 일관성을 보장하기 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;운영 복잡도&lt;/strong&gt;: replica 의 lag 모니터링, failover, 백업 등 운영 부담이 늘어납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 글의 후반부에서 보겠지만, Rails 의 &lt;code&gt;DatabaseSelector&lt;/code&gt; 는 stale read 문제를 부분적으로 해결합니다. 다만 &lt;em&gt;완전히&lt;/em&gt; 해결하지는 않으니 트레이드오프를 기억해 두는 것이 좋습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rails-설정-databaseyml-과-connects_to&quot;&gt;Rails 설정, &lt;code&gt;database.yml&lt;/code&gt; 과 &lt;code&gt;connects_to&lt;/code&gt;&lt;/h2&gt;
&lt;h3 id=&quot;두-connection-정의&quot;&gt;두 connection 정의&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;config/database.yml&lt;/code&gt; 에 primary 와 replica 두 connection 을 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;production&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  primary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    database&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;my_app_production&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    adapter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    host&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#x3C;%= ENV[&quot;DB_PRIMARY_HOST&quot;] %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    username&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#x3C;%= ENV[&quot;DB_USER&quot;] %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    password&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#x3C;%= ENV[&quot;DB_PASSWORD&quot;] %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  primary_replica&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    database&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;my_app_production&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    adapter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;postgresql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    host&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#x3C;%= ENV[&quot;DB_REPLICA_HOST&quot;] %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    username&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#x3C;%= ENV[&quot;DB_USER&quot;] %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    password&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#x3C;%= ENV[&quot;DB_PASSWORD&quot;] %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    replica&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;replica: true&lt;/code&gt; 옵션은 마이그레이션 대상에서 이 connection 을 제외하라는 표시입니다. Replica 는 primary 의 복제본이므로 별도로 스키마를 변경하면 안 됩니다.&lt;/p&gt;
&lt;h3 id=&quot;모델에서-role-매핑&quot;&gt;모델에서 role 매핑&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ApplicationRecord&lt;/code&gt; 에서 두 connection 을 role 에 묶어 줍니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# app/models/application_record.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApplicationRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  primary_abstract_class&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  connects_to &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;database:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    writing:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :primary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    reading:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :primary_replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 두 가지가 중요합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;primary_abstract_class&lt;/code&gt;&lt;/strong&gt;: 모든 모델이 이 추상 클래스를 통해 connection 을 공유하도록 만듭니다. 이게 없으면 모델마다 connection 풀이 따로 잡혀 메모리를 낭비합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;connects_to database: { ... }&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;writing&lt;/code&gt; 과 &lt;code&gt;reading&lt;/code&gt; 이라는 &lt;strong&gt;role&lt;/strong&gt; 이름에 실제 connection 을 매핑합니다. 이 이름은 자유롭게 정할 수 있지만 &lt;code&gt;writing&lt;/code&gt; / &lt;code&gt;reading&lt;/code&gt; 이 관례입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 모델 입장에서는 “지금 어느 role 인가” 에 따라 어느 connection 으로 쿼리가 발행될지가 결정됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;자동-라우팅-databaseselector-미들웨어&quot;&gt;자동 라우팅, &lt;code&gt;DatabaseSelector&lt;/code&gt; 미들웨어&lt;/h2&gt;
&lt;h3 id=&quot;미들웨어-활성화&quot;&gt;미들웨어 활성화&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;config/environments/production.rb&lt;/code&gt; 에 다음을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;active_record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;database_selector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;delay:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;seconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;active_record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;database_resolver&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Middleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DatabaseSelector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Resolver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;active_record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;database_resolver_context&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Middleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DatabaseSelector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Resolver&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Session&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 세 줄이 &lt;code&gt;DatabaseSelector&lt;/code&gt; 미들웨어를 Rack 스택에 끼워 넣습니다. 모든 요청은 이 미들웨어를 거치면서 &lt;strong&gt;role 이 결정된 다음&lt;/strong&gt; 컨트롤러로 전달됩니다.&lt;/p&gt;
&lt;h3 id=&quot;동작-방식&quot;&gt;동작 방식&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;미들웨어가 보는 것은 두 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;HTTP 메서드&lt;/strong&gt;: &lt;code&gt;GET&lt;/code&gt; 또는 &lt;code&gt;HEAD&lt;/code&gt; 면 &lt;code&gt;reading&lt;/code&gt; role, 그 외면 &lt;code&gt;writing&lt;/code&gt; role.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션의 마지막 쓰기 시각&lt;/strong&gt;: 같은 세션에서 최근에 쓰기가 있었다면, &lt;code&gt;delay&lt;/code&gt; 윈도우 안의 모든 GET 도 &lt;code&gt;writing&lt;/code&gt; role 로 강제됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;두 번째가 핵심입니다. 사용자가 게시글을 작성한 직후 (POST), 페이지를 새로고침해서 자기 글을 확인하면 (GET) replica 의 lag 때문에 게시글이 안 보일 수 있습니다. &lt;code&gt;delay: 2.seconds&lt;/code&gt; 는 “쓰기 후 2 초 동안은 같은 세션의 GET 도 primary 로 보내라” 는 뜻입니다. Replica lag 가 1 초 이내라면 이 윈도우 안에서 stale read 가 거의 사라집니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;code&gt;delay&lt;/code&gt; 는 &lt;em&gt;세션 단위&lt;/em&gt; 입니다. 다른 사용자의 GET 은 영향을 받지 않습니다. 즉, 자기가 방금 쓴 글은 자기에게 즉시 보이지만, 다른 사용자에게는 lag 만큼 지연되어 보일 수 있습니다. 이 정도면 대부분의 서비스에서 받아들일 만한 일관성 수준입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;미들웨어가-컨트롤러-액션-전체를-감싼다&quot;&gt;미들웨어가 컨트롤러 액션 전체를 감싼다&lt;/h3&gt;
&lt;p&gt;여기서 미묘한 함정이 하나 있습니다. &lt;code&gt;DatabaseSelector&lt;/code&gt; 는 &lt;strong&gt;컨트롤러 액션이 시작되기 &lt;em&gt;전&lt;/em&gt; 에 role 을 결정&lt;/strong&gt; 합니다. 그리고 액션이 끝날 때까지 그 role 을 유지합니다. 즉, GET 요청으로 들어온 액션 내부에서는 모든 쿼리가 reading role 로 가게 됩니다.&lt;/p&gt;
&lt;p&gt;문제는 GET 요청이라고 해서 항상 읽기만 하는 것이 아니라는 점입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;문제-get-인데-쓰기가-필요한-경우&quot;&gt;문제: GET 인데 쓰기가 필요한 경우&lt;/h2&gt;
&lt;p&gt;자동 라우팅의 한계가 드러나는 케이스들입니다.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;케이스&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;조회수 / 카운터 증가&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;GET /posts/:id&lt;/code&gt; 에서 &lt;code&gt;post.increment!(:view_count)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Lazy migration&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;첫 GET 때 누락된 메타 데이터를 채워 넣기&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;검색 로그 기록&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;GET /search?q=...&lt;/code&gt; 에서 검색 기록 INSERT&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;OAuth / Webhook 콜백&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;외부 서비스가 &lt;code&gt;GET&lt;/code&gt; 으로 콜백, 내부에서 토큰 저장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;마지막 접근 시각 갱신&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;GET /me&lt;/code&gt; 에서 &lt;code&gt;current_user.touch(:last_seen_at)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이런 액션 내부에서 그냥 &lt;code&gt;post.increment!(:view_count)&lt;/code&gt; 를 호출하면 어떻게 될까요? Reading role 의 connection 은 replica 를 가리키고 있고, 대부분의 replica 는 read-only 로 설정되어 있습니다. 결과는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PostsController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApplicationController&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    @post &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(params[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    @post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;increment!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:view_count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # =&gt; ActiveRecord::ReadOnlyError:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    #    Write query attempted while in readonly mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명시적으로 예외가 발생합니다. 친절하지만 막막합니다. “그러면 이 액션 전체를 POST 로 바꿔야 하나?” 라고 묻고 싶지만, REST 의 관점에서 &lt;em&gt;조회&lt;/em&gt; 는 GET 이 맞고 카운터는 사이드이펙트일 뿐입니다. 그렇다고 컨트롤러 전체를 writing role 로 묶어 버리면 읽기 부하 분산 효과가 사라집니다.&lt;/p&gt;
&lt;p&gt;필요한 것은 &lt;strong&gt;쓰기가 발생하는 그 블록만 writing role 로 일시 승격&lt;/strong&gt; 시키는 방법입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;connected_to-로-role-수동-전환&quot;&gt;&lt;code&gt;connected_to&lt;/code&gt; 로 role 수동 전환&lt;/h2&gt;
&lt;p&gt;ActiveRecord 는 &lt;code&gt;connected_to&lt;/code&gt; 라는 메서드를 통해 role 을 수동으로 전환할 수 있게 해 줍니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connected_to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;role:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :writing&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  @post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;increment!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:view_count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;블록 안에서 발행되는 모든 쿼리는 &lt;code&gt;writing&lt;/code&gt; role 의 connection 을 사용합니다. 블록이 끝나면 원래 role 로 돌아갑니다. 이게 핵심 메커니즘입니다.&lt;/p&gt;
&lt;p&gt;다만 매번 &lt;code&gt;ActiveRecord::Base.connected_to(role: :writing) do ... end&lt;/code&gt; 를 적는 것은 길고 노출이 잦아집니다. 컨트롤러 곳곳에 흩어져 있으면 의도가 잘 안 보이기도 합니다. 이걸 한 줄로 줄이는 것이 다음 단계입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;forcewrite-concern&quot;&gt;ForceWrite Concern&lt;/h2&gt;
&lt;p&gt;Rails 의 관용구를 따라 [[rails-concerns|concern]] 으로 추출합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# app/controllers/concerns/force_write.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ForceWrite&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  extend&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ActiveSupport&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Concern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; force_write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connected_to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;role:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :writing&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ApplicationController&lt;/code&gt; 에 include 하면 모든 컨트롤러에서 &lt;code&gt;force_write&lt;/code&gt; 메서드를 쓸 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# app/controllers/application_controller.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApplicationController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ActionController::Base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  include&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ForceWrite&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;블록-안에서-일어나는-일&quot;&gt;블록 안에서 일어나는 일&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GET 요청이 들어오면 처음에는 reading role 입니다. &lt;code&gt;Post.find(1)&lt;/code&gt; 같은 SELECT 는 replica 로 갑니다. &lt;code&gt;force_write do ... end&lt;/code&gt; 블록 안으로 들어가는 순간 role 이 writing 으로 승격되면서, 그 안의 모든 쿼리는 primary 로 발행됩니다. 블록이 끝나면 다시 reading role 로 복귀합니다.&lt;/p&gt;
&lt;p&gt;타임라인으로 보면 한 GET 요청 안에서 role 이 &lt;code&gt;reading → writing → reading&lt;/code&gt; 으로 두 번 전환됩니다. 이 전환 자체에는 거의 비용이 없습니다. ActiveRecord 의 connection handler 가 thread-local 변수로 현재 role 을 추적할 뿐입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;실전-패턴&quot;&gt;실전 패턴&lt;/h2&gt;
&lt;p&gt;세 가지 대표적인 사용 예시를 보겠습니다.&lt;/p&gt;
&lt;h3 id=&quot;예시-1-조회수-증가&quot;&gt;예시 1: 조회수 증가&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PostsController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApplicationController&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    @post &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(params[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# replica 에서 SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    force_write &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      @post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;increment!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:view_count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# primary 에 UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # render 는 그 다음, delay 윈도우 안이라면 primary 유지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가장 흔한 케이스입니다. 조회수가 정확히 +1 일 필요는 없으니 카운터 손실에 관대해도 되고, 매 GET 마다 UPDATE 하는 비용이 부담이라면 [[Redis|Redis]] 의 INCR 로 버퍼링한 다음 주기적으로 flush 하는 패턴도 자주 씁니다. 다만 그 flush 도 결국 writing role 에서 일어나야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;예시-2-검색-로그-기록&quot;&gt;예시 2: 검색 로그 기록&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SearchController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApplicationController&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; index&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    @results &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(params[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:q&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    force_write &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      SearchLog&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;create!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:q&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; current_user)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;검색 자체는 replica 에서 수행하지만, 검색 기록은 primary 에 남깁니다. 검색 기록을 잃어도 비즈니스에 큰 영향이 없으면 이 기록도 비동기 job 으로 보내는 것이 더 낫습니다.&lt;/p&gt;
&lt;h3 id=&quot;예시-3-oauth-콜백&quot;&gt;예시 3: OAuth 콜백&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CallbacksController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApplicationController&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # OAuth provider 가 GET 으로 redirect 보내는 경우&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; oauth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    token&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; exchange_code&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(params[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:code&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    force_write &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      current_user.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;update!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;oauth_token:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    redirect_to root_path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;외부 OAuth provider 가 GET 으로 콜백을 보내는 경우입니다. 우리 도메인에서는 분명히 &lt;em&gt;쓰기&lt;/em&gt; 동작이지만 HTTP 메서드로는 GET 으로 들어옵니다. 이런 케이스는 &lt;code&gt;force_write&lt;/code&gt; 로 명시적으로 감싸는 것이 자연스럽습니다.&lt;/p&gt;
&lt;h3 id=&quot;모델--서비스-객체에서도-쓰기&quot;&gt;모델 / 서비스 객체에서도 쓰기&lt;/h3&gt;
&lt;p&gt;컨트롤러를 거치지 않는 코드 (콜백, ActiveJob, Service Object 등) 에서도 같은 패턴이 필요합니다. 모델용 concern 도 같은 방식으로 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# app/models/concerns/force_write.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ForceWrite&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  extend&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ActiveSupport&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Concern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  module&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ClassMethods&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; force_write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connected_to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;role:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :writing&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; force_write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;force_write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  include&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ForceWrite&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; record_view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    force_write { &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;increment!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:view_count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;함정과-주의사항&quot;&gt;함정과 주의사항&lt;/h2&gt;
&lt;h3 id=&quot;1-블록-안의-모든-쿼리가-writing-으로-갑니다&quot;&gt;1. 블록 안의 &lt;em&gt;모든&lt;/em&gt; 쿼리가 writing 으로 갑니다&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;force_write &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  log_view&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(post)               &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# SearchLog INSERT (의도)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;related_posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;to_a&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 이것도 primary 로 감 (의도 아닐 수 있음)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;force_write&lt;/code&gt; 블록 안에서는 SELECT 까지 모두 primary 로 갑니다. 블록의 범위를 &lt;em&gt;최소한의 쓰기 한두 줄&lt;/em&gt; 로 좁혀 두는 것이 안전합니다.&lt;/p&gt;
&lt;h3 id=&quot;2-트랜잭션-경계와-role-은-별개입니다&quot;&gt;2. 트랜잭션 경계와 role 은 별개입니다&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 위험한 패턴&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  force_write &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;트랜잭션은 &lt;em&gt;열린 시점&lt;/em&gt; 의 connection 에 묶입니다. Reading role 로 transaction 을 연 다음 안에서 writing role 로 전환하면 트랜잭션이 두 connection 에 걸쳐 일관성이 깨질 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 올바른 순서&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;force_write &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  ApplicationRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    post.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;force_write&lt;/code&gt; 가 항상 트랜잭션을 감싸는 &lt;em&gt;바깥&lt;/em&gt; 에 있어야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;3-get-의-멱등성&quot;&gt;3. GET 의 멱등성&lt;/h3&gt;
&lt;p&gt;HTTP/1.1 명세상 GET 은 &lt;strong&gt;멱등 (idempotent)&lt;/strong&gt; 이어야 합니다. 즉 같은 GET 을 여러 번 보내도 서버 상태가 같은 결과로 수렴해야 합니다. &lt;code&gt;force_write&lt;/code&gt; 로 사이드이펙트를 만드는 순간 이 약속이 깨집니다. 브라우저 prefetch, 검색 엔진 크롤러, retry 로직이 의도치 않게 카운터를 여러 번 증가시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;이런 경우에는 다음 중 하나를 고려하는 것이 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;세션 / IP 단위 dedup&lt;/strong&gt;: 같은 사용자가 짧은 시간 안에 여러 번 GET 해도 한 번만 카운트.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HyperLogLog 같은 근사 자료구조&lt;/strong&gt;: 정확한 카운트가 아니어도 되는 케이스.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비동기 job 으로 분리&lt;/strong&gt;: GET 응답에서는 큐에 넣고 워커가 실제 쓰기 수행.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-delay-윈도우-안의-후속-get&quot;&gt;4. Delay 윈도우 안의 후속 GET&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;delay: 2.seconds&lt;/code&gt; 를 잡았다면, 쓰기 직후 2 초 안에 들어온 같은 세션의 GET 은 모두 primary 로 갑니다. 이 GET 의 컨트롤러 액션 안에서 SELECT 가 100 개 발행되면 그 100 개도 primary 에 부담을 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Delay 는 replica lag 의 상한보다 살짝 크게&lt;/strong&gt; 잡는 것이 좋습니다. Lag 이 보통 200ms 라면 1초, 가끔 1초까지 튀면 2~3초 정도가 적절합니다. 무작정 크게 잡으면 분리 효과가 약해집니다.&lt;/p&gt;
&lt;h3 id=&quot;5-미들웨어-우선순위&quot;&gt;5. 미들웨어 우선순위&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DatabaseSelector&lt;/code&gt; 는 Rack 미들웨어 스택의 특정 위치에 끼워집니다. 다른 커스텀 미들웨어를 추가할 때 순서가 꼬이면 role 이 결정되기 전에 컨트롤러 코드가 실행될 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rails&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; middleware&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명령으로 현재 스택을 출력해서 &lt;code&gt;ActiveRecord::Middleware::DatabaseSelector&lt;/code&gt; 의 위치를 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 id=&quot;6-테스트-환경에서의-분기&quot;&gt;6. 테스트 환경에서의 분기&lt;/h3&gt;
&lt;p&gt;테스트 환경에서는 보통 single DB 로 돌리므로 &lt;code&gt;connects_to&lt;/code&gt; 를 환경별로 분기하는 것이 편합니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApplicationRecord&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  primary_abstract_class&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Rails&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;production?&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Rails&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;staging?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    connects_to &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;database:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;writing:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :primary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;reading:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :primary_replica&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또는 테스트 환경의 &lt;code&gt;database.yml&lt;/code&gt; 에 replica 를 정의하지 않으면 reading role 도 primary 로 fallback 하므로 그대로 두는 방법도 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;자동-라우팅-vs-수동-전환-언제-무엇을-쓸까요&quot;&gt;자동 라우팅 vs 수동 전환, 언제 무엇을 쓸까요&lt;/h2&gt;
&lt;p&gt;지금까지 본 도구들을 정리하면 다음 표가 됩니다.&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;기준&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;자동 (&lt;code&gt;DatabaseSelector&lt;/code&gt;)&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;수동 (&lt;code&gt;connected_to&lt;/code&gt; / &lt;code&gt;force_write&lt;/code&gt;)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP 메서드가 의도와 일치&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;적합합니다&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;불필요합니다&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;GET 에서 쓰기 일부&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;자동 + &lt;code&gt;force_write&lt;/code&gt; 보조&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모든 쓰기 직접 감싸야 합니다&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;컨트롤러 외부 (Job, Service)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;동작하지 않습니다&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;필수입니다&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;모든 요청을 writing 으로 고정&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;미들웨어를 제거합니다&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;항상 &lt;code&gt;connected_to(:writing)&lt;/code&gt; 으로 감쌉니다&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;대부분의 Rails 앱은 &lt;strong&gt;자동 라우팅 + 예외 케이스만 &lt;code&gt;force_write&lt;/code&gt;&lt;/strong&gt; 조합이 균형점입니다. 자동 라우팅이 80% 이상의 케이스를 처리하고, 나머지 사이드이펙트 케이스만 명시적으로 표시하는 식입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;
&lt;p&gt;읽기와 쓰기를 분리한다는 결정은 단순히 “replica 를 띄우면 끝” 이 아닙니다. 다음 세 가지가 함께 와야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인프라 레벨&lt;/strong&gt;: primary-replica 복제 구성, replication lag 모니터링, failover 자동화.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;앱 레벨&lt;/strong&gt;: &lt;code&gt;connects_to&lt;/code&gt; 로 role 매핑, &lt;code&gt;DatabaseSelector&lt;/code&gt; 미들웨어 활성화, &lt;code&gt;delay&lt;/code&gt; 윈도우 설정.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;코드 레벨&lt;/strong&gt;: GET 의 사이드이펙트를 &lt;code&gt;force_write&lt;/code&gt; 같은 concern 으로 명시적으로 표시.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;세 번째가 흔히 빠뜨리는 부분입니다. 미들웨어만 켜 두고 코드는 그대로 두면, 어딘가에서 &lt;code&gt;ActiveRecord::ReadOnlyError&lt;/code&gt; 가 터지거나 카운터가 안 올라가는 미묘한 버그로 이어집니다. &lt;strong&gt;GET 안의 쓰기를 의도적으로 만든다면, 그 의도를 코드에 남기는 것&lt;/strong&gt;이 결국 가장 중요합니다.&lt;/p&gt;
&lt;p&gt;추가로 깊이 들어가고 싶다면 다음을 참고하시면 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;공식 가이드: &lt;a href=&quot;https://guides.rubyonrails.org/active_record_multiple_databases.html&quot;&gt;Multiple Databases with Active Record&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;미들웨어 API: &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Middleware/DatabaseSelector.html&quot;&gt;&lt;code&gt;ActiveRecord::Middleware::DatabaseSelector&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련 [[rails-activerecord-basics|Rails ActiveRecord 기초]]&lt;/li&gt;
&lt;li&gt;관련 [[rails-concerns|Rails Concerns]]&lt;/li&gt;
&lt;li&gt;관련 [[redis|Redis]] (카운터 버퍼링용)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>rails</category><category>activerecord</category><category>database</category><category>read-replica</category><category>scaling</category><category>performance</category><author>koa (김신건)</author></item><item><title>글 쓰는 즐거움을 위해 직접 만든 세 개의 도구: 에디터, 스튜디오, 차트 에디터</title><link>https://shinkeonkim.com/posts/2026-06-25-blog-dev-tools/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-25-blog-dev-tools/</guid><description>shinkeonkim.com 안에 살고 있는 세 가지 개발 전용 도구의 기능을 한자리에 모았다. 마크다운 에디터, 애니메이션 스튜디오, Chart.js 에디터까지.</description><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;왜-도구부터-만들었나&quot;&gt;왜 도구부터 만들었나&lt;/h2&gt;
&lt;p&gt;이 블로그를 시작할 때, 머릿속에 가장 먼저 떠오른 건 “어떻게 글을 쓸까” 가 아니라 “어떻게 보여줄까” 였다. 시각화 자료, 다이어그램, 다양한 형태의 소개 자료를 한곳에 모으는 공간이 갖고 싶었고, 그러려면 단순히 마크다운만 잘 렌더링하는 정적 사이트로는 부족했다.&lt;/p&gt;
&lt;p&gt;그래서 두 가지 방향으로 도구를 키워나갔다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;글 쓰는 과정 자체를 쉽게 만들기&lt;/strong&gt;, 매번 VSCode 를 열고 frontmatter 를 손으로 채우고 이미지 경로를 신경 쓰는 건 곧 피로가 된다. 글이 잘 써지지 않는 가장 큰 이유는 영감 부족이 아니라 마찰이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시각화·애니메이션·차트를 일급 시민으로&lt;/strong&gt;, 그래야 같은 주제도 더 직관적으로 설명할 수 있고, 위키 페이지를 점진적으로 키워나가는 흐름과도 맞다. 위키는 내가 모르는 분야라도 AI 의 도움으로 작은 조각을 모으고, 시각 자료를 곁들여 키워가고 싶었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그 결과 [[Astro]] 기반 블로그 안에 세 가지 &lt;strong&gt;개발 모드 전용 도구&lt;/strong&gt; 가 자라났다.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;도구&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;라우트&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;역할&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;콘텐츠 에디터&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/_editor&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;마크다운 작성, 미리보기, 자산 관리, Git&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;애니메이션 스튜디오&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/_studio&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SVG 기반 인터랙티브 애니메이션 제작&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;차트 에디터&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/_chart-editor&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Chart.js 차트 시각 편집 + MDX 코드 추출&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
세 도구 모두 &lt;code&gt;bun dev&lt;/code&gt; 로컬 개발 환경에서만 동작한다. Astro 플러그인이 &lt;code&gt;command === &apos;dev&apos;&lt;/code&gt; 일 때만 라우트를 주입하므로 프로덕션 빌드에는 단 한 줄도 섞이지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;세 도구 모두 같은 모토를 공유한다, “도구가 글의 모양을 결정한다”. 좋은 도구는 새로운 형태의 글을 쓰게 만든다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-콘텐츠-에디터-_editor&quot;&gt;1. 콘텐츠 에디터 (&lt;code&gt;/_editor&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;가장 자주 쓰는 도구. 글, 위키, 노트, 출처 자료를 모두 여기서 작성한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/editor-overview.png&quot; alt=&quot;콘텐츠 에디터 메인 화면, 좌측 파일 트리, 중앙 마크다운 편집기, 우측 실시간 미리보기&quot;&gt;&lt;/p&gt;
&lt;p&gt;화면을 펼치면 세 칸이다. 왼쪽은 컬렉션별 파일 트리, 가운데는 마크다운 작성 영역과 툴바, 오른쪽은 분할 미리보기. 글이 자라는 모양을 실시간으로 보면서 쓸 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;11-파일-트리-단순한-탐색기가-아니다&quot;&gt;1.1 파일 트리, 단순한 탐색기가 아니다&lt;/h3&gt;
&lt;p&gt;좌측 사이드바는 &lt;code&gt;posts&lt;/code&gt;, &lt;code&gt;wiki&lt;/code&gt;, &lt;code&gt;notes&lt;/code&gt;, &lt;code&gt;sources&lt;/code&gt; 네 컬렉션을 그대로 보여준다. 폴더는 펼치고 접을 수 있고, 그 상태는 &lt;code&gt;localStorage&lt;/code&gt; 에 저장돼서 다음번 페이지를 열어도 유지된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;검색 입력&lt;/strong&gt; : 파일 이름으로 즉시 필터, ESC 로 초기화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;우클릭 메뉴&lt;/strong&gt; : 새 파일, 새 폴더, 이름 변경, 삭제, 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;드래그-드롭 이동&lt;/strong&gt; : 폴더 사이로 파일을 끌어서 옮길 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션 복원&lt;/strong&gt; : 마지막으로 열었던 파일을 자동으로 다시 열고, 커서/스크롤 위치까지 복원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;12-마크다운-툴바-37개-버튼의-의미&quot;&gt;1.2 마크다운 툴바, 37개 버튼의 의미&lt;/h3&gt;
&lt;p&gt;툴바는 “마크다운 문법을 외우지 않아도 글을 쓸 수 있게” 가 목표였다. 그래서 자주 쓰는 모든 형태를 버튼으로 빼놓았다.&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;그룹&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;버튼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;텍스트 포맷&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;굵게 (⌘B), 기울임 (⌘I), 취소선, 인라인 코드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;링크·미디어&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;링크 (⌘K), 이미지, URL 미리보기, URL ↔ 카드 토글&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;헤딩&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;H1, H2, H3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;리스트·인용&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;목록, 번호 목록, 할 일, 인용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;코드·표&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;코드 블록, 표, 구분선&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;콜아웃 ▾&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Note, Tip, Important, Warning, Caution&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;CodeWithOutput ▾&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단일 언어, 멀티 언어 (variants)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Mermaid ▾&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Flowchart, Sequence, State, Class, ER Diagram&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;🎬 Animation ▾&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;스튜디오에서 만든 애니메이션 검색·삽입&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Frontmatter ▾&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;커버·썸네일·시리즈·카테고리·태그·출처 관리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;위키링크&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이중 대괄호 문법의 위키 페이지 링크 삽입&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;콜아웃 드롭다운을 열면 GitHub 스타일 5종이 한눈에 들어온다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/editor-toolbar-callout.png&quot; alt=&quot;에디터 툴바의 콜아웃 드롭다운, Note·Tip·Important·Warning·Caution 5종&quot;&gt;&lt;/p&gt;
&lt;p&gt;Mermaid 드롭다운도 마찬가지다. Flowchart 부터 ER Diagram 까지 자주 쓰는 다섯 가지가 템플릿으로 들어있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/editor-toolbar-mermaid.png&quot; alt=&quot;에디터 툴바의 Mermaid 드롭다운, 5가지 다이어그램 템플릿&quot;&gt;&lt;/p&gt;
&lt;p&gt;Frontmatter 드롭다운은 글 메타데이터를 본문 위로 끌어오지 않고 바로 편집할 수 있게 해준다. 커버 이미지, 썸네일, 시리즈, 카테고리, 태그, 출처를 하나의 메뉴에서 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/editor-frontmatter.png&quot; alt=&quot;에디터 툴바의 Frontmatter 드롭다운, 6가지 메타데이터 편집 메뉴&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;13-이미지-다이얼로그-모든-경로를-한-곳에서&quot;&gt;1.3 이미지 다이얼로그, 모든 경로를 한 곳에서&lt;/h3&gt;
&lt;p&gt;이미지 버튼을 누르면 다음 옵션을 한 화면에서 정리한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;용도&lt;/strong&gt; : 본문 삽입 / 커버 이미지 / 썸네일&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이미지 소스&lt;/strong&gt; : 파일 업로드 (or 클립보드 붙여넣기) / 외부 URL 다운로드 저장 / 외부 URL 그대로 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대체 텍스트&lt;/strong&gt; : a11y/SEO 를 위해 필수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;너비&lt;/strong&gt; : 원본·25%·50%·75%·100% 또는 직접 입력&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정렬&lt;/strong&gt; : 기본 (인라인) / 왼쪽 / 가운데 / 오른쪽&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/editor-image-dialog.png&quot; alt=&quot;이미지 삽입 다이얼로그, 용도·소스·alt·너비·정렬을 한 화면에서 설정&quot;&gt;&lt;/p&gt;
&lt;p&gt;업로드한 파일은 &lt;code&gt;public/uploads/&lt;/code&gt; 에 타임스탬프 + 랜덤 슬러그로 저장되고, frontmatter 의 커버를 고르면 자동으로 &lt;code&gt;cover&lt;/code&gt; / &lt;code&gt;coverAlt&lt;/code&gt; 가 채워진다.&lt;/p&gt;
&lt;h3 id=&quot;14-git-패널-에디터를-떠나지-않는다&quot;&gt;1.4 Git 패널, 에디터를 떠나지 않는다&lt;/h3&gt;
&lt;p&gt;상단 ”🌿 Git” 버튼을 누르면 변경 사항을 그대로 다룰 수 있다. 브랜치를 갈아끼우거나 stash 를 관리하는 일까지 모두 같은 화면에서 끝낸다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/editor-git-panel.png&quot; alt=&quot;Git 패널, 브랜치 전환·스태시·파일 스테이징·diff 보기&quot;&gt;&lt;/p&gt;
&lt;p&gt;지원하는 작업은 아래와 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 브랜치 표시, 앞/뒤 커밋 카운트 (↑/↓)&lt;/li&gt;
&lt;li&gt;파일별 상태 배지 : 수정 / 추가 / 삭제 / 이름변경 / 추적 안 됨 / 충돌&lt;/li&gt;
&lt;li&gt;체크박스로 스테이징, amend 옵션 포함 커밋&lt;/li&gt;
&lt;li&gt;브랜치 전환 / 새 브랜치 생성&lt;/li&gt;
&lt;li&gt;Stash 목록 보기·적용&lt;/li&gt;
&lt;li&gt;Push / Pull&lt;/li&gt;
&lt;li&gt;파일별 diff 보기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;15-자주-쓰는-단축키&quot;&gt;1.5 자주 쓰는 단축키&lt;/h3&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;단축키&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘S&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;파일 저장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘B / ⌘I / ⌘K&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;굵게 / 기울임 / 링크&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘F&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;현재 파일 안에서 찾기·바꾸기 (정규식/대소문자 지원)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘⇧F&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;전체 컬렉션 grep 검색&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘Z / ⌘⇧Z&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;실행 취소 / 다시 하기&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;[[&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;위키링크 자동완성 (최대 12개 결과)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;ESC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;검색 초기화, 메뉴 닫기&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
자동 저장은 2초마다 &lt;code&gt;localStorage&lt;/code&gt; 에 백업된다. 디스크 저장은 ⌘S 로 명시적으로. 두 레이어가 있어서 실수로 탭을 닫아도 직전 상태로 복원된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;16-잘-안-보이지만-매일-도움되는-것들&quot;&gt;1.6 잘 안 보이지만 매일 도움되는 것들&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;드래그-드롭 이미지&lt;/strong&gt; : Finder 에서 textarea 위로 끌어오면 자동 업로드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;클립보드 붙여넣기 이미지&lt;/strong&gt; : 스크린샷 직후 ⌘V 한 번으로 본문에 삽입&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 리스트 계속&lt;/strong&gt; : 리스트 안에서 Enter 치면 다음 줄도 같은 들여쓰기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스크롤 동기화&lt;/strong&gt; : 편집 영역과 미리보기 영역이 함께 스크롤&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.md ↔ .mdx 토글&lt;/strong&gt; : 차트 같은 JSX 가 필요한 순간 한 번에 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-애니메이션-스튜디오-_studio&quot;&gt;2. 애니메이션 스튜디오 (&lt;code&gt;/_studio&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;말로 설명하기보다 움직임으로 보여주는 게 빠른 개념들이 있다. BFS 의 큐가 어떻게 비워지는지, async 콜백이 어떤 순서로 큐를 옮겨다니는지, TCP 핸드셰이크의 세 메시지가 어떻게 오가는지. 그런 장면을 위해 만든 SVG 애니메이션 에디터다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-elements-overview.png&quot; alt=&quot;애니메이션 스튜디오 메인 화면, 캔버스에 8종 요소(Rect, Circle, Polygon, Line, Arrow, Path, Text, Rotated)가 배치되고 타임라인에 도입·핵심·마무리 3개 챕터, 요소별 트랙이 정렬&quot;&gt;&lt;/p&gt;
&lt;p&gt;화면은 네 구역으로 나뉜다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;왼쪽&lt;/strong&gt;, 요소 추가 패널 + 캔버스 크기 프리셋&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가운데&lt;/strong&gt;, 800×500 기본 캔버스 (프리셋으로 변경 가능)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;오른쪽&lt;/strong&gt;, 선택한 요소의 속성 패널 (선택 없으면 애니메이션 메타)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;아래&lt;/strong&gt;, 챕터 / 키프레임 / 요소별 트랙이 있는 타임라인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;21-요소-타입-9종&quot;&gt;2.1 요소 타입 9종&lt;/h3&gt;
&lt;p&gt;캔버스에 그릴 수 있는 모든 도형이다. 모두 SVG 로 직접 렌더링되므로 결과물은 어디서든 깨지지 않는다.&lt;/p&gt;























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;타입&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;핵심 속성&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Rect&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x / y / width / height / cornerRadius&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;라벨 텍스트 내장, 모서리 라운드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Circle&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;cx / cy / r&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;라벨 텍스트 내장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Line&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x1 / y1 / x2 / y2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;양 끝점을 요소에 sticky 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Arrow&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x1 / y1 / x2 / y2 + arrowHead&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;앵커 시스템으로 요소 연결&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Text&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x / y / content / fontSize / color&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;캔버스에서 더블클릭으로 inline 편집&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Image&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;x / y / width / height / src&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;업로드·드래그-드롭·붙여넣기로 추가&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Path&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;d (SVG path)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;임의 곡선·다각형&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Polygon&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;points&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정다각형/꼭지점 직접 지정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;childIds&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;여러 요소를 하나로 묶음 (⌘G / ⌘⇧G)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;22-요소-추가-한-번의-클릭&quot;&gt;2.2 요소 추가, 한 번의 클릭&lt;/h3&gt;
&lt;p&gt;좌측 패널의 버튼을 누르면 해당 타입의 요소가 캔버스 중앙에 즉시 생성되고, 타임라인에 자동으로 트랙이 깔린다. 새로 추가된 요소는 곧바로 선택 상태가 되어 우측 속성 패널에서 바로 다듬을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-add-elements.gif&quot; alt=&quot;애니메이션 GIF, 빈 캔버스에 Rect, Circle, Text, Arrow 를 차례로 추가하면 캔버스에 요소가 등장하고 동시에 하단 타임라인에 트랙이 한 줄씩 추가되는 모습&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;23-속성-패널-모든-디테일을-한-곳에서&quot;&gt;2.3 속성 패널, 모든 디테일을 한 곳에서&lt;/h3&gt;
&lt;p&gt;요소를 선택하면 우측 패널이 그 요소 전용 속성으로 바뀐다. Rect 라면 위치·크기·코너 라운드·채우기·테두리·라벨·회전이 한 화면에 들어온다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-properties-rect.png&quot; alt=&quot;Rect 가 선택된 상태의 속성 패널, name·label·x·y·width·height·strokeWidth 입력 필드들이 우측에 표시되고 캔버스의 Rect 에 회전 핸들과 8방향 리사이즈 핸들이 보임&quot;&gt;&lt;/p&gt;
&lt;p&gt;값을 바꾸면 캔버스가 즉시 갱신된다. 색상 같은 시각 속성은 패널 안의 컬러 픽커로 다루고, 결과는 한 박자도 늦지 않게 반영된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-property-edit.gif&quot; alt=&quot;애니메이션 GIF, Fill Demo 라벨이 붙은 사각형의 fill 색을 보라 → 분홍 → 초록 → 노랑으로 바꿀 때마다 캔버스가 즉시 다시 칠해지는 모습&quot;&gt;&lt;/p&gt;
&lt;p&gt;타입별로 노출되는 속성도 의도적으로 다르다. Circle 은 cx/cy/r, Line/Arrow 는 x1/y1/x2/y2, Text 는 content/fontSize/color/fontWeight/textAnchor. 굳이 안 보이는 속성을 보여주지 않는 쪽이 잘못 누를 일이 적다.&lt;/p&gt;
&lt;h3 id=&quot;24-캔버스-그리는-공간&quot;&gt;2.4 캔버스, 그리는 공간&lt;/h3&gt;
&lt;p&gt;캔버스 자체에도 사소하지만 자주 쓰이는 도구가 붙어 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;프리셋 6종 캔버스 크기&lt;/strong&gt; : 8:5 (800×500), 16:9 (1280×720), 4:3 (1024×768), 1:1 (600×600), 3:1 (1200×400), 모바일 9:19 (375×812)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;격자 + 스냅&lt;/strong&gt; : &lt;code&gt;G&lt;/code&gt; 키로 토글, 8px 단위&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;줌&lt;/strong&gt; : ⌘ + 휠 스크롤로 캔버스 자체를 확대 / 축소&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;팬&lt;/strong&gt; : Space + 드래그 (Figma 와 같은 손바닥 도구)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;선택 핸들&lt;/strong&gt; : 8방향 리사이즈 + 위쪽 회전 핸들 + 앵커 포인트&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인라인 텍스트 편집&lt;/strong&gt; : 텍스트 요소를 더블클릭하면 캔버스 위에서 바로 타이핑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;드래그-드롭 이미지&lt;/strong&gt; : Finder 에서 캔버스로 끌어오면 자동 업로드 후 배치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;25-다중-선택과-정렬분배&quot;&gt;2.5 다중 선택과 정렬·분배&lt;/h3&gt;
&lt;p&gt;여러 요소를 한 번에 다루는 일이 잦아서 별도 도구 모음을 붙였다. ⌘ + 클릭이나 마퀴 드래그로 2개 이상을 골라두면 속성 패널이 다중 선택 모드로 바뀐다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-multi-select-align.png&quot; alt=&quot;3개 사각형이 선택된 상태에서 우측 패널에 다중 선택 안내 + 6방향 정렬 버튼 + 가로/세로 분포 버튼이 표시&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정렬 6종&lt;/strong&gt; : 왼쪽 / 가운데(수평) / 오른쪽 / 위 / 가운데(수직) / 아래&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분포 2종&lt;/strong&gt; : 가로 간격 균등 / 세로 간격 균등 (3개 이상 선택 시)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;함께 이동&lt;/strong&gt; : 캔버스 드래그나 화살표 키 한 번으로 선택된 모든 요소가 같은 양만큼 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-multi-align.gif&quot; alt=&quot;애니메이션 GIF, 캔버스 위에 비스듬히 흩어진 A·B·C 사각형을 선택한 뒤 왼쪽 정렬 버튼을 누르면 세 요소의 왼쪽 변이 동일 x 좌표로 정렬되고, 이어서 위쪽 정렬 버튼을 누르면 세 요소가 같은 위치로 겹쳐지는 모습&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;26-그룹화-함께-움직이는-요소&quot;&gt;2.6 그룹화, 함께 움직이는 요소&lt;/h3&gt;
&lt;p&gt;여러 요소를 ⌘G 로 묶으면 하나의 &lt;code&gt;group&lt;/code&gt; 요소로 합쳐진다. 그룹은 자체적으로 위치/크기가 없는 대신 자식 요소들의 bounding box 를 따라간다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-group.png&quot; alt=&quot;3개 요소(rect-1, circle-1, text-1)가 묶인 그룹의 속성 패널, &amp;#x22;Group 1&amp;#x22; 이름 / &amp;#x22;그룹은 자식 요소를 함께 이동합니다&amp;#x22; 안내 / &amp;#x22;그룹 해제 (⌘⇧G)&amp;#x22; 버튼 / 자식 (3) 목록이 표시&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;그룹 / 그룹 해제&lt;/strong&gt; : ⌘G / ⌘⇧G&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;드릴 인&lt;/strong&gt; : Alt+클릭으로 그룹 안의 개별 자식을 직접 선택&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;그룹 단위 이동&lt;/strong&gt; : 화살표 키 / 드래그가 자식 전체에 동일하게 적용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자식 목록&lt;/strong&gt; : 속성 패널에서 직접 확인하고 클릭으로 점프&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;27-타임라인과-챕터-시간의-모양&quot;&gt;2.7 타임라인과 챕터, 시간의 모양&lt;/h3&gt;
&lt;p&gt;스튜디오의 두 번째 본체는 타임라인이다. 모든 요소는 시간축 위에 트랙으로 존재하고, 그 위에 챕터·이펙트·키프레임이 얹힌다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chapter&lt;/strong&gt; : 의미 단위로 시간을 자른다. 라벨 + 시간 + 길이를 가지며, 선택 후 ←/→ 로 ±100ms, Shift + 로 ±1000ms 미세 조정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Effect 3종&lt;/strong&gt; : &lt;code&gt;highlight&lt;/code&gt;, &lt;code&gt;pulse&lt;/code&gt;, &lt;code&gt;flow&lt;/code&gt; 로 특정 시점에 요소를 강조&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keyframe Track&lt;/strong&gt; : 요소 속성별로 시간축에 키프레임을 찍고 드래그로 타이밍 조정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재생 컨트롤&lt;/strong&gt; : Play/Stop, Reset, 0.25 ~ 3x 속도 슬라이더&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타임라인 줌&lt;/strong&gt; : ⌘ + 휠 로 pxPerMs 조정 (값은 &lt;code&gt;localStorage(&apos;studio.timeline.pxPerMs&apos;)&lt;/code&gt; 에 저장)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;28-재생-의도한-움직임을-확인&quot;&gt;2.8 재생, 의도한 움직임을 확인&lt;/h3&gt;
&lt;p&gt;Play 버튼을 누르면 캔버스가 정의된 순서대로 요소를 등장시킨다. Entry/Exit 모드는 &lt;code&gt;instant&lt;/code&gt;, &lt;code&gt;fade&lt;/code&gt;, &lt;code&gt;slide-left/right/up/down&lt;/code&gt;, &lt;code&gt;zoom&lt;/code&gt;, &lt;code&gt;pop&lt;/code&gt; 8종이 기본 제공되고, 각각 등장 시간을 따로 줄 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-playback.gif&quot; alt=&quot;애니메이션 GIF, &amp;#x22;Studio Demo&amp;#x22; 텍스트가 fade in 으로 들어온 뒤 첫 번째 원이 zoom 으로 등장하고, 화살표가 slide-left 로 이어지며 두 번째 원과 세 번째 원이 차례로 zoom / pop 으로 나타나는 5초짜리 시연&quot;&gt;&lt;/p&gt;
&lt;p&gt;만든 애니메이션은 &lt;code&gt;loop&lt;/code&gt;, &lt;code&gt;autoplay&lt;/code&gt;, &lt;code&gt;showCaption&lt;/code&gt;, &lt;code&gt;showChapterList&lt;/code&gt; 같은 메타 설정과 함께 JSON 으로 저장돼, 블로그 본문 어디에든 한 줄로 끼워 넣을 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;29-아이콘-라이브러리-1856개&quot;&gt;2.9 아이콘 라이브러리, 1856개&lt;/h3&gt;
&lt;p&gt;처음에 아이콘이 필요할 때마다 svg 를 손으로 가져다 붙였는데, 그게 쌓이다 보니 결국 카테고리별 아이콘 라이브러리가 됐다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-icon-library.png&quot; alt=&quot;아이콘 라이브러리 다이얼로그, AWS·GCP·Azure·Cloud·DB·Lang 등 25개 카테고리로 분류된 1856개 아이콘 그리드&quot;&gt;&lt;/p&gt;
&lt;p&gt;분류는 클라우드 (AWS / GCP / Azure / Cloud), 데이터 (DB), 언어 (Lang), 프론트·백 (FE / BE / Tool / Net), 모델링 (ERD / Arrow / Doc / Device), 의사소통 (Comm), 행위 (Action), 도형 (Chart / Shape), 사람 (User), 미디어 (Media), 금융 (Finance / Crypto), 기호 (Symbol), 그리고 외부 아이콘 세트인 Tabler 166개와 Lucide 294개.&lt;/p&gt;
&lt;p&gt;검색창에 키워드만 넣으면 즉시 필터된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-icon-search.gif&quot; alt=&quot;애니메이션 GIF, 아이콘 라이브러리에서 검색창에 a → aws → database 순으로 입력할 때마다 그리드가 즉시 필터되어 1856개 → AWS 아이콘 → 22개 database 관련 아이콘으로 좁혀지는 모습&quot;&gt;&lt;/p&gt;
&lt;p&gt;선택한 아이콘은 SVG path 로 캔버스에 들어가서, 색상·크기·회전 같은 일반 도형 속성을 그대로 받는다.&lt;/p&gt;
&lt;h3 id=&quot;210-명령-팔레트-k&quot;&gt;2.10 명령 팔레트, ⌘K&lt;/h3&gt;
&lt;p&gt;자주 쓰는 명령을 손 가까이 두고 싶어서 만든 작은 검색 인터페이스. 명령 / 요소 / 태그 / 도움말 네 종류를 한 입력창에서 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-command-palette.png&quot; alt=&quot;명령 팔레트 UI, 라이브러리 열기·새 애니메이션·저장·삭제·실행 취소·다시 실행·작업 이력·재생 토글·격자 토글·단축키 보기가 단축키와 함께 나열&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;명령&lt;/strong&gt; : 라이브러리 열기, 새 애니메이션, 저장, 삭제, 실행 취소 / 다시 실행, 작업 이력, 재생 / 정지, 격자 토글, 단축키 보기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;요소 검색&lt;/strong&gt; : 캔버스에 있는 요소의 이름·라벨·id·타입을 한 번에 훑어서 즉시 선택&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;태그·도움말&lt;/strong&gt; : 보다 가벼운 검색을 위한 채널&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;211-작업-이력-시간을-되돌리는-길&quot;&gt;2.11 작업 이력, 시간을 되돌리는 길&lt;/h3&gt;
&lt;p&gt;⌘⇧H 를 누르면 작업 이력 패널이 열린다. 단순한 undo/redo 보다 한 단계 위, 모든 변경 사항이 의미 단위로 라벨링돼 있어서 과거의 한 시점을 골라 거기로 점프할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-history-panel.png&quot; alt=&quot;작업 이력 패널, &amp;#x22;현재 위치&amp;#x22; 마커 아래로 &amp;#x22;요소 추가: polygon&amp;#x22;, &amp;#x22;요소 추가: arrow&amp;#x22;, &amp;#x22;요소 추가: text&amp;#x22;, &amp;#x22;요소 추가: circle&amp;#x22;, &amp;#x22;요소 추가: rect&amp;#x22; 가 시간 순으로 나열&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;현재 위치&lt;/strong&gt; : 패널 상단에 분명한 구분선 + 라벨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;과거 항목&lt;/strong&gt; : 클릭하면 그만큼 undo 된 상태로 이동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;미래 항목&lt;/strong&gt; : 클릭하면 redo (현재가 미래의 한 시점이라면)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;라벨링 규칙&lt;/strong&gt; : &lt;code&gt;이동: client&lt;/code&gt;, &lt;code&gt;Chapter 추가: ...&lt;/code&gt;, &lt;code&gt;요소 추가: rect&lt;/code&gt; 식으로 도메인 + 대상 + 키&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HistoryKind&lt;/strong&gt; : 메타 / 추가 / 삭제 / 이동 / 스타일 / 회전 / 리사이즈 등 12 종으로 색상 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;212-단축키와-도움말&quot;&gt;2.12 단축키와 도움말&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;?&lt;/code&gt; 키를 누르면 자주 쓰는 단축키가 한 화면에 모인다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/studio-help-dialog.png&quot; alt=&quot;도움말 다이얼로그, ⌘S 저장, ⌘Z 실행 취소, ⌘C/V/X 복사 / 붙여넣기 / 잘라내기, Delete 선택 요소 삭제, Esc 선택 해제, 드래그(캔버스) 요소 이동, 드래그(요소 위 dot) 화살표 연결, 드래그(선/화살표 끝점) sticky 재배치, 드래그(회전 핸들) 회전, 드래그(타임라인 바 우측) duration 조정 단축키가 표로 정리&quot;&gt;&lt;/p&gt;
&lt;p&gt;전체 단축키 요약 :&lt;/p&gt;





































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;단축키&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;동작&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘S&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;저장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘Z / ⌘⇧Z&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;실행 취소 / 다시 하기&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘C / ⌘V / ⌘X&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;복사 / 붙여넣기 / 잘라내기&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘D&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;복제&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘A&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;전체 선택&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘G / ⌘⇧G&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;그룹화 / 그룹 해제&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘K&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;명령 팔레트&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘⇧H&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;작업 이력&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;G&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;격자 토글&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Space + 드래그&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;캔버스 팬&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;⌘ + 휠&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;캔버스 줌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;방향키&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;±1px 이동 (Shift 와 함께 ±10px)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Tab / ⇧Tab&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;요소 순환&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Delete / Backspace&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;선택 요소 삭제&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;? / ⇧?&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;도움말&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;213-데이터-모델&quot;&gt;2.13 데이터 모델&lt;/h3&gt;
&lt;p&gt;스튜디오에서 만든 모든 애니메이션은 결국 하나의 JSON 으로 직렬화된다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AnimationDef&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  description&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  duration&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;       &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  canvas&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  settings&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    loop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;autoplay&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    showCaption&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;showChapterList&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  elements&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AnimationElement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[];   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// rect | circle | ... | group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  chapters&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Chapter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  effects&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AnimationEffect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[];     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// highlight | pulse | flow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;public/animations/*.json&lt;/code&gt; 에 저장된 파일은 본문에서 다음과 같이 호출한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;```anim:bfs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;version&lt;/code&gt; 을 두 가지로 유지하는 건 의도적이다. v3 시절 만든 29개 파일이 그대로 로드되고, v4 에서 추가한 &lt;code&gt;group&lt;/code&gt; 타입은 옵셔널이라 옛 파일을 깨뜨리지 않는다. 새 타입이 더해질 때마다 union 에 한 줄 더하고, 마이그레이션 헬퍼를 옆에 두는 식으로 키워간다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-chartjs-에디터-_chart-editor&quot;&gt;3. Chart.js 에디터 (&lt;code&gt;/_chart-editor&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;벤치마크 결과, 성능 비교, 분포 같은 글에서는 차트 한 장이 표 열 개보다 빠르다. 그래서 &lt;a href=&quot;https://www.chartjs.org/&quot;&gt;Chart.js&lt;/a&gt; 차트를 만들어 MDX 본문에 붙여넣을 수 있는 작은 에디터를 따로 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/chart-editor-overview.png&quot; alt=&quot;차트 에디터 메인 화면, 좌측에 폼 입력·우측에 실시간 Line 차트 미리보기·하단에 MDX 코드 출력&quot;&gt;&lt;/p&gt;
&lt;p&gt;좌측에서 값을 바꾸면 우측 미리보기가 200ms 디바운스 후 다시 그려지고, 동시에 아래쪽 &lt;code&gt;MDX 코드&lt;/code&gt; 패널에 본문에 붙여넣을 수 있는 &lt;code&gt;&amp;#x3C;!-- mdx-component: ChartJs --&gt;&lt;/code&gt; 스니펫이 갱신된다.&lt;/p&gt;
&lt;h3 id=&quot;31-지원-차트-타입&quot;&gt;3.1 지원 차트 타입&lt;/h3&gt;
&lt;p&gt;내장 컴포넌트 (&lt;code&gt;&amp;#x3C;!-- mdx-component: ChartJs --&gt;&lt;/code&gt;) 는 Chart.js 의 8종 차트를 모두 지원하고, 에디터 UI 는 그중 자주 쓰는 6종을 드롭다운으로 제공한다.&lt;/p&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;차트&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;에디터&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;축&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Bar&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Line&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Pie&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Doughnut&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Radar&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Scatter&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Bubble&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;(수동 작성)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Polar Area&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;(수동 작성)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;32-5개-내장-템플릿&quot;&gt;3.2 5개 내장 템플릿&lt;/h3&gt;
&lt;p&gt;처음부터 빈 JSON 을 채우는 건 피곤하다. 그래서 자주 쓰던 형태 다섯 가지를 템플릿 버튼으로 빼놨다.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;템플릿&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;용도 예시&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Line (기본)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;시계열, 단순 트렌드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Bar (비교)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;A vs B 처럼 두 값 비교&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Line (Log Scale)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;자릿수가 큰 값 비교 (&lt;code&gt;scales.y.type: &apos;logarithmic&apos;&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Horizontal Bar&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;라벨이 긴 값들의 순위 비교 (&lt;code&gt;indexAxis: &apos;y&apos;&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Pie&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;비율 표시&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Pie 템플릿을 누르면 즉시 미리보기와 MDX 코드가 함께 바뀐다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2026/06/25/chart-editor-pie.png&quot; alt=&quot;차트 에디터에서 Pie 템플릿을 선택한 화면, Index Scan vs Full Scan 비율을 보여주는 파이 차트와 그에 해당하는 MDX 코드&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;33-자동으로-신경-써주는-것들&quot;&gt;3.3 자동으로 신경 써주는 것들&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;테마 자동 대응&lt;/strong&gt; : &lt;code&gt;&amp;#x3C;html class=&quot;dark&quot;&gt;&lt;/code&gt; 가 토글되면 &lt;code&gt;MutationObserver&lt;/code&gt; 가 감지해서 축 라벨·그리드 색상을 즉시 갱신&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 옵션 병합&lt;/strong&gt; : &lt;code&gt;responsive&lt;/code&gt;, &lt;code&gt;maintainAspectRatio: false&lt;/code&gt;, 범례 위치·폰트, 그리드 색상 등은 컴포넌트가 자동으로 채움&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인스턴스 재사용&lt;/strong&gt; : 타입이 바뀔 때만 차트 인스턴스 재생성, 나머지는 &lt;code&gt;chart.update()&lt;/code&gt; 로 부드럽게 갱신&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON 파싱 에러 표시&lt;/strong&gt; : data 와 options 각각 별도 에러 메시지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;34-본문에서-사용하기&quot;&gt;3.4 본문에서 사용하기&lt;/h3&gt;
&lt;p&gt;MDX 파일에서는 &lt;code&gt;import&lt;/code&gt; 없이 바로 사용할 수 있다 (&lt;code&gt;astro-auto-import&lt;/code&gt; 가 모든 MDX 에 자동 주입).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mdx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;!--&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; mdx-component:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChartJs&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; --&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;code&gt;client:visible&lt;/code&gt; 디렉티브를 빠뜨리면 SSR 시점에 렌더되면서 차트 라이브러리가 실행되지 않는다. 차트는 무조건 클라이언트 hydration 후에 그려야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
Pie, Doughnut, Radar, Polar Area 같은 radial 차트에는 &lt;code&gt;scales&lt;/code&gt; 옵션이 없다. 넣으면 Chart.js 가 무시하지만, 헷갈리지 않게 빼두는 게 깔끔하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;35-실제-사용-예&quot;&gt;3.5 실제 사용 예&lt;/h3&gt;
&lt;p&gt;차트 에디터로 만든 차트가 실제로 들어가 있는 글들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[CPU vs GPU vs TPU vs NPU, 네 종류 프로세서의 원리와 최근 동향|CPU vs GPU vs TPU vs NPU]] : 수평 막대 + 로그 스케일&lt;/li&gt;
&lt;li&gt;[[UNION vs OR vs IN 벤치마크 (1), 실험 설계와 결과|UNION vs OR vs IN 벤치마크 (1)]] : 다중 시리즈 선 차트&lt;/li&gt;
&lt;li&gt;[[브라우저 기반 통신 총정리, HTTP부터 WebRTC까지, 어디에 부하가 가는가|브라우저 기반 통신 총정리]] : 수평 막대 차트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;도구가-자라는-방향&quot;&gt;도구가 자라는 방향&lt;/h2&gt;
&lt;p&gt;세 도구는 모두 “필요해서 만든다 → 자주 쓰니 다듬는다 → 다듬다 보니 새로운 기능이 생긴다” 의 사이클로 자라왔다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;에디터&lt;/strong&gt; 는 단순한 textarea + preview 에서 시작해서 Git 패널, 위키링크 자동완성, 전체 파일 grep 까지 도달했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스튜디오&lt;/strong&gt; 는 [[BFS|너비 우선 탐색]] 한 장면을 그리려고 만든 SVG 손그림 에디터에서 시작해서, 지금은 타임라인·키프레임·그룹·작업 이력까지 갖춘 작은 모션 도구가 됐다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;차트 에디터&lt;/strong&gt; 는 글 안에 차트를 넣을 때마다 JSON 을 손으로 쓰다가 지쳐서 만든, 가장 늦게 합류한 도구다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음에 손볼 부분도 비슷한 결로 정해둔다. 위키와의 연동을 더 깊이 가져가는 일 (AI 가 모은 초안을 에디터 안에서 곧장 다듬기), 스튜디오에서 만든 애니메이션의 한 프레임을 차트 에디터로 보내거나, 반대로 차트의 한 상태를 애니메이션 키프레임으로 묶는 일 같은.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
이 글에 등장한 모든 스크린샷도 &lt;code&gt;/_editor&lt;/code&gt; 의 이미지 다이얼로그를 거쳐 본문에 자동 삽입됐다. 도구가 도구 자신을 소개하는 글의 자료가 되는 작은 자기 참조.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;도구를 만든 후 가장 크게 달라진 건, 글을 시작하는 속도와 글을 끝내지 못하는 빈도 둘 다 줄었다는 점이다. 마찰을 줄이는 일이 결국 더 많은 글을 남긴다, 라는 가설을 매일 검증하는 중이다.&lt;/p&gt;</content:encoded><category>blog</category><category>devtools</category><category>editor</category><category>studio</category><category>chart</category><category>astro</category><author>koa (김신건)</author></item><item><title>[Leetcode] 2804 Array Prototype ForEach</title><link>https://shinkeonkim.com/posts/problem-solving/leetcode/algorithm/2804-array-prototype-foreach/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/problem-solving/leetcode/algorithm/2804-array-prototype-foreach/</guid><description>Leetcode 2804번 Array Prototype ForEach 풀이. callback 내부 this 를 context 로 바인딩하는 다섯 가지 방법 (call, apply, bind, 객체 메서드, 그리고 화살표 함수 함정) 을 비교한다.</description><pubDate>Thu, 18 Jun 2026 05:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;문제-요약&quot;&gt;문제 요약&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.com/problems/array-prototype-foreach&quot;&gt;2804. Array Prototype ForEach&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;표준 &lt;code&gt;Array.prototype.forEach&lt;/code&gt; 의 동작을 직접 구현하는 문제다. 두 인자를 받는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;callback(value, index, array)&lt;/code&gt; 가 배열의 각 요소마다 호출된다.&lt;/li&gt;
&lt;li&gt;두 번째 인자 &lt;code&gt;context&lt;/code&gt; 가 주어지면, &lt;code&gt;callback&lt;/code&gt; 내부의 &lt;code&gt;this&lt;/code&gt; 가 &lt;code&gt;context&lt;/code&gt; 를 가리켜야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;표준 메서드를 쓰지 말고 직접 구현하라는 제약이 붙는다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 여기서 callback 을 호출하면서 this 를 context 로 바꾸어야 한다&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;표면적으로는 단순한 반복문이다. 다만 “함수의 &lt;code&gt;this&lt;/code&gt; 를 외부에서 지정한다” 는 요건이 [[function]] 의 &lt;code&gt;this&lt;/code&gt; 동적 바인딩, [[arrow function]] 의 lexical &lt;code&gt;this&lt;/code&gt;, 그리고 &lt;code&gt;call&lt;/code&gt; / &lt;code&gt;apply&lt;/code&gt; / &lt;code&gt;bind&lt;/code&gt; 까지 한 자리에 모이는 좋은 출발점이라 정리할 가치가 있다.&lt;/p&gt;
&lt;p&gt;검증용 예시:&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;같은 결과를 만들어내는 다섯 가지 길을 차례로 본다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-call-사용-정석&quot;&gt;1. call 사용 (정석)&lt;/h2&gt;
&lt;h3 id=&quot;구현&quot;&gt;구현&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    callback.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(context, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i], i, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;동작-원리&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Function.prototype.call(thisArg, ...args)&lt;/code&gt; 는 &lt;strong&gt;함수를 즉시 실행하면서 &lt;code&gt;this&lt;/code&gt; 를 &lt;code&gt;thisArg&lt;/code&gt; 로 지정&lt;/strong&gt; 한다. 인자는 일반 호출처럼 콤마로 나열한다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;&lt;code&gt;forEach&lt;/code&gt; 구현에 그대로 옮기면 매 반복마다 &lt;code&gt;callback&lt;/code&gt; 을 호출할 때 &lt;code&gt;this&lt;/code&gt; 가 &lt;code&gt;context&lt;/code&gt; 로 묶인다. 표준 명세 &lt;a href=&quot;https://tc39.es/ecma262/#sec-array.prototype.foreach&quot;&gt;Array.prototype.forEach&lt;/a&gt; 도 내부적으로 &lt;code&gt;Call(callbackfn, T, ...)&lt;/code&gt; 형태, 즉 같은 방식으로 정의돼 있다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;장단점&quot;&gt;장단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;표준 동작과 가장 닮았다.&lt;/li&gt;
&lt;li&gt;반복문 안이 한 줄로 끝나 읽기 쉽다.&lt;/li&gt;
&lt;li&gt;매 반복마다 &lt;code&gt;call&lt;/code&gt; 한 번씩의 호출 비용이 있지만 실측으로 드러나는 수준은 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;면접/코딩 테스트에서 기대되는 답이 보통 이 형태다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-apply-사용&quot;&gt;2. apply 사용&lt;/h2&gt;
&lt;h3 id=&quot;구현-1&quot;&gt;구현&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    callback.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(context, [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i], i, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;동작-원리-1&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Function.prototype.apply(thisArg, argArray)&lt;/code&gt; 는 &lt;code&gt;call&lt;/code&gt; 과 동일하게 함수를 즉시 실행하면서 &lt;code&gt;this&lt;/code&gt; 를 지정한다. 차이는 &lt;strong&gt;인자를 배열로 전달&lt;/strong&gt; 한다는 것뿐이다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;장단점-1&quot;&gt;장단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;인자가 동적으로 정해지는 (이미 배열 형태로 들고 있는) 경우 편리하다.&lt;/li&gt;
&lt;li&gt;이 문제처럼 인자 개수가 고정 (3 개) 이라면 매 반복마다 &lt;strong&gt;임시 배열을 새로 만드는 비용&lt;/strong&gt; 만 더해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
ES6 의 &lt;code&gt;...spread&lt;/code&gt; 이후로 &lt;code&gt;apply&lt;/code&gt; 의 쓰임이 많이 줄었다. &lt;code&gt;fn.apply(ctx, args)&lt;/code&gt; 는 &lt;code&gt;fn.call(ctx, ...args)&lt;/code&gt; 로 대체할 수 있기 때문이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-bind-사용&quot;&gt;3. bind 사용&lt;/h2&gt;
&lt;h3 id=&quot;구현-2&quot;&gt;구현&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; bound&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; callback.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(context);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    bound&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i], i, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;동작-원리-2&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Function.prototype.bind(thisArg)&lt;/code&gt; 는 &lt;strong&gt;함수를 실행하지 않는다&lt;/strong&gt;. 대신 &lt;code&gt;this&lt;/code&gt; 가 미리 묶인 &lt;strong&gt;새 함수 객체&lt;/strong&gt; 를 반환한다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;장단점-2&quot;&gt;장단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;반복문이 그냥 &lt;code&gt;bound(...)&lt;/code&gt; 가 되어 가장 깔끔해 보인다.&lt;/li&gt;
&lt;li&gt;매 반복마다 &lt;code&gt;call&lt;/code&gt; 을 부르지 않아도 된다.&lt;/li&gt;
&lt;li&gt;대신 &lt;strong&gt;새 함수 객체를 한 번 생성&lt;/strong&gt; 한다. 반복 호출이 많다면 절약이 되지만, 한 번 쓰고 버리는 경우에는 오히려 손해다.&lt;/li&gt;
&lt;li&gt;만약 &lt;code&gt;bound&lt;/code&gt; 를 반복문 안에서 만들면 (&lt;code&gt;callback.bind(context)&lt;/code&gt; 를 매 반복마다 호출) 가장 비싼 선택이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-객체-메서드-방식-call-의-내부-구현&quot;&gt;4. 객체 메서드 방식 (call 의 내부 구현)&lt;/h2&gt;
&lt;h3 id=&quot;구현-3&quot;&gt;구현&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Symbol&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  context[key] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; callback;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    context[key](&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i], i, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  delete&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context[key];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;동작-원리-3&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p&gt;JavaScript 에서 함수 호출 구문 &lt;code&gt;obj.fn()&lt;/code&gt; 의 &lt;code&gt;this&lt;/code&gt; 는 &lt;strong&gt;호출 직전에 &lt;code&gt;.&lt;/code&gt; 좌측이 무엇이었는지&lt;/strong&gt; 로 결정된다 ([[function]] 의 &lt;code&gt;this&lt;/code&gt; 동적 바인딩). 즉,&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;context.temp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; callback;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;context.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;temp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 이 호출 시점에 this === context&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 임시로 객체의 메서드로 만든 뒤 호출하면 &lt;code&gt;call&lt;/code&gt; 없이도 &lt;code&gt;this&lt;/code&gt; 를 바꿀 수 있다.&lt;/p&gt;
&lt;p&gt;이 방식은 사실 &lt;code&gt;Function.prototype.call&lt;/code&gt; 을 직접 구현해야 하는 면접 문제의 표준 해법으로 자주 등장한다. &lt;code&gt;Symbol()&lt;/code&gt; 로 고유 키를 만들어 기존 속성과의 충돌을 막고, 끝난 뒤 &lt;code&gt;delete&lt;/code&gt; 로 흔적을 지운다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;장단점-3&quot;&gt;장단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;this&lt;/code&gt; 바인딩의 메커니즘을 가장 노골적으로 보여준다.&lt;/li&gt;
&lt;li&gt;인자로 받은 &lt;code&gt;context&lt;/code&gt; 객체를 &lt;strong&gt;직접 변형&lt;/strong&gt; 한다. 호출 도중 &lt;code&gt;throw&lt;/code&gt; 가 나면 &lt;code&gt;delete&lt;/code&gt; 까지 도달하지 못해 키가 남는다 (&lt;code&gt;try / finally&lt;/code&gt; 로 보완 필요).&lt;/li&gt;
&lt;li&gt;frozen 객체 (&lt;code&gt;Object.freeze&lt;/code&gt;), Proxy, getter/setter 가 걸린 객체에는 통하지 않는다.&lt;/li&gt;
&lt;li&gt;실무에서는 거의 쓰지 않는다. “원리를 설명할 때” 용도.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
입력으로 받은 객체를 임의로 수정하는 것은 사이드 이펙트의 원천이다. 라이브러리/표준 메서드 구현에서는 피해야 할 패턴이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-화살표-함수-arrow-function-주의사항&quot;&gt;5. 화살표 함수 (Arrow Function) 주의사항&lt;/h2&gt;
&lt;p&gt;위 네 가지 방식 모두 한 가지 가정에 기대고 있다. &lt;strong&gt;callback 이 일반 함수일 것&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;[[arrow function]] 은 자체 &lt;code&gt;this&lt;/code&gt; 를 가지지 않는다. 자신이 선언된 위치의 [[Lexical Environment]] 에 있는 &lt;code&gt;this&lt;/code&gt; 를 그대로 본다. &lt;code&gt;call&lt;/code&gt; / &lt;code&gt;apply&lt;/code&gt; / &lt;code&gt;bind&lt;/code&gt; 모두 화살표 함수의 &lt;code&gt;this&lt;/code&gt; 를 바꿀 수 없다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“화살표 함수의 this 는 call 로도 안 바뀐다”
language=“javascript”
code={`const fn = () =&gt; {
console.log(this);
};&lt;/p&gt;
&lt;p&gt;fn.call({ name: ‘Tom’ });&lt;code&gt;}   output={&lt;/code&gt;(전역 객체 또는 상위 스코프의 this, strict 모듈에서는 undefined)`}
/&gt;&lt;/p&gt;
&lt;p&gt;즉, 문제의 검증 케이스가 &lt;code&gt;callback&lt;/code&gt; 으로 화살표 함수를 넘긴다면 &lt;strong&gt;어떤 구현을 써도 &lt;code&gt;context&lt;/code&gt; 가 반영되지 않는다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;같은 lexical vs dynamic this 차이는 [[arrow function]] 의 setInterval 예시에서도 동일하게 드러난다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
LeetCode 의 채점 케이스도 이 차이를 노린다. &lt;code&gt;(val, i, arr) =&gt; arr[i] = this&lt;/code&gt; 같은 화살표 콜백을 넘겼을 때 &lt;code&gt;this&lt;/code&gt; 가 정의 시점의 모듈 스코프 (테스트 러너가 화살표 함수를 평가한 환경) 로 묶이는 것을 확인하는 식이다. 구현이 잘못된 게 아니라, 화살표 함수의 정의 그 자체가 그렇게 동작한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;call&lt;/code&gt; / &lt;code&gt;apply&lt;/code&gt; / &lt;code&gt;bind&lt;/code&gt; 는 모두 &lt;strong&gt;일반 함수에만 의미가 있다.&lt;/strong&gt; 이 사실을 모르고 &lt;code&gt;forEach&lt;/code&gt; 의 callback 으로 화살표 함수를 쓰면 디버그하기 까다로운 버그가 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;각-방식-비교&quot;&gt;각 방식 비교&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;즉시 실행&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;this 지정&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;함수 객체 생성&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;입력 객체 변형&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;call&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;apply&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;bind&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O (한 번)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;객체 메서드&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;O (Symbol 키)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;call&lt;/code&gt; 과 &lt;code&gt;apply&lt;/code&gt; 는 호출 시 비용 외에 부산물이 없다. &lt;code&gt;bind&lt;/code&gt; 는 함수 객체 하나를 새로 만들지만 반복 호출 비용은 가장 적다. 객체 메서드 방식은 호출 비용 자체는 가장 적지만 입력 객체를 변형한다는 점에서 사이드 이펙트가 가장 크다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;결론-무엇을-골라야-하는가&quot;&gt;결론, 무엇을 골라야 하는가&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    callback.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(context, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i], i, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 풀이가 가장 무난한 선택이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;명세 &lt;a href=&quot;https://tc39.es/ecma262/#sec-array.prototype.foreach&quot;&gt;Array.prototype.forEach&lt;/a&gt; 의 내부 알고리즘이 사실상 &lt;code&gt;Call(callbackfn, thisArg, ...)&lt;/code&gt; 라, 그것과 가장 가깝다.&lt;/li&gt;
&lt;li&gt;코드가 짧고 읽기 쉽다.&lt;/li&gt;
&lt;li&gt;입력 객체를 변형하지 않는다.&lt;/li&gt;
&lt;li&gt;새로운 함수 객체를 만들지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;bind&lt;/code&gt; 는 같은 callback 을 여러 곳에서 재사용할 때 의미가 있다. 객체 메서드 방식은 &lt;code&gt;call&lt;/code&gt; 의 동작 원리를 묻는 면접에서 별도로 등장한다. 둘 다 이 문제의 정답으로는 과한 선택이다.&lt;/p&gt;
&lt;p&gt;화살표 함수에 관한 함정은 &lt;code&gt;forEach&lt;/code&gt; 만의 문제가 아니다. &lt;code&gt;setTimeout&lt;/code&gt; 의 콜백, 이벤트 리스너, &lt;code&gt;Promise.then&lt;/code&gt; 의 핸들러 등 &lt;strong&gt;외부에서 &lt;code&gt;this&lt;/code&gt; 를 지정하려는 모든 API&lt;/strong&gt; 와 화살표 함수는 어울리지 않는다. 이 부분의 메커니즘은 [[비동기와 타이밍, 콜백부터 async/await까지의 발전사]] 의 콜백 절에서도 같은 언어로 설명된다.&lt;/p&gt;</content:encoded><category>Leetcode</category><category>JavaScript</category><category>this</category><author>koa (김신건)</author></item><item><title>브라우저 기반 통신 총정리, HTTP부터 WebRTC까지, 어디에 부하가 가는가</title><link>https://shinkeonkim.com/posts/2026-06-16-browser-communication/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-16-browser-communication/</guid><description>HTTP/1.1·2·3, Long Polling, Server-Sent Events, WebSocket, WebRTC, WebTransport를 Stateless/Stateful 관점에서 정리하고, 클라이언트·서버·네트워크 부하를 실측 수치로 비교한다.</description><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;시작-왜-이렇게-많은-통신-방식이-있나&quot;&gt;시작, 왜 이렇게 많은 통신 방식이 있나&lt;/h2&gt;
&lt;p&gt;웹은 처음엔 단순했다. 브라우저가 페이지를 요청하면 서버가 HTML을 돌려주는 것. &lt;strong&gt;요청 한 번, 응답 한 번.&lt;/strong&gt; HTTP/1.0의 모든 것.&lt;/p&gt;
&lt;p&gt;그런데 시간이 흐르며 요구사항이 늘어났다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“&lt;strong&gt;여러 자원을 동시에 받고 싶다&lt;/strong&gt;” → HTTP/1.1 keep-alive, HTTP/2 multiplexing&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;서버가 먼저 알려주고 싶다&lt;/strong&gt;” → Long Polling, Server-Sent Events&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;양쪽이 자유롭게 주고받고 싶다&lt;/strong&gt;” → WebSocket&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;서버를 거치지 말고 둘이 직접 연결되고 싶다&lt;/strong&gt;” → WebRTC&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;HTTP/3 위에서 더 가볍게 양방향 stream이 필요하다&lt;/strong&gt;” → WebTransport&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;각 통신 방식은 직전 방식의 한계를 푸는 한 걸음씩이다. 이 글에서는 각 방식의 &lt;strong&gt;통신 주체·프로토콜·내용·리소스 사용량&lt;/strong&gt; 을 차례로 보고, “&lt;strong&gt;부하는 어디에 가는가&lt;/strong&gt;” 라는 질문에 답하는 한 장의 표와 실측 차트로 정리한다.&lt;/p&gt;
&lt;p&gt;먼저 모든 선택의 첫 단추인 &lt;strong&gt;Stateless vs Stateful&lt;/strong&gt; 부터.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;한-단계-뒤로-stateless-vs-stateful-이라는-첫-단추&quot;&gt;한 단계 뒤로, Stateless vs Stateful 이라는 첫 단추&lt;/h2&gt;
&lt;p&gt;[[Stateless]] vs [[Stateful]] 의 선택은 통신 방식 선택보다 먼저 와야 한다. 이 결정이 &lt;strong&gt;수평 확장 전략·인프라 복잡도·운영 비용&lt;/strong&gt;을 결정하기 때문이다.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateless&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateful&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;서버 메모리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;요청 처리에만 필요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;활성 세션마다 메모리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;수평 확장&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매우 쉬움, 어느 서버든 OK&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;[[Sticky Session]]&lt;/strong&gt; 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;장애 복원&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 교체 무영향&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;세션 손실 (외부 store 시 완화)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;로드밸런서&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단순 round-robin&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;client → server 일관 라우팅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;캐싱&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;URL 기반 캐시 효율적&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;캐시 효율 낮음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;각 통신 방식의 상태성:&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Stateless / Stateful&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP (1.1/2/3)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateless&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP + 쿠키/JWT&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;상태를 &lt;em&gt;클라이언트&lt;/em&gt; 에 위임한 Stateless&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Long Polling&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless (각 요청 독립)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Server-Sent Events&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt; (지속 연결, 단방향)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt; (지속 연결, 양방향)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebRTC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Stateful&lt;/strong&gt; (P2P 세션)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
“Stateful 한 통신을 쓰겠다”는 결정은 곧 “&lt;strong&gt;서버 메모리에 연결 상태가 N개 쌓일 것을 감수하고, [[Sticky Session]] 또는 외부 라우팅 인프라를 운영하겠다&lt;/strong&gt;” 는 결정과 같다. 단순히 “기능적으로 필요하다”는 이유만으로 가볍게 선택하면 운영 비용이 따라온다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 관점을 가지고 HTTP부터 보자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;기본-http-stateless-의-정수&quot;&gt;기본 HTTP, Stateless 의 정수&lt;/h2&gt;
&lt;h3 id=&quot;http11-tcp-한-연결-위에서-순차적-요청응답&quot;&gt;HTTP/1.1: [[TCP]] 한 연결 위에서 순차적 요청·응답&lt;/h3&gt;
&lt;p&gt;가장 익숙한 형태. 브라우저가 GET/POST 등의 요청을 보내면 서버가 응답한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /api/users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Accept&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:[{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;alice&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}]}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;keep-alive-연결을-재사용하는-첫-번째-시도&quot;&gt;Keep-Alive, 연결을 재사용하는 첫 번째 시도&lt;/h4&gt;
&lt;p&gt;HTTP/1.0 의 큰 문제는 &lt;strong&gt;요청마다 [[TCP]] 연결을 새로 맺는 것&lt;/strong&gt;이었다. TCP 3-way handshake에 1 RTT, [[TLS]] handshake에 1-2 RTT, 모바일 네트워크에서 100~300 ms 가 매 요청마다 추가됐다.&lt;/p&gt;
&lt;p&gt;HTTP/1.1 은 기본적으로 &lt;code&gt;Connection: keep-alive&lt;/code&gt; 를 활성화해 한 TCP 연결을 여러 요청에 재사용한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /api/users &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Connection&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; keep-alive    ← (HTTP/1.1 기본값이라 생략 가능)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /api/orders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Connection&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; keep-alive    ← 같은 TCP 연결로 다음 요청&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버는 응답 후 연결을 닫지 않고 일정 시간 대기 (&lt;code&gt;Keep-Alive: timeout=5&lt;/code&gt; 같은 헤더로 표현). 이 시간 내에 같은 클라이언트가 또 요청하면 핸드셰이크 비용 없이 처리.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;한계 1, Pipelining 의 실패&lt;/strong&gt;: HTTP/1.1 의 pipelining (응답을 기다리지 않고 다음 요청 보내기)은 거의 모든 프록시에서 비활성화됨. 실무에선 같은 연결의 다음 요청은 이전 응답을 기다려야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;한계 2, [[Head-of-Line Blocking]]&lt;/strong&gt;: 한 연결 안에서는 응답이 순서대로 와야 한다. 첫 응답이 5초 걸리면 그 연결의 두 번째 응답은 5초 뒤에야 시작.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;한계 3, 도메인당 6 연결&lt;/strong&gt;: 브라우저는 같은 호스트에 최대 6~8 개의 TCP 연결만 동시에 유지한다. 자원이 많은 페이지(이미지 100장)는 6 줄 줄로 받아야 함.&lt;/p&gt;
&lt;p&gt;이걸 푸는 게 HTTP/2.&lt;/p&gt;
&lt;h3 id=&quot;http2-한-연결에서-multiplexing&quot;&gt;HTTP/2: 한 연결에서 multiplexing&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP/2 는 단일 [[TCP]] 연결 안에서 여러 stream 을 동시에 주고받는다. 단순히 “효과”를 외우기보다 각 기능이 &lt;em&gt;무엇을 하는지&lt;/em&gt; 부터 보자.&lt;/p&gt;
&lt;h4 id=&quot;multiplexing-frame-단위-인터리브&quot;&gt;Multiplexing, frame 단위 인터리브&lt;/h4&gt;
&lt;p&gt;한 TCP 연결을 여러 &lt;strong&gt;stream&lt;/strong&gt; 으로 나누고, 각 stream 의 데이터를 잘게 &lt;strong&gt;frame&lt;/strong&gt; 으로 쪼개 인터리브해 전송한다. 각 frame 헤더에 stream ID 가 붙어 어느 stream 의 것인지 식별한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 TCP 연결&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├ Stream 1 (CSS):   frame ─ frame ─ frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├ Stream 3 (JS):    frame ─ frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └ Stream 5 (Image): frame ─ frame ─ frame ─ frame&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버는 빠른 응답(작은 CSS)을 먼저 보내고, 느린 응답(이미지)을 그 사이사이에 끼워 보낼 수 있다.&lt;/p&gt;
&lt;p&gt;→ &lt;strong&gt;효과&lt;/strong&gt;: HTTP/1.1 의 도메인당 6 연결 한계 + 연결 내 HOL Blocking 모두 해소. 동시 요청이 많은 페이지에서 큰 이득.&lt;/p&gt;
&lt;h4 id=&quot;hpack-헤더-압축&quot;&gt;[[HPACK]] 헤더 압축&lt;/h4&gt;
&lt;p&gt;HTTP/1.1 은 매 요청마다 같은 헤더(&lt;code&gt;User-Agent&lt;/code&gt;, &lt;code&gt;Cookie&lt;/code&gt;, &lt;code&gt;Accept-Encoding&lt;/code&gt; 등)를 텍스트로 반복 전송. 한 페이지 로드에 헤더만 수십 KB.&lt;/p&gt;
&lt;p&gt;HPACK 은 두 단계로 압축한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Static Table&lt;/strong&gt;: &lt;code&gt;:method GET&lt;/code&gt;, &lt;code&gt;:path /&lt;/code&gt; 같은 자주 쓰이는 헤더를 미리 정의된 인덱스(1~61) 로 치환&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic Table&lt;/strong&gt;: 처음 본 헤더는 인덱스에 등록 → 다음부터는 인덱스만 송신&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Huffman 인코딩&lt;/strong&gt;: 텍스트 부분도 Huffman 코드로 압축&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;→ &lt;strong&gt;효과&lt;/strong&gt;: 페이지 로드 시 헤더 크기 80~95% 절약. 모바일 (TCP slow start 단계) 에서 특히 큼.&lt;/p&gt;
&lt;h4 id=&quot;stream-priority&quot;&gt;Stream Priority&lt;/h4&gt;
&lt;p&gt;각 stream 에 우선순위와 의존 관계를 부여 (&lt;code&gt;PRIORITY&lt;/code&gt; frame). 서버는 우선순위 높은 stream 의 frame 부터 송신.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;브라우저:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - main.css (priority 256, blocking)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - app.js (priority 220, blocking)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - hero.png (priority 100, dependent on main.css)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ &lt;strong&gt;효과&lt;/strong&gt;: 같은 대역폭에서 critical rendering path 가 먼저 도착 → First Contentful Paint 가 빨라진다.&lt;/p&gt;
&lt;h4 id=&quot;server-push-왜-deprecated-되었나&quot;&gt;Server Push, &lt;em&gt;왜 deprecated 되었나&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;서버가 클라이언트가 요청하지 않은 자원도 미리 push 하는 기능이었다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET /index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 서버: &quot;어차피 main.css 도 곧 요청할 거잖아? 미리 보낼게&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → PUSH_PROMISE frame + main.css 내용 함께 송신&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이론상: HTML 파싱 → CSS 발견 → 요청 → 응답 의 RTT 한 번을 절약.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실제로 deprecated 된 이유&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;캐시 미스를 모름&lt;/strong&gt;: 서버는 클라이언트가 이미 캐시에 main.css 를 가지고 있는지 알 수 없다 → 불필요한 중복 전송이 빈번&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;우선순위 충돌&lt;/strong&gt;: push 된 자원이 실제 critical 자원의 대역폭을 빼앗는 경우 발생&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;구현 복잡도&lt;/strong&gt;: 서버가 “어떤 자원을 push 할지” 정확히 판단하기 어려움&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;103 Early Hints 가 더 나음&lt;/strong&gt;: 같은 효과를 캐시 친화적으로 달성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Chrome 은 2022년 (M106) HTTP/2 Server Push 지원을 &lt;strong&gt;제거&lt;/strong&gt;했다. 대신 &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8297&quot;&gt;&lt;code&gt;103 Early Hints&lt;/code&gt;&lt;/a&gt; 를 권장한다, 서버가 최종 응답 전에 “곧 필요할 자원 URL 목록”을 미리 알려주면 브라우저가 &lt;em&gt;자기 캐시 상태를 보고 직접&lt;/em&gt; 요청하는 방식.&lt;/p&gt;
&lt;h4 id=&quot;남은-한계-tcp-hol-blocking&quot;&gt;남은 한계: TCP HOL Blocking&lt;/h4&gt;
&lt;p&gt;HTTP/2 가 &lt;em&gt;HTTP 레벨&lt;/em&gt; 의 [[Head-of-Line Blocking]] 을 풀었지만, &lt;em&gt;TCP 레벨&lt;/em&gt; 의 HOL Blocking 은 그대로다. 한 TCP 패킷이 손실되면, 그 패킷이 어느 stream 에 속하든 [[TCP]] 는 순서 유지를 위해 모든 stream 의 후속 패킷을 대기시킨다.&lt;/p&gt;
&lt;p&gt;흥미로운 실측: &lt;strong&gt;요청 수가 적을 때 HTTP/2 가 HTTP/1.1 보다 오히려 느릴 수 있다.&lt;/strong&gt; 단일 대용량 요청은 HTTP/1.1 의 한 연결이 HTTP/2 의 multiplexing 오버헤드보다 가볍다. 100개 동시 요청 같은 시나리오에서 HTTP/2 의 진가가 발휘된다.&lt;/p&gt;
&lt;h3 id=&quot;http3-quic-위에서&quot;&gt;HTTP/3: [[QUIC]] 위에서&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP/3 는 [[TCP]] 가 아니라 [[UDP]] 기반의 [[QUIC]] 프로토콜을 쓴다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stream 격리&lt;/strong&gt;: 한 stream 의 패킷 손실이 다른 stream 에 영향 안 줌, TCP HOL Blocking 의 본질적 해결&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0-RTT resumption&lt;/strong&gt;: 재접속 시 첫 패킷에 데이터 동봉&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection migration&lt;/strong&gt;: 와이파이 → 셀룰러 전환 시 IP 가 바뀌어도 같은 연결 유지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[[TLS]] 1.3 통합&lt;/strong&gt;: 핸드셰이크에 보안 내장 (1 RTT)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;브라우저 코드는 그대로다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://example.com/api/users&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;어떤 프로토콜 버전이 사용되는지는 서버 설정과 네트워크 환경이 결정한다. 개발자 도구 Network 탭 Protocol 열에 &lt;code&gt;h2&lt;/code&gt;, &lt;code&gt;h3&lt;/code&gt; 으로 표시된다.&lt;/p&gt;
&lt;h3 id=&quot;http-통합-비교-어디에-부하가-가나&quot;&gt;HTTP 통합 비교, 어디에 부하가 가나&lt;/h3&gt;
&lt;p&gt;HTTP 자체는 [[Stateless]] 다. 서버가 stateless 라는 건 &lt;strong&gt;수평 확장이 매우 단순하다&lt;/strong&gt;는 뜻이다. 트래픽 증가 시 서버 인스턴스를 늘리고 로드밸런서가 round-robin 분배하면 끝.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;버전&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;클라이언트&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;서버&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;네트워크&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP/1.1&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;6 연결 관리, 핸드셰이크 다수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless, 매우 가벼움&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;헤더 반복 + HOL 영향&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP/2&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1 연결 + stream 관리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless, 가벼움&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;멀티플렉싱 효율적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP/3&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QUIC stack (CPU 약간 더)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless, 가벼움&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UDP 차단 환경에서 폴백&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;REST API, 정적 자원 fetch, 일회성 폼 제출 등 거의 모든 비실시간 요청이 HTTP 로 충분하다.&lt;/p&gt;
&lt;p&gt;문제는 “&lt;strong&gt;서버가 알려주고 싶을 때&lt;/strong&gt;”다. HTTP의 요청-응답 모델은 클라이언트가 먼저 요청해야만 응답이 가능하다. 그래서 등장한 첫 번째 우회 방법이 polling.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;polling-http-위에서-실시간-흉내내기&quot;&gt;Polling, HTTP 위에서 실시간 흉내내기&lt;/h2&gt;
&lt;h3 id=&quot;short-polling-매-n초마다-묻기&quot;&gt;Short Polling: 매 N초마다 묻기&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setInterval&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/notifications&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  events.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(showNotification);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;구현 간단, HTTP 그대로 (여전히 Stateless 서버)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 대부분의 요청은 빈 응답. 매 요청마다 TCP 핸드셰이크(또는 keep-alive 점유) + 헤더 비용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부하&lt;/strong&gt;: 클라이언트 1000명이 5초마다 polling → 서버는 초당 200 RPS 처리. 빈 응답이 대부분&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;long-polling-서버가-응답을-보류&quot;&gt;Long Polling: 서버가 응답을 보류&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; longPoll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/notifications/wait&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; event&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (event) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;showNotification&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(event);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;longPoll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버는 새 이벤트가 발생할 때까지 응답을 잡고 있다가, 발생하면 즉시 응답한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점&lt;/strong&gt;: 빈 응답 거의 없음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 서버가 connection 을 길게 점유 → &lt;strong&gt;연결 수 = 동시 클라이언트 수&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태&lt;/strong&gt;: HTTP 의미상 Stateless 지만, 운영 입장에선 &lt;strong&gt;TCP 연결 점유가 Stateful 과 비슷한 부담&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 애니메이션이 Short Polling, Long Polling, 그리고 다음에 볼 SSE, WebSocket 의 메시지 흐름 차이를 한눈에 보여준다.&lt;/p&gt;
&lt;h3 id=&quot;polling-의-부하-특성&quot;&gt;Polling 의 부하 특성&lt;/h3&gt;
&lt;p&gt;c5.xlarge EC2 (4 vCPU, 8GB RAM) 에서 10K 동시 연결 측정 (BirJob 2026):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Long Polling 메모리&lt;/strong&gt;: 1.8 GB (연결당 ~180 KB)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long Polling CPU&lt;/strong&gt;: 45% (constant 재연결 처리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 수치를 다음 절들의 SSE/WebSocket 수치와 비교하면 차이가 명확해진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;server-sent-events-서버가-스트림으로-push&quot;&gt;Server-Sent Events, 서버가 스트림으로 push&lt;/h2&gt;
&lt;p&gt;SSE 는 사실상 &lt;strong&gt;하나의 HTTP 응답이 영원히 끝나지 않는&lt;/strong&gt; 방식이다. HTTP/1.1 위에서 동작하지만 [[Stateful]] 한 연결을 유지한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; es&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; EventSource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/stream&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;es.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onmessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;event:&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e.data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;es.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user-joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e.data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  showUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;es.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onerror&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 자동으로 재연결 시도됨&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버 측 응답 포맷:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Content-Type: text/event-stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Cache-Control: no-cache&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;event: user-joined&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;data: {&quot;id&quot;: 1, &quot;name&quot;: &quot;alice&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;id: 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;data: hello&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;id: 43&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;retry: 5000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 이벤트는 &lt;code&gt;data: &lt;/code&gt;, &lt;code&gt;event: &lt;/code&gt;, &lt;code&gt;id: &lt;/code&gt;, &lt;code&gt;retry: &lt;/code&gt; 같은 필드로 구성되고 빈 줄로 구분된다.&lt;/p&gt;
&lt;h3 id=&quot;sse-의-자동-재연결--last-event-id&quot;&gt;SSE 의 자동 재연결 + Last-Event-ID&lt;/h3&gt;
&lt;p&gt;가장 큰 장점이다. &lt;strong&gt;EventSource 는 연결이 끊어지면 자동으로 재시도하고, 마지막으로 받은 이벤트 ID 를 &lt;code&gt;Last-Event-ID&lt;/code&gt; 헤더로 보낸다.&lt;/strong&gt; 서버는 그 ID 이후의 이벤트만 다시 보내면 된다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Node.js + Express 예시&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/stream&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;text/event-stream&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Cache-Control&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;no-cache&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Connection&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;keep-alive&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; lastId&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; parseInt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(req.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Last-Event-ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;0&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getEventsSince&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(lastId);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  events.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`id: ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`event: ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`data: ${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; onNewEvent&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`id: ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`data: ${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;evt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  eventBus.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;new&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, onNewEvent);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  req.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;close&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; eventBus.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;off&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;new&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, onNewEvent));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 자동 재연결 + 상태 복원이 WebSocket 에는 없어서 직접 구현해야 하는 부분이다.&lt;/p&gt;
&lt;h3 id=&quot;sse-의-부하-특성-실측&quot;&gt;SSE 의 부하 특성 (실측)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;연결당 메모리&lt;/strong&gt;: ~1.2 KB (Ark Protocol 2026) ~ ~60 KB (Wolf-tech 2026, Node.js net layer 포함)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10K 동시 연결 총 메모리&lt;/strong&gt;: ~600 MB (BirJob 2026)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU idle&lt;/strong&gt;: 8% (BirJob 2026)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;연결당 idle 대역폭&lt;/strong&gt;: ~0.1 KB/keepalive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메시지 지연 p50/p99&lt;/strong&gt;: 12ms / 45ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WebSocket 보다 SSE 가 &lt;strong&gt;메모리 35-40% 적은&lt;/strong&gt; 이유: SSE 는 HTTP 스택을 그대로 쓰므로 WebSocket 의 frame buffer + state machine + protocol framing metadata 같은 추가 자료구조가 필요 없다. &lt;strong&gt;구조적으로 가벼움&lt;/strong&gt;이지 최적화 결과가 아니다.&lt;/p&gt;
&lt;h3 id=&quot;sse-가-잘-맞는-시나리오&quot;&gt;SSE 가 잘 맞는 시나리오&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;실시간 알림 (새 메시지, 댓글)&lt;/li&gt;
&lt;li&gt;진행률 표시 (파일 처리, 빌드 로그)&lt;/li&gt;
&lt;li&gt;주가/스코어 ticker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLM 응답 스트리밍&lt;/strong&gt; (ChatGPT, Claude, Gemini 모두 SSE)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;도메인당-6개-연결-한계의-메커니즘&quot;&gt;도메인당 6개 연결 한계의 메커니즘&lt;/h3&gt;
&lt;p&gt;브라우저는 &lt;strong&gt;같은 호스트(도메인+포트 조합)에 대해 동시 [[TCP]] 연결을 6~8개로 제한&lt;/strong&gt;한다. 이 숫자는 표준이 아니라 브라우저 구현 기본값 (Chrome/Edge 6, Firefox 6, Safari 6).&lt;/p&gt;
&lt;p&gt;이 한계가 SSE 에 특히 치명적인 이유:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SSE 한 stream = TCP 연결 1개를 영구 점유&lt;/strong&gt;. 응답이 끝나지 않으므로 연결이 자유롭게 되지 않는다.&lt;/li&gt;
&lt;li&gt;같은 호스트에 SSE 6개를 열면 7번째부터 &lt;strong&gt;다른 모든 요청&lt;/strong&gt; (이미지, API call, 다른 SSE) 도 큐에서 대기.&lt;/li&gt;
&lt;li&gt;사용자가 같은 사이트를 여러 탭에 열면 각 탭이 자기 SSE 를 열려고 시도 → 7번째 탭부터 영구 대기 (탭이 안 보일 정도).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;왜 6개 제한이 존재하나&lt;/strong&gt;, 역사적 이유. HTTP/1.x 는 연결당 동시 하나의 요청만 처리 가능 (pipelining 실패). 만약 무제한이면 한 사용자가 서버 자원을 독점할 수 있어 RFC 2616 권장은 2개, 실무 브라우저는 6~8개로 절충.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결책 3가지&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2 이상으로 운영&lt;/strong&gt;, 단일 TCP 연결에서 multiplexing 으로 6개 한계 완전 우회. 가장 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;여러 서브도메인 분산&lt;/strong&gt;, &lt;code&gt;sse1.example.com&lt;/code&gt;, &lt;code&gt;sse2.example.com&lt;/code&gt; 등 (구식 해결책, 인증서/CORS 부담)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SharedWorker&lt;/strong&gt;, 모든 탭이 공유하는 단일 worker 가 SSE 연결 1개만 유지, 메시지를 각 탭에 분배&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
대부분의 모던 인프라(Cloudflare, AWS CloudFront, Vercel 등) 는 HTTP/2 또는 HTTP/3 를 기본 제공하므로 이 한계는 실무에서 거의 만나지 않는다. 단 사내 레거시 서버 / 일부 프록시 / Nginx 미튜닝 환경에서는 여전히 발생.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SSE 의 한계는 명확하다, &lt;strong&gt;단방향&lt;/strong&gt;이다. 양방향이 필요하면 다음 단계.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;websocket-한-번-열고-양방향&quot;&gt;WebSocket, 한 번 열고 양방향&lt;/h2&gt;
&lt;p&gt;WebSocket 은 HTTP 핸드셰이크로 시작해서 &lt;strong&gt;프로토콜을 통째로 바꾸는&lt;/strong&gt; 방식이다. 명확히 [[Stateful]] 통신.&lt;/p&gt;
&lt;h3 id=&quot;핸드셰이크-http-upgrade&quot;&gt;핸드셰이크: HTTP Upgrade&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET /chat HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host: example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Upgrade: websocket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connection: Upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sec-WebSocket-Version: 13&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTTP/1.1 101 Switching Protocols&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Upgrade: websocket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connection: Upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;101 Switching Protocols&lt;/code&gt; 이후로는 같은 TCP 연결을 WebSocket frame 형식으로 사용한다. 더 이상 HTTP 가 아니다.&lt;/p&gt;
&lt;h3 id=&quot;브라우저-api&quot;&gt;브라우저 API&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ws&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; WebSocket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;wss://example.com/chat&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onopen&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uint8Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onmessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e.data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;text:&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e.data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;binary:&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e.data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onclose&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 자동 재연결 없음, 직접 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(connect, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;frame-구조&quot;&gt;Frame 구조&lt;/h3&gt;
&lt;p&gt;WebSocket 메시지는 frame 이라는 단위로 송수신된다. 한 frame 헤더는 2~14 bytes 로 매우 작다 (HTTP 헤더 수백 바이트와 대조). &lt;strong&gt;작은 메시지를 자주 주고받기에 최적&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;websocket-이-직접-처리해야-하는-것들&quot;&gt;WebSocket 이 직접 처리해야 하는 것들&lt;/h3&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;기능&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;SSE&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;WebSocket&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;자동 재연결&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗ (직접 구현 또는 라이브러리)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;마지막 이벤트 복원&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓ (Last-Event-ID)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Heartbeat / Keep-alive&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;별도 ping 불필요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;필요&lt;/strong&gt; (proxy timeout 방지)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;메시지 직렬화&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;자동 (text)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;직접 (JSON.stringify 등)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이래서 실무에서는 &lt;strong&gt;Socket.IO&lt;/strong&gt; 같은 라이브러리를 자주 쓴다. 재연결, 룸/네임스페이스, 폴백, 메시지 ACK 등을 제공.&lt;/p&gt;
&lt;h3 id=&quot;서버-구현-예시&quot;&gt;서버 구현 예시&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; wss&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; WebSocketServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ port: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8080&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wss.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;connection&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ws&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;message&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    wss.clients.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (client &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ws &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.readyState &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ws.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;OPEN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Keep-alive ping (proxy idle timeout 방지)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; pingInterval&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; setInterval&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (ws.readyState &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ws.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;OPEN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ping&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ws.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;close&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; clearInterval&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(pingInterval));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;websocket-의-부하-특성-실측&quot;&gt;WebSocket 의 부하 특성 (실측)&lt;/h3&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측정&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;값&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;출처&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;연결당 메모리 (raw ws)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~3.5 KB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Ark Protocol 2026&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;연결당 메모리 (Socket.IO)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~60-90 KB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Wolf-tech 2026&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;10K 동시 연결 총 메모리 (raw)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~400 MB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BirJob 2026&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5K 동시 연결 (Socket.IO)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~400-500 MB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Wolf-tech 2026&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;100K 연결 (raw)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~342 MB&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Ark Protocol c5.2xlarge&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;CPU idle (10K 연결)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;5%&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BirJob 2026&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;메시지 지연 p50/p99&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;8ms / 22ms&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BirJob 2026&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;[[Stateful]] 의 진짜 비용은 메모리가 아니라 운영 복잡도&lt;/strong&gt;다. 수평 확장하려면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;[[Sticky Session]]&lt;/strong&gt;: 로드밸런서가 같은 클라이언트를 같은 서버로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[[Pub/Sub Bus]]&lt;/strong&gt;: 서버 간 메시지 라우팅 (Socket.IO + Redis adapter)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backpressure&lt;/strong&gt;: 클라이언트가 느릴 때 서버 send buffer 가 차오름 → 명시적 관리&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;websocket-시나리오&quot;&gt;WebSocket 시나리오&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;채팅&lt;/li&gt;
&lt;li&gt;협업 도구 (커서 위치, 실시간 편집)&lt;/li&gt;
&lt;li&gt;게임 (실시간 입력)&lt;/li&gt;
&lt;li&gt;거래 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
“양방향이니까 그냥 WebSocket 쓰자”는 흔한 함정이다. &lt;strong&gt;단방향이면 SSE 가 훨씬 단순하고 부담이 적다.&lt;/strong&gt; WebSocket 의 stateful 특성은 인프라 복잡도를 크게 늘린다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;webrtc-서버를-거치지-않는-p2p&quot;&gt;WebRTC, 서버를 거치지 않는 P2P&lt;/h2&gt;
&lt;p&gt;WebRTC 는 두 브라우저가 &lt;strong&gt;서로 직접 연결&lt;/strong&gt;되는 방식이다. 단, 만남 자체는 서버를 한 번 거쳐야 한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;세-가지-서버-역할&quot;&gt;세 가지 서버 역할&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Signaling Server&lt;/strong&gt;: 두 피어가 서로의 정보(SDP)를 주고받기 위한 통로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;STUN Server&lt;/strong&gt;: 각 피어가 자기의 공인 IP/포트를 알아내는 데 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TURN Server&lt;/strong&gt;: 직접 연결 실패 시 데이터 중계&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;STUN 은 가볍지만 TURN 은 모든 데이터가 통과하므로 비용이 크다.&lt;/p&gt;
&lt;h3 id=&quot;sdp-offeranswer-교환&quot;&gt;SDP Offer/Answer 교환&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Peer A (Caller)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; pc&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RTCPeerConnection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  iceServers: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    { urls: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;stun:stun.l.google.com:19302&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; dataChannel&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createDataChannel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;chat&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dataChannel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onopen&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dataChannel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;hello!&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dataChannel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onmessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;peer:&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e.data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; offer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createOffer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setLocalDescription&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(offer);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;signaling.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;offer&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, sdp: offer });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;signaling.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;answer&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;sdp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setRemoteDescription&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sdp);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onicecandidate&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (e.candidate) signaling.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, candidate: e.candidate });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;signaling.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;candidate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pc.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;addIceCandidate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(candidate));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;datachannel-sctp-over-dtls-over-udp&quot;&gt;DataChannel: SCTP over DTLS over UDP&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;RTCDataChannel&lt;/code&gt; 은 SCTP 를 사용한다. SCTP 는 다음을 설정할 수 있다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ordered&lt;/code&gt;: 순서 보장 여부 (기본 true)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxRetransmits&lt;/code&gt;: 재전송 횟수 (UDP 스타일은 0)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxPacketLifeTime&lt;/code&gt;: 패킷 유효 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;게임처럼 “&lt;strong&gt;약간 잃어도 좋으니 빠르게&lt;/strong&gt;” 가 필요하면 unordered + maxRetransmits 0 (사실상 UDP)으로 설정. 파일 전송처럼 정확성이 중요하면 기본값.&lt;/p&gt;
&lt;p&gt;모든 통신은 &lt;strong&gt;[[DTLS]] 로 자동 암호화&lt;/strong&gt;된다, [[UDP]] 위에서 동작하는 [[TLS]] 변형.&lt;/p&gt;
&lt;h3 id=&quot;webrtc-부하-특성&quot;&gt;WebRTC 부하 특성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;시그널링 서버&lt;/strong&gt;: 만남 단계만 부담 (수 kB SDP + 수 십개 ICE candidate 메시지)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;STUN 서버&lt;/strong&gt;: 매우 가벼움 (단순 응답)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TURN 서버&lt;/strong&gt;: 통화 1쌍에 양방향 대역폭, 가장 비싼 인프라&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;클라이언트&lt;/strong&gt;: ICE 후보 수집, 암호화/복호화, 미디어 인코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;TURN 의존도가 높으면 서버 부담이 다시 커진다.&lt;/strong&gt; 모바일 사용자가 많고 NAT 가 엄격한 환경(통신사망 뒤)에서는 50~80% 가 TURN 을 거친다는 보고도 있다. WebRTC 가 서버 비용을 0 으로 만들지는 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;webrtc-시나리오&quot;&gt;WebRTC 시나리오&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;영상/음성 통화&lt;/li&gt;
&lt;li&gt;화면 공유&lt;/li&gt;
&lt;li&gt;파일 직접 전송 (대용량을 서버 거치지 않고)&lt;/li&gt;
&lt;li&gt;P2P 게임&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;webtransport-http3-위의-양방향-stream-미래&quot;&gt;WebTransport, HTTP/3 위의 양방향 stream (미래)&lt;/h2&gt;
&lt;p&gt;WebTransport 는 &lt;strong&gt;HTTP/3 [[QUIC]] 위에서 양방향 stream + datagram&lt;/strong&gt; 을 제공하는 새 API.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; wt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; WebTransport&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://example.com/wt&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wt.ready;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; stream&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createBidirectionalStream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; writer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stream.writable.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getWriter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; writer.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TextEncoder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; datagramWriter&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wt.datagrams.writable.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getWriter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; datagramWriter.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uint8Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WebSocket 대비:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;다중 stream&lt;/strong&gt;: 한 연결에서 여러 독립 stream&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datagram&lt;/strong&gt;: UDP 스타일 unreliable 전송도 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;연결 마이그레이션&lt;/strong&gt;: QUIC 의 성질 그대로&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;브라우저 지원이 아직 부분적이지만, WebSocket 의 후속으로 자리잡을 가능성이 크다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;벤치마크-실측-차트&quot;&gt;벤치마크, 실측 차트&lt;/h2&gt;
&lt;p&gt;여기까지 본 부하 수치를 한눈에. 데이터는 &lt;a href=&quot;https://www.birjob.com/blog/realtime-websockets-sse&quot;&gt;BirJob 2026 c5.xlarge 10K 측정&lt;/a&gt; + &lt;a href=&quot;https://medium.com/@ArkProtocol1/websockets-vs-sse-at-100k-connections-sse-used-40-less-memory-40faeafd419d&quot;&gt;Ark Protocol 2026 c5.2xlarge 100K 측정&lt;/a&gt; + &lt;a href=&quot;https://wolf-tech.io/blog/nextjs-15-sse-vs-websockets-vs-polling-real-time-decision-matrix-2026&quot;&gt;Wolf-tech 2026 Next.js production&lt;/a&gt; 의 종합.&lt;/p&gt;
&lt;h3 id=&quot;10000-동시-연결에서-서버-메모리&quot;&gt;10,000 동시 연결에서 서버 메모리&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h3 id=&quot;연결당-메모리-오버헤드&quot;&gt;연결당 메모리 오버헤드&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h3 id=&quot;cpu-사용량-10k-idle-연결&quot;&gt;CPU 사용량 (10K idle 연결)&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h3 id=&quot;메시지-지연-p50--p99&quot;&gt;메시지 지연 (p50 / p99)&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h3 id=&quot;차트의-한-줄-결론&quot;&gt;차트의 한 줄 결론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;메모리&lt;/strong&gt;: Long Polling &gt;&gt; WebSocket(Socket.IO) &gt; SSE &gt; WebSocket(raw)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;: Long Polling &gt;&gt; SSE &gt; WebSocket&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지연&lt;/strong&gt;: Long Polling은 사실상 실시간 X. SSE/WebSocket 만 실시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;벤치마크 코드는 별도 프로젝트로 분리, &lt;a href=&quot;https://github.com/...&quot;&gt;/Users/[user]/0099-web-comm-bench&lt;/a&gt; (Node.js + ws + 모든 4 가지 엔드포인트 + 5초 메모리 스냅샷).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;종합-비교-어디에-부하가-가나&quot;&gt;종합 비교, 어디에 부하가 가나&lt;/h2&gt;





















































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;통신 주체&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;프로토콜&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;상태&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;클라이언트&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;서버&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;네트워크&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;부하 위치&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP/1.1/2/3&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Client ↔ Server&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TCP/UDP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Stateless]]&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;헤더 + RTT&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 분산&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Short Polling&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Client → Server&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낭비 큼&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 분산 (빈 응답)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Long Polling&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Client → Server&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateless*&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;높음&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;효율적&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 연결 풀&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SSE&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Server → Client&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Stateful]]&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;효율적&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 연결 + 6 연결 한계&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Client ↔ Server&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TCP frame&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;[[Stateful]]&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;높음&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;매우 효율적&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버 메모리 + [[Sticky Session]]&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebRTC&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Peer ↔ Peer&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SCTP/DTLS/UDP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateful&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;높음&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;시그널링만&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;P2P 직접&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;클라이언트 + P2P, TURN 시 서버&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebTransport&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Client ↔ Server&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;QUIC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Stateful&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;HTTP/3 효율&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP/3 인프라&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;*Long Polling 은 HTTP 의미상 Stateless 지만 운영상 연결 점유가 Stateful 과 비슷.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;시나리오별-선택-가이드&quot;&gt;시나리오별 선택 가이드&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;시나리오&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;권장&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;REST API · 정적 자원&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;HTTP/2 또는 HTTP/3&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;알림 push (한쪽 방향)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SSE&lt;/strong&gt;, 단순 + 자동 재연결&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;LLM 응답 스트리밍&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SSE&lt;/strong&gt;, 사실상 표준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;채팅 (양방향)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebSocket&lt;/strong&gt; + 라이브러리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;실시간 협업 (Figma, Notion)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;, CRDT/OT 메시지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;게임 (실시간 입력)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebSocket&lt;/strong&gt; 또는 &lt;strong&gt;WebRTC DataChannel&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;영상/음성 통화&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebRTC&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;대용량 파일 P2P&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebRTC DataChannel&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;미래 지향 양방향 + datagram&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;WebTransport&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;잘못된-선택이-비용이-되는-패턴&quot;&gt;잘못된 선택이 비용이 되는 패턴&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“&lt;strong&gt;알림 받아야 하니까 WebSocket&lt;/strong&gt;” → SSE 면 충분한데 인프라 복잡도 늘림&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;채팅이니까 polling&lt;/strong&gt;” → 클라이언트 100명 × 1초 polling = 서버 6000 RPS 빈 응답&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;P2P니까 WebRTC&lt;/strong&gt;” → 시그널링 + STUN/TURN 인프라가 의외로 큰 비용&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;HTTP/3이 좋다니까 무조건 HTTP/3&lt;/strong&gt;” → 사내망/특정 환경에선 UDP 가 막혀 fallback&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;p&gt;브라우저 통신은 한 방향으로 발전해왔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“요청-응답”이라는 HTTP의 본질적 제약을 어떻게 우회하느냐&lt;/strong&gt;, 그리고 &lt;strong&gt;“서버에 얼마나 의존하느냐”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;우회 방법&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;서버 의존도&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;우회 안 함&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;모든 트래픽&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Polling&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;자주 요청&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;모든 트래픽 (낭비 많음)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Long Polling&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;응답 보류&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;모든 트래픽 (효율적)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;SSE&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;응답을 영원히 안 끝냄&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;모든 트래픽 (단방향)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebSocket&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP 졸업, 양방향 프로토콜&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;모든 트래픽&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebRTC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;만남만 서버, 데이터는 P2P&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;시그널링만&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;WebTransport&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP/3 위 다중 stream&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;모든 트래픽 (효율적)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;선택은 결국 “&lt;strong&gt;부하를 어디에 두고 싶은가&lt;/strong&gt;”의 문제다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
“최신이니까 좋다”는 함정이다. WebSocket 한 줄로 끝낼 알림을 WebRTC 로 만들면 운영 비용이 10 배로 늘 수 있다. 반대로 영상 통화를 WebSocket 으로 만들면 서버 대역폭이 폭발한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요구사항 → [[Stateless]] vs [[Stateful]] 결정 → 데이터 흐름 방향 → 부하 위치 선택 → 통신 방식 결정&lt;/strong&gt; 순서로 생각하면 거의 틀리지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ 이 글은 MDN Web Docs, RFC (HTTP/2 RFC 7540, HTTP/3 RFC 9114, WebSocket RFC 6455, [[QUIC]] RFC 9000, [[HPACK]] RFC 7541), web.dev 의 WebRTC 문서, 그리고 2026년 실측 벤치마크 자료(BirJob, Ark Protocol, Wolf-tech)를 참조하여 &lt;strong&gt;AI(Claude)와 함께 작성&lt;/strong&gt; 되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Evolution_of_HTTP&quot;&gt;MDN, HTTP/3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events&quot;&gt;MDN, Server-Sent Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API&quot;&gt;MDN, WebSocket API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling&quot;&gt;MDN, WebRTC Signaling and video calling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/webrtc-datachannels&quot;&gt;web.dev, WebRTC DataChannels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.salesforce.com/the-full-picture-on-http-2-and-hol-blocking-7f964b34d205/&quot;&gt;Salesforce, HTTP/2 와 [[Head-of-Line Blocking]]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rxdb.info/articles/websockets-sse-polling-webrtc-webtransport.html&quot;&gt;RxDB, WebSockets vs SSE vs Long-Polling vs WebRTC vs WebTransport&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ably.com/blog/websockets-vs-sse&quot;&gt;Ably, WebSockets vs SSE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.birjob.com/blog/realtime-websockets-sse&quot;&gt;BirJob, Real-Time Systems with WebSockets, SSE, Long Polling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@ArkProtocol1/websockets-vs-sse-at-100k-connections-sse-used-40-less-memory-40faeafd419d&quot;&gt;Ark Protocol, WebSockets vs SSE at 100K Connections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wolf-tech.io/blog/nextjs-15-sse-vs-websockets-vs-polling-real-time-decision-matrix-2026&quot;&gt;Wolf-tech, Next.js 15 SSE vs WebSockets vs Polling Decision Matrix 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6455&quot;&gt;RFC 6455, The WebSocket Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9114&quot;&gt;RFC 9114, HTTP/3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9000&quot;&gt;RFC 9000, [[QUIC]]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>http</category><category>websocket</category><category>sse</category><category>webrtc</category><category>networking</category><category>browser</category><category>web-api</category><author>koa (김신건)</author></item><item><title>비동기와 타이밍, 콜백부터 async/await까지의 발전사</title><link>https://shinkeonkim.com/posts/2026-06-15-js-async-timing/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-15-js-async-timing/</guid><description>싱글 스레드 JavaScript 가 어떻게 동시에 여러 일을 처리하는지, 이벤트 루프부터 Promise·async/await·AbortController·AsyncIterator 까지 메커니즘으로 풀어본다. setTimeout 0 이 왜 즉시가 아닌지, 마이크로태스크가 왜 먼저인지, 콜백이 왜 지옥이 되는지를 차례로 답한다.</description><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;시작-의문&quot;&gt;시작 의문&lt;/h2&gt;
&lt;p&gt;다음 코드의 출력 순서를 맞춰보자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;B&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;C&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;D&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;직관적으로는 &lt;code&gt;A B C D&lt;/code&gt; 일 것 같다. &lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; 은 “0 밀리초 뒤”라고 했으니 바로 다음 줄로 가야 자연스럽다. 그런데 실제 출력은:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;B&lt;/code&gt; 가 가장 늦게 찍힌다. &lt;code&gt;Promise.then&lt;/code&gt; 으로 등록한 &lt;code&gt;C&lt;/code&gt; 가 &lt;code&gt;setTimeout 0&lt;/code&gt; 으로 등록한 &lt;code&gt;B&lt;/code&gt; 보다 먼저다. 그리고 동기 코드인 &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt; 는 둘 다 비동기보다 먼저다.&lt;/p&gt;
&lt;p&gt;세 가지 질문이 떠오른다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JavaScript 는 싱글 스레드라는데, 어떻게 setTimeout 과 Promise 가 “동시에” 진행되는가?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; 은 왜 즉시가 아닌가? 0 밀리초인데도 왜 그렇게 느린가?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;같은 비동기인데 왜 Promise 가 setTimeout 보다 항상 먼저 실행되는가?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;답을 한 줄로 던지면 “이벤트 루프와 두 종류의 큐 때문” 으로 끝난다. 하지만 &lt;em&gt;어떻게&lt;/em&gt; 그렇게 되는지를 이해하려면 한 단계 더 들어가야 한다. 이 글은 &lt;strong&gt;콜 스택&lt;/strong&gt;·&lt;strong&gt;Web API&lt;/strong&gt;·&lt;strong&gt;태스크 큐&lt;/strong&gt;·&lt;strong&gt;마이크로태스크 큐&lt;/strong&gt; 네 개념을 쌓아 세 질문 모두에 답하고, 거기서 자라난 &lt;strong&gt;콜백&lt;/strong&gt; → &lt;strong&gt;Promise&lt;/strong&gt; → &lt;strong&gt;async/await&lt;/strong&gt; 의 발전사와 &lt;strong&gt;fetch + AbortController&lt;/strong&gt;, &lt;strong&gt;Generator/AsyncIterator&lt;/strong&gt;, &lt;strong&gt;queueMicrotask/requestAnimationFrame&lt;/strong&gt; 같은 현대적 도구까지 한 흐름으로 풀어보는 것을 목표로 한다.&lt;/p&gt;
&lt;p&gt;먼저 가장 기초적인 곳부터.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;한-단계-뒤로-javascript-는-왜-비동기가-필요한가&quot;&gt;한 단계 뒤로, JavaScript 는 왜 비동기가 필요한가&lt;/h2&gt;
&lt;h3 id=&quot;싱글-스레드의-본질&quot;&gt;싱글 스레드의 본질&lt;/h3&gt;
&lt;p&gt;JavaScript 엔진은 한 번에 한 줄씩만 실행한다. 함수 호출 하나가 끝나야 다음 줄이 진행된다. 이를 &lt;strong&gt;싱글 스레드 (single-threaded)&lt;/strong&gt; 라고 한다. 이는 V8, SpiderMonkey, JavaScriptCore 모든 주류 엔진의 공통 약속이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;왜 싱글 스레드인가? JavaScript 가 1995 년 Brendan Eich 에 의해 10 일 만에 만들어졌을 때 주된 용도는 “브라우저에서 사용자 클릭에 반응하기” 였다. 화면 갱신과 동시에 두 스크립트가 같은 DOM 노드를 수정한다면 결과가 비결정적 (race condition) 이 된다. 단순함과 안전을 위해 의도적으로 싱글 스레드를 선택했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이게 무슨 뜻인지 코드로 보자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; blocking&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 3초간 CPU 점유&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;done&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;blocking&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;B&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 3초 뒤에 찍힘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;blocking()&lt;/code&gt; 이 도는 3 초 동안 그 어떤 코드도 실행되지 않는다. 브라우저 환경이라면 페이지가 얼어붙는다. 클릭, 스크롤, 애니메이션 모두 멈춘다.&lt;/p&gt;
&lt;h3 id=&quot;그런데-우리는-동시에-여러-일을-본다&quot;&gt;그런데 우리는 동시에 여러 일을 본다&lt;/h3&gt;
&lt;p&gt;브라우저를 켜면 다음이 동시에 일어난다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;네트워크에서 데이터 받기 (수십~수천 밀리초)&lt;/li&gt;
&lt;li&gt;사용자가 스크롤 / 클릭 / 타이핑&lt;/li&gt;
&lt;li&gt;매 16ms 마다 화면 다시 그리기 (60fps)&lt;/li&gt;
&lt;li&gt;타이머 카운트다운&lt;/li&gt;
&lt;li&gt;백그라운드 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JavaScript 가 진짜로 한 번에 한 줄만 한다면 이게 가능할 리 없다. &lt;em&gt;어디서&lt;/em&gt; 이 동시성이 나오는가?&lt;/p&gt;
&lt;p&gt;답은 &lt;strong&gt;JavaScript 엔진은 싱글 스레드지만, 그것을 실행하는 호스트 환경 (브라우저, Node.js) 은 멀티 스레드&lt;/strong&gt; 라는 것이다. 엔진은 “지금 당장 실행할 코드 한 줄” 만 처리하고, 시간이 걸리는 작업 (네트워크, 타이머, 파일 I/O) 은 호스트 환경이 백그라운드 스레드로 처리한 뒤 결과만 큐에 넣어준다.&lt;/p&gt;
&lt;p&gt;엔진은 그 큐를 무한 루프로 감시하다가, 자기가 한가해지면 큐에서 하나씩 꺼내 실행한다. 이 무한 루프가 곧 [[이벤트 루프]] (Event Loop) 다.&lt;/p&gt;
&lt;h3 id=&quot;차근차근-그림으로&quot;&gt;차근차근 그림으로&lt;/h3&gt;
&lt;p&gt;이게 우리가 곧 만나게 될 그림이다. 익숙해질 때까지 머릿속에 두자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;네 개의 구성요소가 있다.&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;구성요소&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;역할&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;누가 관리?&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Call Stack&lt;/strong&gt; (콜 스택)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지금 실행 중인 함수들&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;JavaScript 엔진&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Web API&lt;/strong&gt; (또는 Node.js API)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;타이머, fetch, DOM 이벤트 등&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호스트 환경 (브라우저 / Node)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Task Queue&lt;/strong&gt; (매크로태스크 큐)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;setTimeout&lt;/code&gt;, DOM 이벤트, &lt;code&gt;setInterval&lt;/code&gt; 콜백&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호스트 + 이벤트 루프&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Microtask Queue&lt;/strong&gt; (마이크로태스크 큐)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Promise.then&lt;/code&gt;, &lt;code&gt;queueMicrotask&lt;/code&gt;, &lt;code&gt;MutationObserver&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;JS 엔진 (ECMAScript 명세)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이벤트 루프의 알고리즘은 단순하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while (true) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if (콜 스택이 비어있으면) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    마이크로태스크 큐를 비울 때까지 전부 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    태스크 큐에서 한 개 꺼내서 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (필요하면 렌더링)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 5 줄짜리 루프가 JavaScript 의 모든 비동기 동작을 떠받친다. 세 질문의 답도 여기서 자라난다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;타이밍의-시작-settimeout-과-setinterval&quot;&gt;타이밍의 시작, setTimeout 과 setInterval&lt;/h2&gt;
&lt;p&gt;비동기를 가장 먼저 만나는 곳은 &lt;strong&gt;타이머 API&lt;/strong&gt; 다. [[setTimeout]], [[setInterval]] 두 함수를 보자. (문법과 함정에 대한 상세는 각 위키 페이지 참고.)&lt;/p&gt;
&lt;h3 id=&quot;settimeout-한-번만-실행&quot;&gt;setTimeout, 한 번만 실행&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setTimeout(callback, delay)&lt;/code&gt; 은 “최소 &lt;code&gt;delay&lt;/code&gt; 밀리초 뒤에 &lt;code&gt;callback&lt;/code&gt; 을 태스크 큐에 넣어달라” 는 요청이다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“setTimeout 기본 동작”
language=“javascript”
label=“브라우저 콘솔”
outputLabel=“출력”
code={`console.log(‘A’);&lt;/p&gt;
&lt;p&gt;setTimeout(() =&gt; {
console.log(‘B’);
}, 1000);&lt;/p&gt;
&lt;p&gt;console.log(‘C’);&lt;code&gt;}   output={&lt;/code&gt;A
C
B   // 약 1초 뒤`}
/&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;세 가지 포인트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“최소” 다.&lt;/strong&gt; 정확히 &lt;code&gt;delay&lt;/code&gt; 밀리초가 아니라 “&lt;strong&gt;최소&lt;/strong&gt; 그만큼 뒤” 다. 콜 스택이 비어있어야만 실행된다. 콜 스택에 무거운 작업이 있으면 5초 지연으로 등록한 타이머도 10 초 뒤에야 실행될 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;반환값은 타이머 ID.&lt;/strong&gt; &lt;code&gt;clearTimeout(id)&lt;/code&gt; 로 취소 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;콜백은 그 자체가 [[클로저]].&lt;/strong&gt; 등록 시점의 변수를 들고 다닌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;settimeoutfn-0-은-즉시가-아니다&quot;&gt;setTimeout(fn, 0) 은 즉시가 아니다&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가장 헷갈리는 부분이다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“setTimeout 0 의 진실”
language=“javascript”
code={`console.log(‘1’);
setTimeout(() =&gt; console.log(‘2’), 0);
console.log(‘3’);&lt;/p&gt;
&lt;p&gt;// 동기 코드를 무겁게 만들면?
const start = Date.now();
while (Date.now() - start &amp;#x3C; 100) {}&lt;/p&gt;
&lt;p&gt;console.log(‘4’);&lt;code&gt;}   output={&lt;/code&gt;1
3
4
2   // 100ms+ 뒤에 출력`}
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; 의 &lt;code&gt;fn&lt;/code&gt; 은 &lt;strong&gt;즉시가 아니라, 현재 동기 코드가 모두 끝나고, 마이크로태스크 큐도 다 비고, 그 다음 태스크 큐 차례가 와야&lt;/strong&gt; 실행된다.&lt;/p&gt;
&lt;p&gt;여기에 더해 HTML 명세는 &lt;strong&gt;중첩 5회 이상의 setTimeout 에 대해 최소 4ms 지연&lt;/strong&gt; 을 강제한다 (&lt;a href=&quot;https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers&quot;&gt;WHATWG HTML §8.1.7.4&lt;/a&gt;). 즉 &lt;code&gt;setTimeout(() =&gt; setTimeout(() =&gt; setTimeout(...))) &lt;/code&gt; 처럼 깊게 중첩되면 자동으로 4ms 가 깔린다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;&lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; 의 진짜 의미는 “다음 이벤트 루프 틱에 양보하라” 다.&lt;/strong&gt; 즉시 실행이 아니라 “동기 작업을 먼저 끝내고 큐에 다시 들어가라” 라는 스케줄링 힌트.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 트릭은 무거운 동기 작업을 잘게 쪼개 UI 가 응답하게 만드는 데 자주 쓰인다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1억 번 루프, 한 번에 돌리면 UI 멈춤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; processInChunks&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;processor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; items.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 50&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      processor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(items[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; items.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(next, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 50ms 마다 UI 에 양보&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;setinterval-반복-실행&quot;&gt;setInterval, 반복 실행&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setInterval(callback, delay)&lt;/code&gt; 은 “&lt;strong&gt;매&lt;/strong&gt; &lt;code&gt;delay&lt;/code&gt; 밀리초마다 &lt;code&gt;callback&lt;/code&gt; 을 태스크 큐에 넣어달라” 는 요청이다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“setInterval 기본”
language=“javascript”
code={&lt;code&gt;let count = 0; const id = setInterval(() =&gt; {   console.log(\&lt;/code&gt;tick ${++count}`);
if (count === 3) clearInterval(id);
}, 1000);&lt;code&gt;}   output={&lt;/code&gt;tick 1   // 1초 뒤
tick 2   // 2초 뒤
tick 3   // 3초 뒤 (이후 멈춤)`}
/&gt;&lt;/p&gt;
&lt;p&gt;문제는 &lt;strong&gt;콜백 실행 시간이 interval 보다 길어지면 큐에 쌓인다는 점&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setInterval&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 1500ms 걸리는 작업&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1500&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;done&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// → 1초마다 큐에 등록되지만 한 번에 1.5초 걸림&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// → 큐가 무한히 쌓이거나, 브라우저가 자동으로 누락&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 피하려면 &lt;code&gt;setTimeout&lt;/code&gt; 으로 자기 자신을 재호출하는 패턴이 더 안전하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; poll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  doWork&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(poll, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 작업 끝난 뒤 다음 호출 예약&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;poll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 패턴은 [[Polling]] 의 기본 형태이기도 하다.&lt;/p&gt;
&lt;h3 id=&quot;정리-타이머가-던지는-두-가지-메시지&quot;&gt;정리, 타이머가 던지는 두 가지 메시지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“즉시” 라는 단어는 존재하지 않는다.&lt;/strong&gt; 모든 비동기 콜백은 큐를 거쳐 콜 스택이 빌 때까지 기다린다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타이머는 정확한 시각을 보장하지 않는다.&lt;/strong&gt; 최소 지연을 보장할 뿐.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 두 메시지가 비동기의 모든 함정의 출발점이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;콜백-첫-번째-시도&quot;&gt;콜백, 첫 번째 시도&lt;/h2&gt;
&lt;p&gt;타이머처럼 비동기 결과를 받는 가장 원시적인 방법은 &lt;strong&gt;콜백 함수 (callback)&lt;/strong&gt; 다.&lt;/p&gt;
&lt;h3 id=&quot;콜백이-뭐다&quot;&gt;콜백이 뭐다?&lt;/h3&gt;
&lt;p&gt;함수를 다른 함수의 인자로 넘기면, 받은 쪽이 “내가 끝나면 너를 호출할게” 라고 약속한다. 이때 넘긴 함수가 [[콜백]] 이다. 일급 함수 ([[일급 함수]]) 가 JS 의 기본이라 자연스럽게 가능한 패턴.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 비동기 작업: 1초 뒤에 데이터 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loadUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { id: userId, name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Alice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    callback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, user); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// (error, result) 관행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;loadUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(err);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user.name); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// &apos;Alice&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node.js 가 채택한 &lt;strong&gt;“에러 우선 콜백 (error-first callback)”&lt;/strong&gt; 규칙은 첫 인자가 에러, 두 번째가 결과. 같은 약속을 모두가 지키면 코드를 읽을 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;그런데-두-개를-순서대로-하려면&quot;&gt;그런데 두 개를 순서대로 하려면?&lt;/h3&gt;
&lt;p&gt;사용자를 받아오고, 그 사용자의 게시물을 받아오고, 게시물에 달린 댓글을 받아오고, 댓글 작성자 프로필을 받아오고…&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“콜백 지옥 (callback hell)”
language=“javascript”
code={&lt;code&gt;loadUser(42, (err, user) =&gt; {   if (err) return console.error(err);   loadPosts(user.id, (err, posts) =&gt; {     if (err) return console.error(err);     loadComments(posts[0].id, (err, comments) =&gt; {       if (err) return console.error(err);       loadProfile(comments[0].authorId, (err, profile) =&gt; {         if (err) return console.error(err);         console.log(profile.name);       });     });   }); });&lt;/code&gt;}
output={&lt;code&gt;(4단계 모두 성공 시 profile.name 출력)&lt;/code&gt;}
/&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;들여쓰기가 오른쪽 끝까지 밀려난다. [[콜백 지옥]] 으로 불리는 이 문제는 단순히 가독성만의 문제가 아니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
콜백 지옥의 진짜 문제는 &lt;strong&gt;에러 처리&lt;/strong&gt; 다. 매 단계마다 &lt;code&gt;if (err) return&lt;/code&gt; 을 빠뜨리면 그대로 무시된다. &lt;code&gt;try/catch&lt;/code&gt; 도 통하지 않는다, 콜백이 호출되는 시점에는 등록한 콜 스택이 이미 사라졌기 때문이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;콜백이-가진-또-다른-함정-인버전-오브-컨트롤&quot;&gt;콜백이 가진 또 다른 함정, 인버전 오브 컨트롤&lt;/h3&gt;
&lt;p&gt;콜백을 넘기는 순간, 호출자는 &lt;strong&gt;제어권을 상대 함수에게 넘긴다&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;thirdPartyApi.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  saveToDb&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;thirdPartyApi.fetch&lt;/code&gt; 는 콜백을:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;호출하지 않을 수도&lt;/strong&gt; (조용히 실패)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;여러 번 호출할 수도&lt;/strong&gt; (&lt;code&gt;saveToDb&lt;/code&gt; 가 두 번 실행됨)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;즉시 동기로 호출할 수도&lt;/strong&gt; (콜 스택 상태가 호출자 기대와 다름)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;너무 늦게 호출할 수도&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 모든 불확실성을 호출자가 흡수해야 한다. &lt;strong&gt;약속 (promise) 이 더 강한 보장을 줄 수 있다면 좋을 텐데&lt;/strong&gt;, 라는 갈증이 다음 발전의 동기가 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;resolve-와-reject-봐도-봐도-헷갈리는-그것&quot;&gt;resolve 와 reject, 봐도 봐도 헷갈리는 그것&lt;/h2&gt;
&lt;p&gt;Promise 본격 도입 전에, 가장 헷갈리는 부분을 먼저 정리하자. &lt;strong&gt;resolve, reject 가 누구 손에 있는가&lt;/strong&gt; 의 문제다.&lt;/p&gt;
&lt;p&gt;velog 의 한 글 (&lt;a href=&quot;https://velog.io/@rejoelve/%EB%B4%90%EB%8F%84-%EB%B4%90%EB%8F%84-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-resolve-reject&quot;&gt;봐도 봐도 헷갈리는 resolve, reject&lt;/a&gt;) 이 잘 표현한 것처럼, 비동기 코드에는 두 입장이 공존한다.&lt;/p&gt;
&lt;h3 id=&quot;두-입장&quot;&gt;두 입장&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;요청하는 쪽&lt;/strong&gt;: 비동기 결과를 &lt;em&gt;기다리는&lt;/em&gt; 입장. &lt;code&gt;loadUser(42, callback)&lt;/code&gt; 의 callback 을 쓰는 쪽.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;처리하는 쪽&lt;/strong&gt;: 비동기 작업을 &lt;em&gt;수행하고 결과를 알려주는&lt;/em&gt; 입장. &lt;code&gt;loadUser&lt;/code&gt; 내부 구현.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;콜백 패턴에서는 이 두 입장이 &lt;em&gt;같은 함수 인자&lt;/em&gt; (&lt;code&gt;callback(err, data)&lt;/code&gt;) 로 표현된다. 위치가 곧 의미였다, 1번 자리는 에러, 2번 자리는 데이터.&lt;/p&gt;
&lt;p&gt;Promise 는 이 둘을 &lt;strong&gt;명시적으로 분리&lt;/strong&gt; 한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loadUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 처리하는 쪽&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;userId) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        reject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;userId 필수&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 실패&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ id: userId, name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Alice&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 성공&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 요청하는 쪽&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;loadUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user.name))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(err));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처리하는 쪽이 보는 것: &lt;code&gt;resolve&lt;/code&gt; / &lt;code&gt;reject&lt;/code&gt; 두 함수. 둘 중 하나를 호출하면 자기 일은 끝.&lt;/p&gt;
&lt;p&gt;요청하는 쪽이 보는 것: &lt;code&gt;.then&lt;/code&gt; / &lt;code&gt;.catch&lt;/code&gt; 두 메서드. 결과가 오면 이 두 곳 중 하나로 흘러온다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;strong&gt;resolve = “이게 결과야”&lt;/strong&gt;, &lt;strong&gt;reject = “이게 사유야”&lt;/strong&gt;.
둘 중 하나만 호출하면 된다. 둘 다 호출해도 두 번째 호출은 &lt;strong&gt;조용히 무시&lt;/strong&gt; 된다 (한 번 정착된 Promise 는 상태가 바뀌지 않는다).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;처리하는-쪽--요청하는-쪽-분리의-가치&quot;&gt;처리하는 쪽 / 요청하는 쪽 분리의 가치&lt;/h3&gt;
&lt;p&gt;콜백에서는 &lt;em&gt;호출자&lt;/em&gt; 가 “에러 위치를 첫 번째 인자로 받겠다” 같은 관행을 기억해야 했다. Promise 에서는 &lt;strong&gt;언어 차원에서 두 경로가 분리&lt;/strong&gt; 된다. 잘못 쓸 수가 없다.&lt;/p&gt;
&lt;p&gt;이게 Promise 가 단순히 콜백 지옥을 푸는 도구를 넘어, &lt;strong&gt;에러 처리의 기본 구조를 바꾼&lt;/strong&gt; 진짜 이유다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;promise-상태-기반-처리&quot;&gt;Promise, 상태 기반 처리&lt;/h2&gt;
&lt;p&gt;이제 Promise 자체를 들여다보자.&lt;/p&gt;
&lt;h3 id=&quot;promise-의-세-가지-상태&quot;&gt;Promise 의 세 가지 상태&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;[[Promise]] 객체는 단 세 가지 상태 중 하나에 있다.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;상태&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;어떻게 들어가는가&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;pending&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;대기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;new Promise(...)&lt;/code&gt; 직후&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;fulfilled&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이행 (성공)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;executor 가 &lt;code&gt;resolve(value)&lt;/code&gt; 호출&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;rejected&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;거부 (실패)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;executor 가 &lt;code&gt;reject(reason)&lt;/code&gt; 호출, 또는 예외 throw&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;핵심 규칙: &lt;strong&gt;한 번 fulfilled 또는 rejected 가 되면 다시는 바뀌지 않는다.&lt;/strong&gt; 이 불변성을 [[settled]] 라고 부른다. Promise 가 콜백보다 안전한 핵심 이유다, 한 번 정착하면 끝, 두 번 호출되거나 호출 안 되는 일이 없다.&lt;/p&gt;
&lt;h3 id=&quot;직접-만들어보기&quot;&gt;직접 만들어보기&lt;/h3&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“Promise 생성과 사용”
language=“javascript”
code={`function loadUser(userId) {
return new Promise((resolve, reject) =&gt; {
console.log(‘Promise 생성, 상태: pending’);
setTimeout(() =&gt; {
if (userId &gt; 0) {
resolve({ id: userId, name: ‘Alice’ });
} else {
reject(new Error(‘Invalid ID’));
}
}, 500);
});
}&lt;/p&gt;
&lt;p&gt;const p = loadUser(42);
console.log(‘p =’, p);&lt;/p&gt;
&lt;p&gt;p.then((user) =&gt; {
console.log(‘상태: fulfilled, value =’, user);
});&lt;code&gt;}   output={&lt;/code&gt;Promise 생성, 상태: pending
p = Promise { &lt;pending&gt; }
상태: fulfilled, value = { id: 42, name: ‘Alice’ }`}
/&gt;&lt;/pending&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;new Promise(executor)&lt;/code&gt; 의 &lt;strong&gt;executor 는 동기적으로 즉시 실행&lt;/strong&gt; 된다. 그 안에서 &lt;code&gt;setTimeout&lt;/code&gt; 같은 비동기 작업을 시작하고, 결과가 나오면 &lt;code&gt;resolve&lt;/code&gt; / &lt;code&gt;reject&lt;/code&gt; 를 호출한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;code&gt;.then&lt;/code&gt; 의 콜백은 &lt;strong&gt;결코 동기적으로 실행되지 않는다.&lt;/strong&gt; 이미 fulfilled 된 Promise 에 &lt;code&gt;.then&lt;/code&gt; 을 붙여도, 그 콜백은 &lt;strong&gt;마이크로태스크 큐를 거쳐&lt;/strong&gt; 다음 틱에 실행된다. 이 보장 덕에 “어떨 땐 동기, 어떨 땐 비동기” 같은 인버전 오브 컨트롤이 사라진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;then-catch-finally-세-가족&quot;&gt;.then, .catch, .finally 세 가족&lt;/h3&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;메서드&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;받는 콜백&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;반환&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;언제 호출?&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;.then(onFulfilled, onRejected?)&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;두 개 (선택적)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;새 Promise&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;fulfilled 시 1번, rejected 시 2번 (있다면)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;.catch(onRejected)&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;한 개&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;새 Promise&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;rejected 시 (사실은 &lt;code&gt;.then(undefined, fn)&lt;/code&gt; 의 별칭)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;.finally(onFinally)&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;한 개 (인자 없음)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;새 Promise&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;fulfilled/rejected 모두&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;.then&lt;/code&gt; 이 &lt;em&gt;항상 새 Promise 를 반환&lt;/em&gt; 하기 때문에 체이닝이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;loadUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user.id)         &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 다음 then 에 42 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetchPosts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(id))    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Promise 반환 → 자동 unwrap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(count))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(err))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;done&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;콜백 지옥의 들여쓰기가 사라지고 &lt;strong&gt;평탄한 (flat) 흐름&lt;/strong&gt; 으로 바뀐다.&lt;/p&gt;
&lt;h3 id=&quot;catch-vs-then-의-두-번째-인자&quot;&gt;catch vs then 의 두 번째 인자&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.then&lt;/code&gt; 은 두 번째 인자로도 에러 핸들러를 받지만, 함정이 있다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“.then(onFulfilled, onRejected) 의 함정”
language=“javascript”
code={&lt;code&gt;Promise.resolve(&apos;hi&apos;)   .then(     (value) =&gt; {       console.log(value);       throw new Error(&apos;oops&apos;);     },     (err) =&gt; {       console.log(&apos;잡힘?&apos;, err); // ← 잡힐까?     }   );&lt;/code&gt;}
output={&lt;code&gt;hi (Uncaught Promise Error: oops) // 두 번째 인자는 같은 then 의 onFulfilled 에서 던진 에러를 못 잡는다&lt;/code&gt;}
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;onRejected&lt;/code&gt; 는 &lt;strong&gt;이전 단계&lt;/strong&gt; 의 에러만 잡는다. 자기 자신과 짝지어진 &lt;code&gt;onFulfilled&lt;/code&gt; 에서 던진 에러는 못 잡는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.catch&lt;/code&gt; 는 별도 단계이므로 이전 모든 &lt;code&gt;.then&lt;/code&gt; 의 에러를 모두 잡는다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;strong&gt;&lt;code&gt;.catch&lt;/code&gt; 가 권장된다.&lt;/strong&gt; 자기 단계의 에러도, 이전 모든 단계의 에러도 잡을 수 있다. 캡틴판교 (&lt;a href=&quot;https://joshua1988.github.io/web-development/javascript/promise-for-beginners/&quot;&gt;Promise 쉽게 이해하기&lt;/a&gt;) 의 결론도 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;체이닝의-진짜-메커니즘&quot;&gt;체이닝의 진짜 메커니즘&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.then&lt;/code&gt; 의 콜백이 반환하는 값에 따라 다음 단계에 들어가는 값이 결정된다.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;반환&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;다음 then 의 입력&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;일반 값 &lt;code&gt;x&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;x&lt;/code&gt; 를 fulfilled value 로 가진 새 Promise&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Promise &lt;code&gt;p&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;p&lt;/code&gt; 가 정착할 때까지 기다린 뒤 그 값으로&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Thenable 객체&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Promise 처럼 풀어서 처리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;예외 throw&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;rejected Promise&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이게 인파님이 정리한 &lt;a href=&quot;https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC-Promise&quot;&gt;“프로미스 체이닝”&lt;/a&gt; 의 핵심 메커니즘이다, &lt;strong&gt;&lt;code&gt;.then&lt;/code&gt; 의 반환값은 자동으로 Promise 로 감싸진다.&lt;/strong&gt; 그래서 &lt;code&gt;.then().then().then()&lt;/code&gt; 이 평탄하게 이어진다.&lt;/p&gt;
&lt;h3 id=&quot;promise-도-지옥이-될-수-있다&quot;&gt;Promise 도 지옥이 될 수 있다&lt;/h3&gt;
&lt;p&gt;장점만 있을 리 없다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/users&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/api/posts?user=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/api/comments?post=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;comments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(comments);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.then&lt;/code&gt; 안에서 또 &lt;code&gt;.then&lt;/code&gt; 을 중첩하면 결국 콜백 지옥의 사촌인 &lt;strong&gt;Promise 지옥&lt;/strong&gt; 이 된다. 평탄하게 펴는 게 핵심.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/users&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/api/posts?user=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/api/comments?post=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;comments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(comments));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그래도 가독성이 100% 만족스럽진 않다. 이 마지막 한 걸음이 &lt;strong&gt;async/await&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;asyncawait-promise-의-문법-설탕&quot;&gt;async/await, Promise 의 문법 설탕&lt;/h2&gt;
&lt;h3 id=&quot;한-키워드로-동기처럼&quot;&gt;한 키워드로 동기처럼&lt;/h3&gt;
&lt;p&gt;ES2017 이 도입한 &lt;code&gt;async&lt;/code&gt;, &lt;code&gt;await&lt;/code&gt; 키워드는 Promise 기반 코드를 &lt;strong&gt;마치 동기 코드처럼&lt;/strong&gt; 쓸 수 있게 해준다. 본질은 동일한 Promise, 문법만 다르다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“Promise vs async/await”
variants={[
{
language: ‘javascript’,
label: ‘Promise 체이닝’,
code: &lt;code&gt;fetch(&apos;/api/users&apos;)   .then((r) =&gt; r.json())   .then((users) =&gt; fetch(\&lt;/code&gt;/api/posts?u=${users[0].id}`))
.then((r) =&gt; r.json())
.then((posts) =&gt; console.log(posts))
.catch((err) =&gt; console.error(err));&lt;code&gt;    },     {       language: &apos;javascript&apos;,       label: &apos;async / await&apos;,       code:&lt;/code&gt;async function loadData() {
try {
const userRes = await fetch(‘/api/users’);
const users = await userRes.json();
const postRes = await fetch(`/api/posts?u=${users[0].id}`);
const posts = await postRes.json();
console.log(posts);
} catch (err) {
console.error(err);
}
}
loadData();&lt;code&gt;     }   ]}   outputLabel=&quot;동작&quot;   output={&lt;/code&gt;(둘 다 동일하게 동작, 에러는 catch 에서 잡힘)`}
/&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;세 가지 규칙:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;async&lt;/code&gt; 함수는 항상 Promise 를 반환.&lt;/strong&gt; 명시적으로 &lt;code&gt;return&lt;/code&gt; 한 값이 &lt;code&gt;resolve&lt;/code&gt; 된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;await&lt;/code&gt; 는 오른쪽 Promise 가 정착할 때까지 함수 실행을 &lt;em&gt;일시정지&lt;/em&gt;.&lt;/strong&gt; 단, 함수 밖의 동기 코드는 계속 실행된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러는 &lt;code&gt;try/catch&lt;/code&gt; 로.&lt;/strong&gt; Promise 의 &lt;code&gt;.catch&lt;/code&gt; 와 정확히 같은 의미.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;await-의-진짜-동작&quot;&gt;await 의 진짜 동작&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt; 는 사실 &lt;code&gt;.then&lt;/code&gt; 의 문법 설탕이다. 다음 두 코드는 동치.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// async/await&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; foo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loadA&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loadB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Promise 로 풀어보면&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; foo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loadA&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loadB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;엔진 안에서 &lt;code&gt;await&lt;/code&gt; 는 이렇게 동작한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;오른쪽 표현식을 평가, Promise 가 아니면 &lt;code&gt;Promise.resolve&lt;/code&gt; 로 감싼다.&lt;/li&gt;
&lt;li&gt;함수 상태 (지역 변수, 다음 실행 위치) 를 [[Lexical Environment]] 에 저장.&lt;/li&gt;
&lt;li&gt;Promise 가 정착하면 마이크로태스크 큐에 “함수 재개” 를 등록.&lt;/li&gt;
&lt;li&gt;이벤트 루프가 그 마이크로태스크를 꺼내 함수를 이어 실행.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;&lt;code&gt;await&lt;/code&gt; 는 “Promise 의 마이크로태스크 등록 + 함수 일시정지” 다.&lt;/strong&gt; 그래서 &lt;code&gt;await&lt;/code&gt; 이후 코드는 &lt;em&gt;항상 다음 마이크로태스크 틱에 실행&lt;/em&gt; 된다. 동기 코드보다 늦고, setTimeout 보다 빠르다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;async-함수도-항상-promise&quot;&gt;async 함수도 항상 Promise&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; answer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; answer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(p);          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Promise { 42 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 42&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 보장이 좋은 점은 &lt;strong&gt;모든 async 함수가 동일한 타입을 반환&lt;/strong&gt; 한다는 것이다. 호출자는 “이게 Promise 일까 값일까” 를 고민하지 않아도 된다.&lt;/p&gt;
&lt;h3 id=&quot;한-가지-함정-순차-vs-병렬&quot;&gt;한 가지 함정, 순차 vs 병렬&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 순차, 총 2초&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/a&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1초&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/b&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1초&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 병렬, 총 1초&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/a&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/b&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt; 를 무지성으로 줄 세우면 &lt;strong&gt;병렬로 할 수 있는 일도 순차&lt;/strong&gt; 가 된다. 의존성이 없는 작업은 &lt;code&gt;Promise.all&lt;/code&gt; 로 묶자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;동시성-제어-promise-의-정적-메서드-네-형제&quot;&gt;동시성 제어, Promise 의 정적 메서드 네 형제&lt;/h2&gt;
&lt;p&gt;여러 Promise 를 한꺼번에 다룰 때 쓰는 네 메서드를 차례로 보자.&lt;/p&gt;
&lt;h3 id=&quot;promiseall-모두-성공해야-성공&quot;&gt;Promise.all, 모두 성공해야 성공&lt;/h3&gt;
&lt;p&gt;배열의 모든 Promise 가 fulfilled 되면 그 값들의 배열로 fulfilled. &lt;strong&gt;하나라도&lt;/strong&gt; rejected 되면 즉시 rejected.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“Promise.all”
language=“javascript”
code={&lt;code&gt;const results = await Promise.all([   fetch(&apos;/api/users&apos;).then(r =&gt; r.json()),   fetch(&apos;/api/posts&apos;).then(r =&gt; r.json()),   fetch(&apos;/api/comments&apos;).then(r =&gt; r.json()), ]); // results = [users, posts, comments] console.log(results[0].length);&lt;/code&gt;}
outputLabel=“동작”
output={&lt;code&gt;(3개 fetch 가 병렬로 시작, 모두 끝나면 결과 배열) (하나라도 실패하면 즉시 throw)&lt;/code&gt;}
/&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;Promise.all 은 일찍 끝난 성공을 버린다.&lt;/strong&gt; 두 번째 fetch 가 실패하면 첫 번째와 세 번째가 성공했어도 결과를 받을 수 없다. “전부 또는 무” 의미가 필요할 때만 쓰자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;promiseallsettled-결과를-다-보고-싶을-때&quot;&gt;Promise.allSettled, 결과를 다 보고 싶을 때&lt;/h3&gt;
&lt;p&gt;ES2020 에 추가된 신상. 모든 Promise 가 정착할 때까지 기다린 뒤, 각 결과의 &lt;strong&gt;상태와 값&lt;/strong&gt; 을 담은 배열을 반환.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“Promise.allSettled”
language=“javascript”
code={`const results = await Promise.allSettled([
fetch(‘/api/users’),
fetch(‘/api/posts’),
fetch(‘/broken-url’),
]);&lt;/p&gt;
&lt;p&gt;results.forEach((r, i) =&gt; {
if (r.status === ‘fulfilled’) {
console.log(`${i}: ok`);
} else {
console.log(`${i}: failed -`, r.reason.message);
}
});&lt;code&gt;}   outputLabel=&quot;출력&quot;   output={&lt;/code&gt;0: ok
1: ok
2: failed - fetch failed`}
/&gt;&lt;/p&gt;
&lt;p&gt;각 항목은 &lt;code&gt;{ status: &apos;fulfilled&apos;, value }&lt;/code&gt; 또는 &lt;code&gt;{ status: &apos;rejected&apos;, reason }&lt;/code&gt; 형태. 부분 실패가 자연스러운 작업 (배치 알림 발송, 여러 API 동시 시도) 에 적합.&lt;/p&gt;
&lt;h3 id=&quot;promiserace-가장-먼저-정착한-것&quot;&gt;Promise.race, 가장 먼저 정착한 것&lt;/h3&gt;
&lt;p&gt;성공/실패 무관하게 &lt;strong&gt;가장 먼저 정착한&lt;/strong&gt; Promise 의 결과를 그대로 반환.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 타임아웃 패턴&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ms&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; reject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;timeout&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)), ms);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;race&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/slow&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  timeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 3초 안에 fetch 가 끝나면 result, 아니면 throw&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;promiseany-하나만-성공하면-ok&quot;&gt;Promise.any, 하나만 성공하면 OK&lt;/h3&gt;
&lt;p&gt;ES2021. &lt;strong&gt;하나라도&lt;/strong&gt; fulfilled 되면 그 값으로 fulfilled. &lt;strong&gt;모두&lt;/strong&gt; rejected 되면 &lt;code&gt;AggregateError&lt;/code&gt; 로 rejected.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 여러 CDN 미러 중 하나라도 응답하면 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://mirror1/data&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://mirror2/data&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://mirror3/data&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;네-형제-비교&quot;&gt;네 형제 비교&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;메서드&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;언제 fulfilled&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;언제 rejected&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;결과&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;&lt;code&gt;Promise.all&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모두 fulfilled&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;하나라도 rejected&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;값 배열 / 첫 rejection&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;&lt;code&gt;Promise.allSettled&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모두 정착 (성공/실패 무관)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;절대 안 됨&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;{ status, value/reason }&lt;/code&gt; 배열&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;&lt;code&gt;Promise.race&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가장 먼저 fulfilled&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가장 먼저 rejected&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가장 먼저 정착한 값/사유&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;&lt;code&gt;Promise.any&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;하나라도 fulfilled&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모두 rejected&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;첫 fulfilled / AggregateError&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
실무에서 가장 자주 잘못 쓰는 게 &lt;code&gt;Promise.all&lt;/code&gt; 이다. &lt;strong&gt;일부 실패를 허용해야 한다면 &lt;code&gt;allSettled&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;응답이 빠른 쪽이 중요하다면 &lt;code&gt;race&lt;/code&gt; 또는 &lt;code&gt;any&lt;/code&gt;&lt;/strong&gt; 를 떠올리자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;마이크로태스크-vs-매크로태스크-우선순위의-비밀&quot;&gt;마이크로태스크 vs 매크로태스크, 우선순위의 비밀&lt;/h2&gt;
&lt;p&gt;이제 글머리의 첫 질문으로 돌아갈 차례. &lt;strong&gt;왜 Promise.then 이 setTimeout 0 보다 항상 먼저&lt;/strong&gt; 인가?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;이벤트-루프의-한-틱&quot;&gt;이벤트 루프의 한 틱&lt;/h3&gt;
&lt;p&gt;이벤트 루프가 한 사이클 (틱) 을 도는 알고리즘을 좀 더 정확하게 보자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;한 틱:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  1. 태스크 큐에서 한 개 꺼내 실행 (예: setTimeout 콜백)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     - 콜 스택이 빌 때까지 계속&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  2. 마이크로태스크 큐를 비울 때까지 *전부* 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     - 마이크로태스크 안에서 새 마이크로태스크를 등록해도 같은 틱 안에서 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  3. 필요하면 렌더링 (브라우저)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  4. 다음 틱으로&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;핵심&lt;/strong&gt;: 한 틱에 태스크 큐는 &lt;em&gt;한 개&lt;/em&gt; 만 처리하지만, 마이크로태스크 큐는 &lt;em&gt;완전히 비울 때까지&lt;/em&gt; 처리한다. 그래서 마이크로태스크가 항상 다음 매크로태스크보다 먼저다.&lt;/p&gt;
&lt;h3 id=&quot;실제로-분석해보기&quot;&gt;실제로 분석해보기&lt;/h3&gt;
&lt;p&gt;처음의 코드를 다시 보자.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“시작 의문의 코드, 다시”
language=“javascript”
code={&lt;code&gt;console.log(&apos;A&apos;); setTimeout(() =&gt; console.log(&apos;B&apos;), 0); Promise.resolve().then(() =&gt; console.log(&apos;C&apos;)); console.log(&apos;D&apos;);&lt;/code&gt;}
output={&lt;code&gt;A D C B&lt;/code&gt;}
/&gt;&lt;/p&gt;
&lt;p&gt;이벤트 루프의 눈으로 한 줄씩 따라가보자.&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;단계&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;상태&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;출력&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1. &lt;code&gt;console.log(&apos;A&apos;)&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;콜 스택, 동기 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;A&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2. &lt;code&gt;setTimeout(B, 0)&lt;/code&gt; 등록&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Web API → 0ms 뒤 태스크 큐에 B&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3. &lt;code&gt;Promise.resolve().then(C)&lt;/code&gt; 등록&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;즉시 마이크로태스크 큐에 C&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4. &lt;code&gt;console.log(&apos;D&apos;)&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;콜 스택, 동기 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;D&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5. 동기 코드 종료, 콜 스택 빔&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;,&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;6. 마이크로태스크 큐 비우기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;C&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;7. 태스크 큐에서 한 개 꺼냄&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;B 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;B&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;마이크로태스크는 “이번 동기 작업이 끝나자마자 즉시”, 매크로태스크는 “다음 이벤트 루프 틱에 한 개씩”.&lt;/strong&gt; 그래서 Promise.then 이 setTimeout 0 보다 항상 빠르다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;await-도-마이크로태스크다&quot;&gt;await 도 마이크로태스크다&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; chain&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;2&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 마이크로태스크&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;3&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 또 마이크로태스크&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;start&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;chain&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;end&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// → start, 1, end, 2, 3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt; 가 만나면 함수 실행이 일시정지되고, “재개” 가 마이크로태스크 큐에 들어간다. 그래서 &lt;code&gt;end&lt;/code&gt; 가 먼저, 그 다음 &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt; 이 차례로 찍힌다.&lt;/p&gt;
&lt;h3 id=&quot;마이크로태스크의-폭주-함정&quot;&gt;마이크로태스크의 폭주 함정&lt;/h3&gt;
&lt;p&gt;마이크로태스크는 “큐를 &lt;em&gt;완전히&lt;/em&gt; 비울 때까지” 처리하기 때문에, &lt;strong&gt;마이크로태스크 안에서 또 마이크로태스크를 무한히 등록하면 렌더링과 매크로태스크가 영원히 못 돈다&lt;/strong&gt;.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;매크로태스크로 같은 일을 하면 매 틱마다 한 번씩만 실행되므로 렌더링이 끼어들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(loop, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 매 틱마다 한 번씩&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;loop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// → CPU 는 바쁘지만 렌더링과 이벤트는 동작&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이게 무거운 작업을 잘게 쪼갤 때 &lt;code&gt;setTimeout&lt;/code&gt; 을 쓰는 이유이기도 하다.&lt;/p&gt;
&lt;h3 id=&quot;queuemicrotask-직접-등록하기&quot;&gt;queueMicrotask, 직접 등록하기&lt;/h3&gt;
&lt;p&gt;ES2020 에 추가된 &lt;code&gt;queueMicrotask(fn)&lt;/code&gt; 은 Promise 없이 마이크로태스크를 등록할 수 있게 해준다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;queueMicrotask&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;이번 동기 작업 끝나면 즉시&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Promise.resolve().then(fn)&lt;/code&gt; 보다 의도가 명확하고, Promise 객체 생성 비용도 없다. 라이브러리 내부에서 “다음 마이크로태스크에 실행” 을 명시할 때 쓴다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;fetch-와-abortcontroller-비동기-작업의-취소&quot;&gt;fetch 와 AbortController, 비동기 작업의 취소&lt;/h2&gt;
&lt;p&gt;Promise 와 async/await 가 자리 잡으면서 자연스럽게 마주치는 다음 문제는 &lt;strong&gt;취소&lt;/strong&gt; 다. 사용자가 검색어를 빠르게 입력하면 이전 fetch 요청은 더 이상 필요 없다. 어떻게 멈출까?&lt;/p&gt;
&lt;h3 id=&quot;abortcontroller-의-기본&quot;&gt;AbortController 의 기본&lt;/h3&gt;
&lt;p&gt;ES2017 (DOM 표준) 에 도입된 [[AbortController]] 는 비동기 작업에 &lt;em&gt;취소 신호 (signal)&lt;/em&gt; 를 전달하는 표준 메커니즘이다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
title=“fetch + AbortController”
language=“javascript”
code={`const controller = new AbortController();
const signal = controller.signal;&lt;/p&gt;
&lt;p&gt;// 5초 뒤 자동 취소
setTimeout(() =&gt; controller.abort(), 5000);&lt;/p&gt;
&lt;p&gt;try {
const res = await fetch(‘/api/slow’, { signal });
const data = await res.json();
console.log(data);
} catch (err) {
if (err.name === ‘AbortError’) {
console.log(‘취소됨’);
} else {
console.error(err);
}
}&lt;code&gt;}   outputLabel=&quot;시나리오별&quot;   cases={[     { label: &apos;5초 내 응답 옴&apos;, output: &lt;/code&gt;(data 출력)&lt;code&gt;},     { label: &apos;5초 넘김&apos;, output:&lt;/code&gt;취소됨` },
]}
/&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;구조&lt;/strong&gt; 는 단순하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;new AbortController()&lt;/code&gt; 로 컨트롤러 생성.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;controller.signal&lt;/code&gt; 을 비동기 함수에 전달.&lt;/li&gt;
&lt;li&gt;취소가 필요하면 &lt;code&gt;controller.abort()&lt;/code&gt; 호출.&lt;/li&gt;
&lt;li&gt;받는 쪽은 &lt;code&gt;AbortError&lt;/code&gt; (혹은 &lt;code&gt;DOMException&lt;/code&gt;) 로 reject.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;검색-자동완성에-적용&quot;&gt;검색 자동완성에 적용&lt;/h3&gt;
&lt;p&gt;가장 흔한 사용 사례. 사용자가 타이핑할 때마다 이전 요청을 취소.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; currentController &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 이전 요청 취소&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (currentController) currentController.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;abort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  currentController &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AbortController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/api/search?q=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      signal: currentController.signal,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    showResults&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (err.name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;AbortError&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // AbortError 는 정상, 더 새로운 요청이 있다는 뜻&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;input.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;input&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e.target.value));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;code&gt;AbortController&lt;/code&gt; 는 fetch 전용이 아니다. &lt;strong&gt;모든 비동기 API 가 받을 수 있는 표준&lt;/strong&gt; 이다. Node.js 의 &lt;code&gt;fs/promises&lt;/code&gt;, &lt;code&gt;setTimeout&lt;/code&gt; (Node 16+), &lt;code&gt;EventTarget.addEventListener&lt;/code&gt; 등 점점 더 많은 곳에서 &lt;code&gt;{ signal }&lt;/code&gt; 을 받는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;settimeout-도-취소할-수-있다-node-16&quot;&gt;setTimeout 도 취소할 수 있다 (Node 16+)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; controller&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AbortController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; controller.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;abort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, { signal: controller.signal });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;5초 후 도착&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (err.name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;AbortError&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;1초 뒤 취소됨&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;타이머도 Promise 기반 + 취소 가능한 시대.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;generator-와-asynciterator-흐름을-일시정지하는-또-다른-길&quot;&gt;Generator 와 AsyncIterator, 흐름을 일시정지하는 또 다른 길&lt;/h2&gt;
&lt;p&gt;Promise 와 async/await 외에도 JS 에는 비동기 흐름을 다루는 다른 도구가 있다. &lt;strong&gt;Generator&lt;/strong&gt; 와 &lt;strong&gt;AsyncIterator&lt;/strong&gt; 다.&lt;/p&gt;
&lt;h3 id=&quot;generator-한-번에-한-값씩&quot;&gt;Generator, 한 번에 한 값씩&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;function*&lt;/code&gt; 으로 선언한 함수 ([[function]] 의 확장 문법) 는 호출 시 즉시 실행되지 않고, &lt;strong&gt;이터레이터&lt;/strong&gt; 를 반환한다. &lt;code&gt;yield&lt;/code&gt; 키워드로 실행을 일시정지하고 값을 흘려보낸다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;일반 [[function]] 과 [[arrow function]] 의 본질적 차이 (특히 &lt;code&gt;this&lt;/code&gt; 바인딩) 는 비동기 콜백에서 자주 함정이 된다. 두 형태의 정확한 차이는 각 위키 참고.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;Generator 가 흥미로운 점은 &lt;strong&gt;무한 시퀀스를 메모리 효율적으로&lt;/strong&gt; 표현할 수 있다는 것.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;yield&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; g&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;g.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().value; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;g.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().value; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ... 무한히 가능&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(자세한 메커니즘은 [[Lexical Environment 와 클로저]] 글의 &lt;code&gt;naturals&lt;/code&gt; 예제 참고)&lt;/p&gt;
&lt;h3 id=&quot;asynciterator-비동기-버전&quot;&gt;AsyncIterator, 비동기 버전&lt;/h3&gt;
&lt;p&gt;ES2018 이 도입한 &lt;code&gt;async function*&lt;/code&gt; 은 &lt;strong&gt;비동기 결과를 차례로&lt;/strong&gt; 흘려보낸다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;페이지네이션 API&lt;/strong&gt;, &lt;strong&gt;스트림 처리&lt;/strong&gt;, &lt;strong&gt;WebSocket 메시지 순차 처리&lt;/strong&gt; 같은 곳에서 빛난다. &lt;code&gt;Promise.all&lt;/code&gt; 로 한 번에 받을 수 없는 “차례로만 알 수 있는 비동기 시퀀스” 를 표현하는 정통 도구.&lt;/p&gt;
&lt;h3 id=&quot;nodejs-readablestream-과의-조합&quot;&gt;Node.js ReadableStream 과의 조합&lt;/h3&gt;
&lt;p&gt;Node.js 에서 파일을 한 줄씩 읽는 모던한 방법.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; lines&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; rl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; createInterface&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    input: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;createReadStream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(path),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; rl) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;yield&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; line;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; lines&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;huge.log&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (line.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ERROR&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(line);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// → 메모리에 전체 파일을 올리지 않고 한 줄씩&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;for await...of&lt;/code&gt; 와 &lt;code&gt;async function*&lt;/code&gt; 의 조합이 &lt;strong&gt;백프레셔 (backpressure)&lt;/strong&gt; 를 자연스럽게 처리한다. 소비가 느리면 생산도 멈춘다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;requestanimationframe-화면-갱신과-발-맞추기&quot;&gt;requestAnimationFrame, 화면 갱신과 발 맞추기&lt;/h2&gt;
&lt;p&gt;타이머와 Promise 외에 브라우저가 제공하는 또 하나의 비동기 스케줄링이 [[requestAnimationFrame]] (rAF) 이다.&lt;/p&gt;
&lt;h3 id=&quot;매-프레임마다-실행&quot;&gt;매 프레임마다 실행&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;requestAnimationFrame(callback)&lt;/code&gt; 은 “다음 화면 다시 그리기 직전에 콜백을 실행” 한다. 60fps 환경에서 약 16.67ms 마다 호출된다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lastTime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; performance.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; animate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; dt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; now &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lastTime;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  lastTime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; now;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // dt 만큼 위치 갱신&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ball.x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; velocity &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  render&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  requestAnimationFrame&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(animate);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(animate);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;setInterval(animate, 16)&lt;/code&gt; 과의 차이가 결정적이다.&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;항목&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;&lt;code&gt;setInterval(fn, 16)&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;&lt;code&gt;requestAnimationFrame(fn)&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;호출 시점&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;16ms 마다 (큐가 비어있을 때)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;다음 paint 직전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;브라우저 탭이 백그라운드일 때&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;계속 호출 (~1Hz 로 throttle)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;호출 안 됨&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;모니터 주사율&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;무시&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;적응 (60Hz, 120Hz 등)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;프레임 누락&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;자주&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;드묾&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;rAF 는 &lt;strong&gt;렌더링 사이클과 동기화&lt;/strong&gt; 되므로 같은 CPU 비용에서 훨씬 부드러운 결과를 낸다.&lt;/p&gt;
&lt;h3 id=&quot;이벤트-루프와의-관계&quot;&gt;이벤트 루프와의 관계&lt;/h3&gt;
&lt;p&gt;rAF 콜백은 &lt;strong&gt;마이크로태스크도 매크로태스크도 아니다&lt;/strong&gt;. 이벤트 루프 한 틱의 마지막 단계, “렌더링” 단계에 끼어든다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;한 틱:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  1. 매크로태스크 1개 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  2. 마이크로태스크 전부 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  3. 렌더링 단계:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     - requestAnimationFrame 콜백 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     - 스타일 계산 → 레이아웃 → 페인트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  4. 다음 틱으로&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 위치 덕분에 rAF 콜백 안에서 DOM 을 수정하면 &lt;strong&gt;같은 프레임에 반영&lt;/strong&gt; 된다. 매크로태스크에서 수정하면 다음 프레임이 되어야 보인다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정리-비동기-스케줄링의-위계&quot;&gt;정리, 비동기 스케줄링의 위계&lt;/h2&gt;
&lt;p&gt;다섯 개의 큐 / 단계를 시간 순으로 정리하면.&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;우선순위&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;종류&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;예시&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;언제&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;동기 코드&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;console.log&lt;/code&gt;, 함수 호출&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;지금&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;마이크로태스크&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Promise.then&lt;/code&gt;, &lt;code&gt;await&lt;/code&gt;, &lt;code&gt;queueMicrotask&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;동기 끝나자마자, 전부&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;렌더링 단계&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;paint 직전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매크로태스크&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;setInterval&lt;/code&gt;, DOM 이벤트&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;다음 틱에 한 개씩&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;I/O (Node.js)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;net&lt;/code&gt; 콜백&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;poll 단계&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이 위계는 &lt;strong&gt;암기보다 메커니즘 이해가 더 안전&lt;/strong&gt; 하다. “마이크로태스크 큐가 비어야 다음 매크로로 간다” 는 원칙 하나만 잡으면 어떤 코드 출력도 추론 가능하다.&lt;/p&gt;
&lt;h3 id=&quot;콜백--promise--asyncawait-한-줄로-보면&quot;&gt;콜백 → Promise → async/await, 한 줄로 보면&lt;/h3&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;시대&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;도구&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;해결한 것&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;남긴 문제&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;~ES5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;콜백&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;일급 함수로 단순 비동기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;콜백 지옥, 인버전 오브 컨트롤, 에러 처리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;ES6 (2015)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Promise&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;상태 불변성, 평탄한 체이닝, 에러 통합&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;.then().then()&lt;/code&gt; 의 가독성&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;ES2017&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;async / await&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;동기처럼 읽히는 비동기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;순차/병렬 구분 의식 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;ES2018&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;AsyncIterator&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;비동기 시퀀스 표현&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;학습 곡선&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;ES2020&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;queueMicrotask&lt;/code&gt;, &lt;code&gt;Promise.allSettled&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;명시적 스케줄링, 부분 실패&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;,&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;ES2021&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Promise.any&lt;/code&gt;, &lt;code&gt;AggregateError&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;첫 성공만 필요한 패턴&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;,&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;각 단계는 직전 단계가 남긴 문제를 한 걸음씩 푼 결과다. &lt;strong&gt;콜백은 사라지지 않았다.&lt;/strong&gt; 이벤트 리스너, DOM API, 일부 Node.js API 는 여전히 콜백 기반이다. 다만 &lt;em&gt;비동기 흐름의 주축&lt;/em&gt; 이 Promise / async-await 로 옮겨갔을 뿐.&lt;/p&gt;
&lt;h3 id=&quot;실무-선택-가이드&quot;&gt;실무 선택 가이드&lt;/h3&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;상황&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;추천&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HTTP 한 번 호출&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;await fetch()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;여러 호출, 전부 필요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Promise.all&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;여러 호출, 부분 실패 OK&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Promise.allSettled&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;타임아웃 / 가장 빠른 응답&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Promise.race&lt;/code&gt; 또는 &lt;code&gt;Promise.any&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;페이지네이션 / 스트림&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;for await...of&lt;/code&gt; + &lt;code&gt;async function*&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;사용자 입력에 반응하는 fetch&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;AbortController&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;무거운 작업 잘게 쪼개기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; 또는 &lt;code&gt;queueMicrotask&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;부드러운 애니메이션&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;한 번만 미루기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;queueMicrotask&lt;/code&gt; (Promise 객체 생성 비용 ↓)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;세 질문의 답을 한 줄로 정리하면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Q1&lt;/strong&gt;. 어떻게 동시에 처리되나? → &lt;strong&gt;호스트가 백그라운드 처리, 엔진은 이벤트 루프로 큐를 처리.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q2&lt;/strong&gt;. &lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; 이 왜 즉시가 아닌가? → &lt;strong&gt;모든 비동기는 큐를 거쳐야 하며, 4ms 최소 지연이 누적 가능.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q3&lt;/strong&gt;. 왜 Promise 가 setTimeout 보다 먼저인가? → &lt;strong&gt;마이크로태스크 큐가 매크로태스크 큐보다 먼저 비워진다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;비동기는 마법이 아니다. &lt;strong&gt;싱글 스레드 + 호스트의 백그라운드 처리 + 두 종류의 큐 + 이벤트 루프&lt;/strong&gt;, 이 네 가지 조합이 만들어내는 결정론적 시스템이다.&lt;/p&gt;
&lt;p&gt;이 그림을 한 번 머릿속에 두면 React 의 &lt;code&gt;useEffect&lt;/code&gt; 가 왜 그 시점에 호출되는지, Vue 의 &lt;code&gt;nextTick&lt;/code&gt; 이 정확히 무엇을 미루는지, Express 의 미들웨어 체인이 어떻게 흐르는지가 모두 같은 언어로 설명된다.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ 이 글은 ECMAScript 명세 (ECMA-262), MDN Web Docs, HTML 명세 (WHATWG), 그리고 캡틴판교·인파·rejoelve 의 한국어 자료를 참조하여 &lt;strong&gt;AI(Claude)와 함께 작성&lt;/strong&gt; 되었습니다. 마이크로태스크 정확한 처리 순서는 V8 / SpiderMonkey 등 엔진별로 미세한 차이가 있을 수 있으나, 명세 동작은 모두 동일합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ECMA-262, &lt;a href=&quot;https://tc39.es/ecma262/#sec-jobs&quot;&gt;Jobs and Host Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ECMA-262, &lt;a href=&quot;https://tc39.es/ecma262/#sec-promise-objects&quot;&gt;Promise Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;WHATWG HTML, &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop&quot;&gt;Event loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;WHATWG HTML, &lt;a href=&quot;https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers&quot;&gt;Timers (§8.1.7.4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MDN, &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;Promise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MDN, &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function&quot;&gt;async function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MDN, &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/AbortController&quot;&gt;AbortController&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MDN, &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for-await...of&quot;&gt;for await…of&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Philip Roberts, &lt;a href=&quot;https://www.youtube.com/watch?v=8aGhZQkoFbQ&quot;&gt;What the heck is the event loop anyway? (JSConf EU 2014)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;캡틴판교, &lt;a href=&quot;https://joshua1988.github.io/web-development/javascript/promise-for-beginners/&quot;&gt;자바스크립트 Promise 쉽게 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;인파, &lt;a href=&quot;https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC-Promise&quot;&gt;자바스크립트 Promise 개념 &amp;#x26; 문법 정복하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;rejoelve, &lt;a href=&quot;https://velog.io/@rejoelve/%EB%B4%90%EB%8F%84-%EB%B4%90%EB%8F%84-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-resolve-reject&quot;&gt;봐도 봐도 헷갈리는 resolve, reject&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>javascript</category><category>async</category><category>promise</category><category>async-await</category><category>event-loop</category><category>settimeout</category><category>microtask</category><category>fetch</category><category>generator</category><author>koa (김신건)</author></item><item><title>CPU vs GPU vs TPU vs NPU, 네 종류 프로세서의 원리와 최근 동향</title><link>https://shinkeonkim.com/posts/2026-06-16-cpu-gpu-tpu-npu/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-16-cpu-gpu-tpu-npu/</guid><description>같은 면적의 칩에 코어가 어떻게 다르게 배치되는지부터, 메모리 계층, 양자화, 최신 Trillium/Blackwell/NPU 동향, 그리고 컴퓨터 과학 패러다임에 미치는 영향까지.</description><pubDate>Sun, 14 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;시작-왜-네-종류의-프로세서가-필요한가&quot;&gt;시작, 왜 네 종류의 프로세서가 필요한가&lt;/h2&gt;
&lt;p&gt;2010년대 중반까지는 단순했다. CPU 가 있고, 게임이나 그래픽이 필요하면 GPU 가 있었다. 그게 다였다.&lt;/p&gt;
&lt;p&gt;지금은 다르다. 데이터센터에는 TPU(Tensor Processing Unit), 스마트폰에는 NPU(Neural Processing Unit) 가 들어있다. 같은 회사의 같은 제품군에 네 종류의 프로세서가 동시에 동작하는 경우도 흔하다. 예를 들어 최신 노트북:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;: 운영체제, 브라우저, 일반 앱 실행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU&lt;/strong&gt;: 게임, 비디오 인코딩&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NPU&lt;/strong&gt;: 화상 회의 배경 흐림, on-device LLM (Copilot+)&lt;/li&gt;
&lt;li&gt;(TPU 는 데이터센터 쪽)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;왜 이렇게 분화되었나? 답은 단순하다. &lt;strong&gt;하나의 프로세서로는 모든 워크로드를 효율적으로 처리할 수 없게 되었다.&lt;/strong&gt; 특히 AI 워크로드 (행렬 곱셈 위주) 가 폭증하면서 범용 CPU 의 한계가 명확해졌고, 각 워크로드에 특화된 가속기가 등장한 것이다.&lt;/p&gt;
&lt;p&gt;이 글에서는 네 프로세서의 &lt;strong&gt;원리, 메모리 사용 방식, 강약점, 최근 동향, CS 에 미친 영향&lt;/strong&gt; 을 차례로 살펴본다. 각 프로세서의 내부 구조는 매우 다르며, 각자 자기 섹션에서 애니메이션과 함께 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;먼저 가장 기본적인 질문, “&lt;strong&gt;프로세서가 하는 일은 무엇인가&lt;/strong&gt;” 부터.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;기초-프로세서의-본질&quot;&gt;기초, 프로세서의 본질&lt;/h2&gt;
&lt;p&gt;프로세서는 단 두 가지 일을 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;명령어 fetch + decode + execute&lt;/strong&gt; (Von Neumann 사이클)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터를 메모리에서 가져와서 처리하고 다시 쓰기&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 4단계가 모든 현대 컴퓨터의 가장 기본적인 동작 단위. CPU 든 GPU 든 모두 이 사이클을 변형해 진화시킨 것이다.&lt;/p&gt;
&lt;p&gt;이 두 일을 얼마나 빨리, 어떤 패턴으로 하느냐가 모든 차이를 만든다.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;축&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;변형&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;명령어 다양성&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;적음 (특화) ↔ 많음 (범용)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;동시 실행 단위 수&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;적음 (직렬) ↔ 많음 (병렬)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;메모리 접근 패턴&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;무작위 (캐시 의존) ↔ 순차/규칙적&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;분기 처리&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;복잡한 분기 잘 ↔ 분기 없는 코드만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;수치 정밀도&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;64 bit 부동소수 ↔ 4 bit 정수&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;CPU 는 모든 축에서 “왼쪽”에 가깝고, NPU 는 모든 축에서 “오른쪽”에 가깝다. GPU 와 TPU 는 중간의 다른 지점들. 이제 각각을 보자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cpu-범용성의-정수&quot;&gt;CPU, 범용성의 정수&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;동작-원리&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p&gt;CPU 는 어떤 코드든 실행할 수 있는 범용 프로세서다. 그 대가로 한 코어가 매우 복잡하다.&lt;/p&gt;
&lt;p&gt;한 CPU 코어 안에는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;명령어 디코더&lt;/strong&gt;: x86, ARM 등 수백 개 명령어를 해석&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분기 예측기&lt;/strong&gt;: &lt;code&gt;if&lt;/code&gt; 의 결과를 미리 추측해 파이프라인 채움&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;out-of-order 실행 엔진&lt;/strong&gt;: 명령어를 의존성 그래프대로 재정렬해 동시 실행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수십 단계 파이프라인&lt;/strong&gt;: 하나의 명령어를 fetch → decode → execute → writeback 등으로 쪼개 cycle 마다 다른 명령어가 다른 단계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L1/L2/L3 캐시&lt;/strong&gt;: 메모리 지연을 숨기기 위해 최근 데이터 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 애니메이션이 5단계 파이프라인의 동작을 보여준다. 매 cycle 다른 명령어가 다른 단계에서 동시 진행되고, 데이터 의존성이 있으면 bubble (idle) 이 삽입된다. 분기 예측이 실패하면 in-flight 명령어가 모두 폐기되는 큰 손실. 현대 CPU 는 이 5단계 모델을 확장해 14~20 단계 파이프라인 + superscalar (cycle 당 4 명령) + OoO 까지 발전.&lt;/p&gt;
&lt;p&gt;이 모든 회로가 코어 면적의 큰 비율을 차지한다. 그래서 같은 다이 면적에 &lt;strong&gt;CPU 코어는 8~16개&lt;/strong&gt; 정도밖에 들어가지 않는다.&lt;/p&gt;
&lt;h3 id=&quot;메모리-계층&quot;&gt;메모리 계층&lt;/h3&gt;
&lt;p&gt;CPU 는 &lt;strong&gt;캐시 위주&lt;/strong&gt; 메모리 시스템이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Register: ~0.3 ns (1 cycle)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;L1 Cache: ~1 ns (4 cycles), 32~64 KB/core&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;L2 Cache: ~3 ns (12 cycles), 256 KB~1 MB/core&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;L3 Cache: ~10 ns (40 cycles), 수십 MB (모든 코어 공유)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DRAM:    ~100 ns (400 cycles), 16~256 GB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 계층이 잘 작동하면 평균 메모리 접근 시간이 L1 수준에 가까워진다. CPU 가 “복잡하지만 빠른” 이유.&lt;/p&gt;
&lt;p&gt;대역폭은 보통 &lt;strong&gt;50~100 GB/s&lt;/strong&gt; (DDR5 듀얼 채널 기준).&lt;/p&gt;
&lt;h3 id=&quot;강점과-약점&quot;&gt;강점과 약점&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;강점&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;약점&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;어떤 코드든 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;병렬 코어 수 적음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;복잡한 분기 잘 처리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;행렬 곱셈 비효율&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;큰 캐시로 낮은 지연&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;메모리 대역폭 한계 (DDR5: ~89 GB/s)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;OS, 시스템 콜, 인터럽트&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;AI 워크로드 효율 낮음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;최근-동향&quot;&gt;최근 동향&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AMX (Advanced Matrix Extensions)&lt;/strong&gt;: Intel Sapphire Rapids 부터 CPU 에 행렬 곱셈 가속 회로 통합 (mini-TPU)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple Silicon (M4)&lt;/strong&gt;: ARM 기반 + LPDDR5X unified memory (546 GB/s) 로 GPU 메모리 격차 일부 축소&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이종 코어&lt;/strong&gt;: P-core (성능) + E-core (효율) 조합이 표준화 (Intel 12세대 부터)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CPU 는 죽지 않는다. 다만 AI 워크로드는 다른 프로세서에 위임하고, &lt;strong&gt;시스템 오케스트레이션&lt;/strong&gt; 역할로 진화하고 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;gpu-병렬성의-도약&quot;&gt;GPU, 병렬성의 도약&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;그래픽에서-ai-로&quot;&gt;그래픽에서 AI 로&lt;/h3&gt;
&lt;p&gt;GPU 는 원래 게임 그래픽 렌더링용이었다. 픽셀 100만 개를 동시에 계산해야 하는 작업이라 자연스럽게 “&lt;strong&gt;많은 단순한 코어&lt;/strong&gt;” 구조가 되었다.&lt;/p&gt;
&lt;p&gt;GPU 의 본래 임무는 3D 그래픽 파이프라인이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vertex Shader&lt;/strong&gt;: 3D 모델의 꼭짓점 (vertex) 변환, 카메라 시점 적용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rasterization&lt;/strong&gt;: 삼각형들을 화면의 픽셀로 변환&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragment Shader&lt;/strong&gt;: 각 픽셀의 색을 결정 (조명, 그림자, 텍스처 등)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Texture Mapping&lt;/strong&gt;: 2D 이미지를 3D 표면에 매핑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output Merger&lt;/strong&gt;: 최종 픽셀 출력 + 깊이 테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각 단계가 &lt;strong&gt;수백만 ~ 수천만 개의 독립적인 픽셀/vertex&lt;/strong&gt; 에 대해 같은 연산을 적용해야 한다. 그래서 GPU 는 “같은 연산을 다른 데이터에 동시 적용” 하는 구조로 진화했다.&lt;/p&gt;
&lt;p&gt;이 구조가 우연히 딥러닝과 완벽히 맞았다. 신경망의 행렬 곱셈도 결국 &lt;strong&gt;수백만 번의 독립적인 곱셈&lt;/strong&gt;이고, GPU 가 이를 동시에 처리할 수 있었다. 2010년대 초 NVIDIA 가 CUDA 를 일반 컴퓨팅에 개방하면서 게임 GPU 가 AI 컴퓨팅의 표준이 된 것.&lt;/p&gt;
&lt;h3 id=&quot;simt-실행-모델&quot;&gt;SIMT 실행 모델&lt;/h3&gt;
&lt;h4 id=&quot;단계-1-왜-thread-가-많아야-하는가&quot;&gt;단계 1, 왜 thread 가 많아야 하는가&lt;/h4&gt;
&lt;p&gt;GPU 가 “수천 개 코어” 라고 부르는 건 사실 단순한 ALU (산술 연산 회로) 수천 개를 의미한다. CPU 처럼 한 코어가 분기 예측, 캐시, OoO 같은 복잡한 회로를 다 가진 게 아니다.&lt;/p&gt;
&lt;p&gt;그렇다면 이 수천 개 ALU 에게 일을 어떻게 시킬까? 각 ALU 한 개 한 개를 “thread” 라고 부르면 5,000 개의 thread 가 동시에 돌아야 한다. 이게 GPU 가 그래픽에서 출발한 이유와 자연스럽게 맞아떨어진다. 1920x1080 화면의 200만 픽셀이 각자 자기 색을 계산해야 하니까.&lt;/p&gt;
&lt;h4 id=&quot;단계-2-각-thread-마다-모든-회로를-두면-너무-비싸다&quot;&gt;단계 2, 각 thread 마다 모든 회로를 두면 너무 비싸다&lt;/h4&gt;
&lt;p&gt;문제는 thread 마다 &lt;strong&gt;명령어 디코더 + Program Counter (PC)&lt;/strong&gt; 를 가지면 회로가 너무 커진다. 5,000 개의 thread × 디코더 = 5,000 개의 디코더. 이건 칩 면적의 대부분을 디코더가 차지하는 비효율적 설계.&lt;/p&gt;
&lt;p&gt;CPU 가 한 코어에 디코더 1개 두는 것과 정반대.&lt;/p&gt;
&lt;h4 id=&quot;단계-3-nvidia-의-트레이드오프-warp&quot;&gt;단계 3, NVIDIA 의 트레이드오프, warp&lt;/h4&gt;
&lt;p&gt;NVIDIA 의 해결: &lt;strong&gt;32 thread 를 묶어 같은 디코더를 공유&lt;/strong&gt;시킨다. 이 묶음이 “warp”.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;한 SM (Streaming Multiprocessor):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 디코더 1개 (또는 몇개)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 32 thread = 1 warp 가 같은 명령을 동시에 실행&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 64 warp 까지 동시 보유 (서로 다른 warp 는 다른 명령 실행 가능)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - 한 cycle 마다 어떤 warp 의 명령을 실행할지 warp scheduler 가 선택&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비유: 줄 서 있는 32명에게 “왼쪽 발 든다” 한 번 외치면 모두 왼쪽 발을 든다. 한 명 한 명에게 따로 외치지 않아도 됨.&lt;/p&gt;
&lt;p&gt;같은 명령을 받지만 &lt;strong&gt;각자 자기 데이터로 처리&lt;/strong&gt;. Thread 0 은 픽셀 0, Thread 1 은 픽셀 1을 처리. 같은 “픽셀 색 계산” 명령이지만 결과는 다른 픽셀에 저장.&lt;/p&gt;
&lt;h4 id=&quot;단계-4-이게-바로-simt&quot;&gt;단계 4, 이게 바로 SIMT&lt;/h4&gt;
&lt;p&gt;이 모델 이름이 &lt;strong&gt;SIMT (Single Instruction, Multiple Threads)&lt;/strong&gt;. 한 명령으로 여러 thread 가 다른 데이터를 처리.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비슷한 개념으로 [[SIMT|SIMD (Single Instruction, Multiple Data)]] 가 있다. CPU 의 AVX-512 같은 벡터 명령. 차이는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SIMD&lt;/strong&gt;: 컴파일러가 “한 번에 16개 float 더해” 같은 벡터 명령을 명시적으로 만들어야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SIMT&lt;/strong&gt;: 프로그래머는 그냥 “thread 마다 a[i] + b[i] 한다” 라고 짜면, 하드웨어가 알아서 32개씩 묶어 같은 명령으로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SIMT 가 프로그래밍이 훨씬 자연스럽다. CUDA 가 인기를 끈 핵심 이유.&lt;/p&gt;
&lt;h4 id=&quot;단계-5-분기가-갈리면-어떻게-되나-warp-divergence&quot;&gt;단계 5, 분기가 갈리면 어떻게 되나, warp divergence&lt;/h4&gt;
&lt;p&gt;문제는 같은 명령을 32 thread 가 모두 실행한다는 가정. 그런데 &lt;code&gt;if (x &gt; 0)&lt;/code&gt; 같은 분기에서 일부 thread 만 if 경로, 나머지가 else 경로를 타면?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (data[threadIdx.x] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sqrt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data[threadIdx.x]);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 일부 thread 만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; data[threadIdx.x] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 나머지 thread 만&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해결: &lt;strong&gt;두 경로를 직렬로 실행&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cycle 1: warp 의 (x &gt; 0) thread 만 활성, 나머지는 idle. &lt;code&gt;sqrt(x)&lt;/code&gt; 실행&lt;/li&gt;
&lt;li&gt;Cycle 2: warp 의 (x ≤ 0) thread 만 활성, 나머지는 idle. &lt;code&gt;x * 2&lt;/code&gt; 실행&lt;/li&gt;
&lt;li&gt;결과적으로 같은 warp 의 활용률이 50% 로 떨어짐. 이게 &lt;strong&gt;warp divergence&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GPU 코드 최적화의 핵심은 같은 warp 의 thread 들이 같은 분기를 타도록 데이터를 정렬하는 것. 예) 정수 양수/음수가 섞인 배열이면 미리 정렬해서 warp 안에서 분기가 안 갈리게.&lt;/p&gt;
&lt;h4 id=&quot;단계-6-규모&quot;&gt;단계 6, 규모&lt;/h4&gt;
&lt;p&gt;NVIDIA H100:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;132 SM × 64 warp × 32 thread = &lt;strong&gt;약 27만 thread 동시 활성&lt;/strong&gt; 이론상&lt;/li&gt;
&lt;li&gt;실제로는 메모리 대역폭, divergence, warp 간 의존성으로 활용률 일부&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 정도 thread 가 동시에 같은 행렬 곱셈 명령을 실행하면 LLM 학습/추론이 빠를 수밖에 없다.&lt;/p&gt;
&lt;h4 id=&quot;단계-7-spmd-와의-관계&quot;&gt;단계 7, [[SPMD]] 와의 관계&lt;/h4&gt;
&lt;p&gt;SIMT 모델은 사실 [[SPMD]] (Single Program Multiple Data) 의 GPU 하드웨어 구현이다. MPI 같은 분산 컴퓨팅에서 모든 노드가 같은 프로그램을 실행하되 다른 데이터를 처리하는 패턴. GPU 의 thread = MPI 의 노드, warp = 같은 명령 broadcast 단위.&lt;/p&gt;
&lt;p&gt;분산 학습 코드를 작성해본 사람이라면 CUDA 코드가 익숙하게 느껴지는 이유.&lt;/p&gt;
&lt;h3 id=&quot;메모리-계층-1&quot;&gt;메모리 계층&lt;/h3&gt;
&lt;p&gt;CPU 와 결정적으로 다르다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HBM (High Bandwidth Memory)&lt;/strong&gt;: 칩 옆에 수직 적층된 DRAM, [[HBM]] 참조&lt;/li&gt;
&lt;li&gt;L2 cache (40~60 MB) + L1 per SM (128 KB)&lt;/li&gt;
&lt;li&gt;캐시는 지연 감소보다 &lt;strong&gt;대역폭 압력 완화&lt;/strong&gt; 가 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대역폭 비교:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H100: &lt;strong&gt;HBM3 5 stack = 3,350 GB/s&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;B200: &lt;strong&gt;HBM3e 8 stack = 8,000 GB/s&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;(CPU DDR5: 89 GB/s)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GPU 가 CPU 대비 30~90 배 메모리 대역폭. 이게 딥러닝 학습/추론에서의 격차를 만든다.&lt;/p&gt;
&lt;h3 id=&quot;강점과-약점-1&quot;&gt;강점과 약점&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;강점&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;약점&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;대규모 병렬 처리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;분기 많은 코드 (warp divergence)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;행렬/텐서 연산 빠름&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단일 thread 성능 낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HBM 으로 메모리 대역폭 압도&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;비싼 가격 (H100 ~$30K)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;CUDA 생태계 성숙&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;전력 소모 큼 (700W+)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;최근-동향-blackwell&quot;&gt;최근 동향 (Blackwell)&lt;/h3&gt;
&lt;p&gt;NVIDIA Blackwell 아키텍처 (B200, GB200):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HBM3e 12-stack 으로 4 TB/s 대역폭&lt;/li&gt;
&lt;li&gt;FP6, FP4 새 수치 포맷 지원 (모델 양자화 가속)&lt;/li&gt;
&lt;li&gt;다중 다이 패키지 (multi-chip module)&lt;/li&gt;
&lt;li&gt;Transformer Engine 2세대, LLM 추론 30 배 가속 주장&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CUDA 의 락인 효과는 여전히 강력하지만 AMD ROCm, Intel oneAPI 도 추격 중.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;tpu-행렬-곱셈에-회로를-고정시키다&quot;&gt;TPU, 행렬 곱셈에 회로를 고정시키다&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;왜-tpu-가-필요한가-gpu-가-있는데도&quot;&gt;왜 TPU 가 필요한가, GPU 가 있는데도&lt;/h3&gt;
&lt;p&gt;GPU 가 AI 에 잘 맞는다는 게 알려졌지만, Google 은 이를 자체 데이터센터에 도입하기 어려웠다. 이유 3가지:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;그래픽 회로의 낭비&lt;/strong&gt;: GPU 면적의 상당 부분 (RT Core, ROP, rasterizer 등) 이 게임용. AI 워크로드에는 죽은 면적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CUDA 락인&lt;/strong&gt;: NVIDIA 의 비싼 가격을 NVIDIA 가 결정. 대안 부재&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;워크로드 특화 효율의 한계&lt;/strong&gt;: 범용 GPU 의 SIMT 는 행렬 곱셈에 최적은 아님&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Google 의 답: &lt;strong&gt;자체 ASIC&lt;/strong&gt;. 2015년 첫 TPU. 목표는 단 하나, &lt;strong&gt;행렬 곱셈만, 가장 효율적으로&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;tpu-가-gpu-대비-가지는-구체적-강점&quot;&gt;TPU 가 GPU 대비 가지는 구체적 강점&lt;/h4&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;GPU (H100)&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;TPU (v6 Trillium)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;행렬 곱셈 효율&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tensor Core 가 보조&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MXU 전용 회로, 한 cycle 65,536 MAC&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;메모리 접근&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SIMT, 매 명령마다 메모리&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Weight Stationary, 가중치 1번 적재&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;전력 효율&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;700W&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;200W (3.5배 효율)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;그래픽 회로&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;30% 면적&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;컴파일러 통합&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;CUDA (low-level)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;XLA (high-level)&lt;/strong&gt;, Python 그대로&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;확장성&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;8 GPU per node (NVLink)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;256 chip per pod (ICI)&lt;/strong&gt;, pod 간 광통신&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;TPU 의 진정한 강점은 &lt;strong&gt;데이터센터 스케일&lt;/strong&gt;. 한 pod 에 256 chip, 그 pod 가 수백 개 모인 cluster 가 13 Pbps 데이터센터 네트워크로 연결. &lt;strong&gt;하나의 거대한 컴퓨터처럼&lt;/strong&gt; 학습 가능.&lt;/p&gt;
&lt;h3 id=&quot;systolic-array&quot;&gt;Systolic Array&lt;/h3&gt;
&lt;p&gt;TPU 의 핵심은 [[Systolic Array]] 다. MXU (Matrix Multiply Unit) 라고 부른다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심 아이디어 3가지:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Weight Stationary&lt;/strong&gt;: 가중치는 PE 에 미리 적재되어 움직이지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Activation Flow&lt;/strong&gt;: 입력이 격자를 박동(systolic)처럼 흐르며 곱셈+누적&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메모리 접근 최소화&lt;/strong&gt;: 한 가중치가 N x N 번의 곱셈에 재사용&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;256x256 MXU 한 cycle 에 &lt;strong&gt;65,536 번의 MAC&lt;/strong&gt; 동시 수행.&lt;/p&gt;
&lt;h3 id=&quot;메모리&quot;&gt;메모리&lt;/h3&gt;
&lt;p&gt;GPU 의 HBM 과 유사하지만:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unified Buffer (24~32 MB on-chip SRAM)&lt;/strong&gt;: layer 간 활성화 보관 → DRAM 접근 최소화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HBM&lt;/strong&gt;: TPU v6 에서 32 GB / 1,640 GB/s&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;복잡한 캐시 계층 없이 큰 SRAM + HBM 의 단순한 구조. Systolic array 가 메모리 접근을 적게 하니까 가능한 설계.&lt;/p&gt;
&lt;h3 id=&quot;tpu-의-진화&quot;&gt;TPU 의 진화&lt;/h3&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;세대&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;출시&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;주요 변화&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;TPU v1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2015&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;추론 전용, 8-bit INT&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;TPU v2&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2017&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;학습 지원, bf16&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;TPU v3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2018&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;액체 냉각&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;TPU v4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2021&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SparseCore (embedding 가속)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;TPU v5e/v5p&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2023&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;비용 최적화, 대규모 학습&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;TPU v6 (Trillium)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;2024&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;918 TFLOPs bf16, 4.7x v5e&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Trillium 의 의미: v5e 대비 4.7x 컴퓨트, 67% 에너지 효율 개선, 한 pod 에 256 chip = &lt;strong&gt;234.9 PFLOPs&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;강점과-약점-2&quot;&gt;강점과 약점&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;강점&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;약점&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;행렬 곱셈 최고 효율&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TensorFlow/JAX 외 생태계 약함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;전력 효율 우수 (TPU v6 67% 개선)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Google Cloud 외 사용 불가&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;HBM + 대용량 SRAM&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;행렬이 아닌 워크로드는 느림&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;대규모 pod 확장 (10,000+ chip)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가격 정보 불투명&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;npu-디바이스-위-ai-의-핵심&quot;&gt;NPU, 디바이스 위 AI 의 핵심&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;왜-npu-가-필요한가-gpu-가-있는데도&quot;&gt;왜 NPU 가 필요한가, GPU 가 있는데도&lt;/h3&gt;
&lt;p&gt;스마트폰에도 GPU 가 있다. Adreno (Qualcomm), Mali (ARM), Apple GPU. 그런데도 별도의 NPU 가 생긴 이유는 명확하다.&lt;/p&gt;
&lt;h4 id=&quot;모바일에서-gpu-의-한계&quot;&gt;모바일에서 GPU 의 한계&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;전력 예산&lt;/strong&gt;: 스마트폰 전체 SoC 가 5~10W 가 한계. GPU 를 켜면 게임/그래픽으로 거의 다 씀&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 추론은 게임과 동시에 필요&lt;/strong&gt;: 화상 통화 중 배경 흐림 (GPU 가 영상 인코딩 중) + 카메라 객체 인식 + 음성 비서 등 동시 실행 시 GPU 만으론 부족&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;양자화 효율&lt;/strong&gt;: GPU 는 FP16 위주 회로. INT4 같은 극한 양자화에는 회로 활용률이 떨어짐&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;항상 켜진 워크로드&lt;/strong&gt;: 음성 비서 (“Hey Siri”), 항상 동작하는 카메라 보호, 손동작 인식 등은 GPU 깨우기엔 전력 낭비&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;npu-의-구체적-강점&quot;&gt;NPU 의 구체적 강점&lt;/h4&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;모바일 GPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;NPU (Hexagon, Apple NE)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;와트당 효율 (INT4)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~0.5 TOPS/W&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;15+ TOPS/W (30배)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;수치 정밀도&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;FP32/FP16&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;INT4/INT8 native&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;항상 켜진 추론&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;어려움 (전력 큼)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;가능, 마이크로와트&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;메모리&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;GPU 전용 일부&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SoC LPDDR 공유, 작지만 충분&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;부팅 시간&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수십 ms&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;수 ms (인터럽트 응답급)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h4 id=&quot;실제-사용-시나리오&quot;&gt;실제 사용 시나리오&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hey Siri&lt;/strong&gt;: 음성 비서 detection. CPU 를 깨우지 않고 NPU 가 1mW 로 항상 listening&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;카메라 객체 인식&lt;/strong&gt;: 동영상 녹화 중 GPU 는 인코딩, NPU 는 얼굴/물체 인식&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;on-device LLM&lt;/strong&gt;: Apple Intelligence, Snapdragon AI Hub 가 7B~10B 모델을 INT4 로 실행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배경 흐림 (Zoom/Teams)&lt;/strong&gt;: 화상 통화 중 NPU 가 segmentation, GPU 는 영상 인코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NPU 가 없는 폰에서도 가능하지만, &lt;strong&gt;배터리가 빠르게 닳거나 발열로 throttling&lt;/strong&gt; 발생.&lt;/p&gt;
&lt;h3 id=&quot;양자화의-중요성&quot;&gt;양자화의 중요성&lt;/h3&gt;
&lt;p&gt;NPU 의 핵심 특기는 [[양자화]] 다. 데이터센터 TPU 는 bf16 (16 bit) 정밀도가 일반적이지만, NPU 는 더 공격적으로 줄인다.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;정밀도&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;비트수&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;메모리 절약&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;정확도 손실&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;FP32&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;32&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1x&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;FP16 / bf16&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;16&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2x&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;거의 없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;INT8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;4x&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;&amp;#x3C;1%&lt;/code&gt; (대부분의 모델)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;INT4&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;8x&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;2~5% (LLM 정량화 가능)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;NPU 는 INT8, INT4 연산을 하드웨어 회로로 가속한다. 같은 모델을 4-bit 로 돌리면 메모리 8x 절약 + 처리량 4x.&lt;/p&gt;
&lt;h3 id=&quot;모바일-soc-의-npu-경쟁-2025-2026&quot;&gt;모바일 SoC 의 NPU 경쟁 (2025-2026)&lt;/h3&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;제품&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;NPU&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;TOPS&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Apple A19 Pro / M4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Neural Engine&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;38&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;16-core, on-device 모델 (Apple Intelligence)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Snapdragon 8 Elite&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hexagon NPU&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;75&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;INT4 native, on-device LLM&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Snapdragon 8 Gen 4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hexagon NPU&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;80+&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2025 출시&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;MediaTek Dimensity 9500&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;APU&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;60+&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2nm 공정&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Samsung Exynos 2400&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;NPU&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;34.7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Galaxy S24+&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Intel Lunar Lake&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;NPU 4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;48&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Windows Copilot+ 자격 (40 TOPS 이상)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Qualcomm Snapdragon X Elite&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hexagon&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;45&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Copilot+ PC&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Copilot+ PC 인증 = NPU 40 TOPS 이상&lt;/strong&gt;이 표준 기준이 되면서 NPU 가 데스크톱/노트북에도 의무화되는 추세.&lt;/p&gt;
&lt;h3 id=&quot;강점과-약점-3&quot;&gt;강점과 약점&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;강점&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;약점&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;와트당 효율 최고 (TOPS/W)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;학습 불가, 추론 전용&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;SoC 통합으로 추가 칩 불필요&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;메모리 작음 (디바이스 RAM 공유)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;INT4/INT8 양자화 우수&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모델 변환 + 양자화 필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;항상 사용 가능, 저전력&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;새 아키텍처 모델 지원 늦음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;한-그림-비교&quot;&gt;한 그림 비교&lt;/h2&gt;











































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;CPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;GPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;TPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;NPU&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;코어 수&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;4~64&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수천~수만&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수만 PE&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;수천 PE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;한 코어 복잡도&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매우 높음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;낮음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매우 낮음&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;매우 낮음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;메모리 시스템&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;큰 캐시 + DRAM&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HBM + 작은 캐시&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SRAM + HBM&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SoC 공유 메모리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;메모리 대역폭&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;50~100 GB/s&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1~8 TB/s&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1~3 TB/s&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;~100 GB/s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;주 워크로드&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;범용&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;그래픽, AI 학습&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;AI 학습/추론&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;디바이스 AI 추론&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;수치 정밀도&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;FP64/FP32/INT&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;FP32/FP16/INT8&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bf16/INT8&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;INT8/INT4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;전력&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;15~250W&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;300~1000W&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;200~500W&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;1~10W&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;사용처&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;서버, PC, 모바일&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;데이터센터, PC&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Google Cloud&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모바일, edge, PC&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;가격&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$200~$5,000&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$1,000~$30,000&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;(Cloud)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;SoC 통합&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;메모리-대역폭-시각화&quot;&gt;메모리 대역폭 시각화&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h3 id=&quot;hbm-세대별-대역폭-진화&quot;&gt;HBM 세대별 대역폭 진화&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;h3 id=&quot;모바일-npu-tops-비교-2025-플래그십&quot;&gt;모바일 NPU TOPS 비교 (2025 플래그십)&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;hr&gt;
&lt;h2 id=&quot;최근-동향-2024--2026&quot;&gt;최근 동향 (2024 ~ 2026)&lt;/h2&gt;
&lt;h3 id=&quot;1-학습은-gpu-와-tpu-추론은-npu-로-분화&quot;&gt;1. 학습은 GPU 와 TPU, 추론은 NPU 로 분화&lt;/h3&gt;
&lt;p&gt;대규모 학습 (LLM, 비전 모델) 은 NVIDIA H100/B200 또는 Google TPU 가 거의 독점. 반면 추론 (이미 학습된 모델로 응답 생성) 은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터센터 추론&lt;/strong&gt;: 여전히 GPU/TPU&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;on-device 추론&lt;/strong&gt;: NPU 가 빠르게 점유 (Apple Intelligence, Snapdragon AI Hub, Windows Copilot+)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이유: 학습은 막대한 메모리/연산이 필요하지만, 추론은 양자화 + 작은 모델 (3B~10B) 이면 NPU 로 충분히 빠름.&lt;/p&gt;
&lt;h3 id=&quot;2-양자화-포맷의-폭증&quot;&gt;2. 양자화 포맷의 폭증&lt;/h3&gt;
&lt;p&gt;전통적 FP32 만 쓰던 시절은 지났다. 2024-2026 에는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FP16, bf16&lt;/strong&gt;: 학습 표준&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INT8&lt;/strong&gt;: 추론 가속, 거의 모든 모델 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INT4&lt;/strong&gt;: LLM 추론, 메모리 8x 절약&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FP8&lt;/strong&gt; (NVIDIA H100): 학습 + 추론 양쪽&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FP6, FP4&lt;/strong&gt; (NVIDIA B200): 새 표준&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MXFP4&lt;/strong&gt; (Microsoft scaled FP4): 미세 양자화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;각 정밀도마다 하드웨어 회로가 필요. NPU 는 INT4 native 지원 (Snapdragon 8 Gen 2+).&lt;/p&gt;
&lt;h3 id=&quot;3-메모리-대역폭이-진짜-병목&quot;&gt;3. 메모리 대역폭이 진짜 병목&lt;/h3&gt;
&lt;p&gt;대형 LLM 의 inference 시간은 &lt;strong&gt;모델 가중치를 메모리에서 가져오는 시간&lt;/strong&gt;이 dominant. 그래서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HBM4 가 2026년 약 2 TB/s 로 등장&lt;/li&gt;
&lt;li&gt;NVIDIA Rubin (HBM4 기반) 예정&lt;/li&gt;
&lt;li&gt;SK Hynix HBM 점유율 75%+, AI 메모리 시장 폭증&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-heterogeneous-computing-의-표준화&quot;&gt;4. heterogeneous computing 의 표준화&lt;/h3&gt;
&lt;p&gt;한 SoC 안에 CPU + GPU + NPU + 메모리 + 보안 가속기가 모두 통합. 워크로드별로 가장 적합한 코어로 자동 라우팅:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;사진 촬영 → ISP + NPU (HDR, 객체 인식)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;브라우저 → CPU + GPU (렌더링)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;화상 회의 → NPU (배경 흐림) + GPU (인코딩)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;게임 → CPU + GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;LLM 응답 → NPU (작은 모델) or 클라우드 GPU (큰 모델)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OS 가 작업 스케줄러가 되는 시대. Apple, Qualcomm, Intel 모두 비슷한 방향.&lt;/p&gt;
&lt;h3 id=&quot;5-새-가속기-카테고리의-등장&quot;&gt;5. 새 가속기 카테고리의 등장&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cerebras WSE-3&lt;/strong&gt;: wafer-scale (한 wafer 가 한 chip), 4 trillion transistor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Groq LPU&lt;/strong&gt;: 결정론적 LLM 추론 가속, 초당 수천 토큰&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tenstorrent Wormhole&lt;/strong&gt;: 오픈소스 RISC-V 기반 AI 칩&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NVIDIA 독점 구도에 균열이 보이는 시점.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cs-에-미치는-영향&quot;&gt;CS 에 미치는 영향&lt;/h2&gt;
&lt;p&gt;네 종류 프로세서의 등장이 컴퓨터 과학 패러다임 자체를 바꿨다.&lt;/p&gt;
&lt;h3 id=&quot;1-알고리즘-설계의-transformation&quot;&gt;1. 알고리즘 설계의 transformation&lt;/h3&gt;
&lt;p&gt;전통적 알고리즘 평가 기준은 &lt;strong&gt;시간 복잡도 O(n)&lt;/strong&gt;. 이제는 부족하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;메모리 접근 패턴&lt;/strong&gt;: 캐시 친화적인가? coalesced 인가?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;병렬화 가능성&lt;/strong&gt;: 데이터 의존성이 적은가?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수치 정밀도 허용 범위&lt;/strong&gt;: 4-bit 로도 충분한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;같은 O(n) 알고리즘이라도 GPU 에서 30 배 빠를 수 있고, 캐시 미스 패턴에 따라 같은 CPU 에서도 100 배 차이가 난다.&lt;/p&gt;
&lt;p&gt;알고리즘 책의 “RAM 모델” (균일한 메모리 접근 가정) 은 이제 거의 허구.&lt;/p&gt;
&lt;h3 id=&quot;2-모델-양자화-의-부상&quot;&gt;2. 모델 [[양자화]] 의 부상&lt;/h3&gt;
&lt;p&gt;ML 연구에서 새 분야로 자리잡은 &lt;strong&gt;post-training quantization (PTQ), quantization-aware training (QAT)&lt;/strong&gt;. 같은 모델을 4-bit 로 만들면서 정확도 손실을 최소화하는 게 활발한 연구 주제.&lt;/p&gt;
&lt;p&gt;[[양자화|GPTQ, AWQ, GGUF (llama.cpp), QLoRA]] 같은 기법들이 단 1-2년 사이에 표준이 됨. 양자화 덕분에 ChatGPT 급 모델을 24 GB RTX 4090 한 장 또는 CPU 만으로 실행 가능해졌다.&lt;/p&gt;
&lt;h3 id=&quot;3-시스템-소프트웨어의-재편&quot;&gt;3. 시스템 소프트웨어의 재편&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;컴파일러&lt;/strong&gt;: PyTorch 2.0 의 torch.compile, JAX 의 jit, TensorFlow 의 XLA, MLIR 같은 ML 컴파일러 인프라&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;런타임&lt;/strong&gt;: ONNX Runtime, TensorRT, Core ML, Hexagon SDK 같은 디바이스별 추론 런타임&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS 차원의 통합&lt;/strong&gt;: Windows Copilot+ Runtime, macOS Apple Intelligence Framework&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;전통적 OS 의 스케줄러가 CPU thread 만 다뤘다면, 이제는 &lt;strong&gt;NPU 워크로드 라우팅&lt;/strong&gt; 까지 책임.&lt;/p&gt;
&lt;h3 id=&quot;4-데이터센터-설계-변화&quot;&gt;4. 데이터센터 설계 변화&lt;/h3&gt;
&lt;p&gt;NVIDIA DGX, Google TPU Pod 같은 AI 슈퍼컴퓨터가 표준이 됨. 단순한 서버 rack 이 아니라:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;액체 냉각 (수냉)&lt;/li&gt;
&lt;li&gt;50+ kW per rack (일반 서버 5~10 kW 대비)&lt;/li&gt;
&lt;li&gt;광 통신 (NVLink, ICI)&lt;/li&gt;
&lt;li&gt;HBM 의 비싼 가격이 전체 비용의 1/3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;데이터센터 건축, 전력 인프라, 네트워크 설계 모두 AI 가속기 중심으로 재편.&lt;/p&gt;
&lt;h3 id=&quot;5-분산-학습-의-일반화&quot;&gt;5. [[분산 학습]] 의 일반화&lt;/h3&gt;
&lt;p&gt;한 GPU 에 안 들어가는 모델 (LLM 100B+) 을 학습시키는 게 일상화. 그래서 다양한 [[분산 학습|병렬화 전략]] 이 표준이 됨:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Parallelism (DP)&lt;/strong&gt;: 같은 모델 복제, 다른 배치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tensor Parallelism (TP)&lt;/strong&gt;: 한 layer 의 행렬을 여러 GPU 에 분할&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipeline Parallelism (PP)&lt;/strong&gt;: 모델 layer 를 GPU 들에 수직 분배&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZeRO / FSDP&lt;/strong&gt;: 모델 parameter/gradient/optimizer state 자체를 sharding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3D / 4D Parallelism&lt;/strong&gt;: 위 전략들의 조합 (Megatron-LM 표준)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Llama 3.1 405B 학습에는 TP=8, PP=16, DP=125 조합 16,000 H100 GPU 가 사용됨.&lt;/p&gt;
&lt;p&gt;분산 시스템 설계가 ML 연구자의 필수 지식이 되었고, [[SPMD]] (Single Program Multiple Data) 같은 전통 HPC 개념이 ML 영역의 표준이 됨.&lt;/p&gt;
&lt;h3 id=&quot;6-ai-first-사용자-경험&quot;&gt;6. AI-first 사용자 경험&lt;/h3&gt;
&lt;p&gt;NPU 가 있는 디바이스에서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;항상 켜진 음성 비서 (전력 거의 안 씀)&lt;/li&gt;
&lt;li&gt;실시간 카메라 객체 인식&lt;/li&gt;
&lt;li&gt;오프라인 번역, 음성 인식&lt;/li&gt;
&lt;li&gt;개인정보 누출 없는 on-device LLM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;클라우드에 의존하지 않는 AI 가 가능해진 게 NPU 덕분.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;p&gt;네 종류의 프로세서가 공존하는 이유는 단순하다.&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;CPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;GPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;TPU&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;NPU&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;존재 이유&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;범용 명령 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;대규모 병렬&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;행렬 곱셈 회로화&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;디바이스 AI 효율&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;희생한 것&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;병렬성&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단일 thread 성능 + 분기&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;워크로드 유연성&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;학습 능력&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;얻은 것&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;모든 코드 실행&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;30~90배 대역폭&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;행렬 효율 극대화&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;와트당 효율&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
“가장 좋은 프로세서” 는 없다. &lt;strong&gt;워크로드에 맞는 프로세서를 고르는 것&lt;/strong&gt;이 중요하다. 그리고 현대 시스템은 거의 모든 경우 &lt;strong&gt;여러 종류를 동시에 사용&lt;/strong&gt;한다 (heterogeneous computing).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;향후 5~10년 동안은 더 분화될 가능성이 크다. Quantum Processing Unit, Optical Computing, In-Memory Computing 같은 새 카테고리가 실험되고 있고, 일부는 상용화될 것이다. 그러나 핵심 원리, &lt;strong&gt;“같은 일을 다른 방식으로 더 효율적으로”&lt;/strong&gt;, 는 그대로다.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ 이 글은 Google Cloud TPU v6 문서, NVIDIA Blackwell 발표, Qualcomm Hexagon NPU 백서, EigenState/L-P 의 2026년 AI 하드웨어 가이드, 그리고 2025 플래그십 SoC 비교 논문을 참조하여 &lt;strong&gt;AI(Claude)와 함께 작성&lt;/strong&gt;되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/blog/products/compute/introducing-trillium-6th-gen-tpus&quot;&gt;Google Cloud, TPU v6 (Trillium) 소개&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/tpu/docs/v6e&quot;&gt;Google Cloud, TPU v6e 기술 사양&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nvidia.com/en-us/data-center/technologies/blackwell-architecture/&quot;&gt;NVIDIA Blackwell Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/Unlocking-on-device-generative-AI-with-an-NPU-and-heterogeneous-computing.pdf&quot;&gt;Qualcomm Hexagon NPU 백서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.eigenstate.dev/essay/cpu-vs-gpu-vs-tpu-vs-npu-ai-hardware-architecture-guide-2026&quot;&gt;EigenState, CPU vs GPU vs TPU vs NPU AI Hardware Architecture Guide 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://machinelearning.apple.com/&quot;&gt;Apple Neural Engine 정보&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1704.04760&quot;&gt;Google In-Datacenter Performance Analysis of a TPU (ISCA 2017)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://news.skhynix.com/&quot;&gt;SK Hynix, HBM Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jedec.org/&quot;&gt;JEDEC HBM3 / HBM4 Standards&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>hardware</category><category>cpu</category><category>gpu</category><category>tpu</category><category>npu</category><category>ai</category><category>processor</category><category>architecture</category><author>koa (김신건)</author></item><item><title>함수는 끝났는데 변수가 살아있다, Lexical Environment와 클로저의 메커니즘</title><link>https://shinkeonkim.com/posts/2026-06-13-lexical-environment-closure/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-13-lexical-environment-closure/</guid><description>스코프와 Environment 개념부터 차근차근 쌓아 naturals 이터레이터 예제의 세 가지 질문에 답해본다. n은 어디에 사는가, 왜 GC되지 않는가, 외부 변수와 어떻게 격리되는가.</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;시작-의문&quot;&gt;시작 의문&lt;/h2&gt;
&lt;p&gt;다음과 같은 무한 자연수 이터레이터를 만든다고 하자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Iterator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ← 어디에 사는 변수?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IteratorResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { value: n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, done: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { value: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, done: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 1, done: false }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 2, done: false }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 3, done: false }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;naturals(5)&lt;/code&gt; 함수 호출은 첫 줄에서 끝났다. 그런데 &lt;code&gt;it.next()&lt;/code&gt;를 호출하면 &lt;code&gt;n&lt;/code&gt;이 살아있고, 호출할 때마다 1씩 증가한다.&lt;/p&gt;
&lt;p&gt;세 가지 질문이 떠오른다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;n&lt;/code&gt;은 정확히 어느 메모리 영역에 사는가?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;함수가 종료됐는데 왜 GC되지 않는가?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;외부에 &lt;code&gt;let n = 100&lt;/code&gt;이 있으면 어느 쪽이 이긴다고 보장할 수 있는가?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;답을 바로 던지면 “클로저 때문이다” 한 줄로 끝난다. 하지만 그게 &lt;em&gt;어떻게&lt;/em&gt; 가능한지를 이해하려면 한 단계 더 들어가야 한다. 이 글은 &lt;strong&gt;스코프&lt;/strong&gt;·&lt;strong&gt;Environment&lt;/strong&gt;·&lt;strong&gt;참조 사슬&lt;/strong&gt;·&lt;strong&gt;스코프 체인 워킹&lt;/strong&gt; 네 개념을 차례로 쌓아 세 질문 모두에 답하는 것을 목표로 한다.&lt;/p&gt;
&lt;p&gt;먼저 가장 기초적인 곳부터.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;한-단계-뒤로-스코프와-environment-가-왜-필요한가&quot;&gt;한 단계 뒤로, 스코프와 Environment 가 왜 필요한가&lt;/h2&gt;
&lt;h3 id=&quot;코드는-명령어-데이터는-값&quot;&gt;코드는 명령어, 데이터는 값&lt;/h3&gt;
&lt;p&gt;JavaScript 엔진이 &lt;code&gt;let x = 5&lt;/code&gt;를 만나는 순간 두 가지 일이 동시에 일어난다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;이름&lt;/strong&gt; &lt;code&gt;x&lt;/code&gt; 라는 식별자가 등록된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;값&lt;/strong&gt; &lt;code&gt;5&lt;/code&gt; 가 어딘가 메모리에 자리잡고, &lt;code&gt;x&lt;/code&gt; 와 묶인다(binding).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 “이름 → 값” 묶음이 &lt;strong&gt;binding&lt;/strong&gt;이다. 함수 코드 자체는 그저 명령어 덩어리지만, 함수가 실행되는 동안 만들어지는 binding 들은 &lt;strong&gt;어딘가 별도의 저장소&lt;/strong&gt;에 보관되어야 한다. 그 저장소를 ECMAScript 명세는 &lt;strong&gt;Environment Record&lt;/strong&gt; 라고 부른다.&lt;/p&gt;
&lt;h3 id=&quot;같은-이름이-여러-곳에-있을-때-스코프&quot;&gt;같은 이름이 여러 곳에 있을 때, 스코프&lt;/h3&gt;
&lt;p&gt;다음 코드를 보자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; outer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; inner&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(x); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 어느 x?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  inner&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;outer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;세 군데에 &lt;code&gt;x&lt;/code&gt; 가 있다. &lt;code&gt;inner()&lt;/code&gt; 안의 &lt;code&gt;console.log(x)&lt;/code&gt; 는 어떤 &lt;code&gt;x&lt;/code&gt; 를 가리키는가? 인간이 보기엔 “가장 가까운 30”이 답이지만, 엔진은 어떻게 이 규칙을 구현하는가?&lt;/p&gt;
&lt;p&gt;답은 &lt;strong&gt;스코프(scope)&lt;/strong&gt; 라는 개념이다. 코드의 각 함수·블록은 자기만의 스코프를 가지고, 안쪽 스코프에서 변수를 찾다가 못 찾으면 바깥 스코프를 본다. 스코프들이 부모-자식 관계로 사슬을 이루고, 이 사슬을 &lt;strong&gt;스코프 체인&lt;/strong&gt; 이라 한다.&lt;/p&gt;
&lt;p&gt;여기서 한 가지 결정이 필요하다, &lt;em&gt;어떤 함수가 “바깥”인가?&lt;/em&gt; 정의 시점의 위치인가, 호출 시점의 위치인가?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lexical(어휘적) 스코프&lt;/strong&gt;: 정의 시점 기준. 코드를 쓴 자리가 바깥을 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic(동적) 스코프&lt;/strong&gt;: 호출 시점 기준. 어디서 호출됐는지가 바깥을 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JavaScript는 Lexical 스코프를 선택했다. 그래서 정확한 명세 이름이 “&lt;strong&gt;Lexical&lt;/strong&gt; Environment”다. 호출 위치가 어디든, 정의 위치의 바깥 스코프가 영원히 바깥이 된다.&lt;/p&gt;
&lt;h3 id=&quot;호출마다-새-영역이-필요한-이유&quot;&gt;호출마다 새 영역이 필요한 이유&lt;/h3&gt;
&lt;p&gt;마지막 기초 한 조각. 같은 함수가 여러 번 호출되면 어떻게 되는가?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; counter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1 (1이 또!)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;counter()&lt;/code&gt; 두 호출은 각자 자기 &lt;code&gt;n&lt;/code&gt; 을 가져야 한다. 만약 같은 &lt;code&gt;n&lt;/code&gt; 을 공유했다면 두 번째 호출은 &lt;code&gt;2&lt;/code&gt; 를 출력했을 것이다.&lt;/p&gt;
&lt;p&gt;재귀 호출은 더 명확하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; factorial&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; factorial&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;factorial&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// factorial(3) 의 n = 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// factorial(2) 의 n = 2  ← 동시에 살아 있어야 함&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// factorial(1) 의 n = 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;세 호출의 &lt;code&gt;n&lt;/code&gt; 은 동시에 메모리에 살아 있어야 한다. &lt;strong&gt;함수 호출 한 번당, 그 호출만을 위한 독립된 변수 저장소가 만들어진다.&lt;/strong&gt; 이게 [[Lexical Environment]] 가 “호출마다 새로 생성”되는 이유다.&lt;/p&gt;
&lt;h3 id=&quot;종합-lexical-environment-가-답하는-것&quot;&gt;종합, Lexical Environment 가 답하는 것&lt;/h3&gt;
&lt;p&gt;지금까지 본 것을 한 줄로 묶으면:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lexical Environment 는 “이 호출의 변수들이 어디 살고, 바깥 스코프가 어디인지” 를 기록하는 객체다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이게 우리가 곧 만나게 될 LE의 두 필드, 변수 저장소(Environment Record) + 외부 Environment 링크, 의 의미다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;왜-클로저가-필요한가&quot;&gt;왜 클로저가 필요한가&lt;/h2&gt;
&lt;p&gt;기초 한 가지가 더 남았다. 위 이야기까지는 “함수가 실행되는 동안 변수가 살아있어야 한다”였다. 그런데 우리의 시작 질문은 그 반대다, &lt;em&gt;함수가 끝났는데 변수가 살아있다.&lt;/em&gt; 이건 왜 필요한가?&lt;/p&gt;
&lt;p&gt;다음 패턴들을 보자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// (1) 이터레이터/제너레이터&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// (2) 카운터/누적기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; makeCounter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;count;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// (3) 사적 상태(private state)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; account&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    deposit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;amt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (balance &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; amt),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    balance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; balance,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// (4) 콜백이 외부 컨텍스트를 기억&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; attachLogger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`[${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}]`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, event);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; log&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; attachLogger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;button.onclick &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; log; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// log는 호출 시 label=&apos;user&apos; 를 기억&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 네 패턴 모두 &lt;strong&gt;“바깥 함수가 끝난 뒤에도 안쪽 함수가 바깥 변수를 기억”&lt;/strong&gt; 해야 동작한다. 이게 안 되면 JavaScript에서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이터레이터 같은 상태 보존 패턴이 불가능&lt;/li&gt;
&lt;li&gt;사적 상태(&lt;code&gt;#private&lt;/code&gt; 필드의 정신적 조상)가 불가능&lt;/li&gt;
&lt;li&gt;콜백 함수가 자기 컨텍스트를 들고 다닐 수 없음&lt;/li&gt;
&lt;li&gt;고차 함수(Higher-order Function) 표현력의 큰 손실&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 능력, 함수가 자기 정의 시점의 외부 변수들을 계속 참조할 수 있는 성질, 이 바로 [[클로저]] 다. 즉 클로저는 “있어서 좋은 기능”이 아니라 &lt;strong&gt;JavaScript의 표현력의 절반을 떠받치는 토대&lt;/strong&gt;다.&lt;/p&gt;
&lt;p&gt;이제 진짜 메커니즘으로 들어가자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;결과적-정의-클로저&quot;&gt;결과적 정의, 클로저&lt;/h2&gt;
&lt;p&gt;흔히 쓰는 한 줄 정의:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[[클로저|클로저(Closure)]]: 함수가 자신이 정의된 시점의 외부 변수들을 계속 참조할 수 있는 성질.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;naturals&lt;/code&gt; 의 &lt;code&gt;next()&lt;/code&gt; 함수는 자신을 감싸고 있던 &lt;code&gt;naturals&lt;/code&gt; 의 변수 &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt; 를 “닫아서 들고 다닌다(close over)”. 그래서 &lt;code&gt;naturals&lt;/code&gt; 가 반환된 후에도 &lt;code&gt;n&lt;/code&gt; 이 유효하다.&lt;/p&gt;
&lt;p&gt;여기까지가 흔한 설명이다. 이제 세 질문을 차례로 풀어가자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;q1-n은-어디-사는가-lexical-environment-의-구조&quot;&gt;Q1. &lt;code&gt;n&lt;/code&gt;은 어디 사는가?, Lexical Environment 의 구조&lt;/h2&gt;
&lt;p&gt;앞 절에서 우리는 Lexical Environment 가 “변수 저장소 + 외부 스코프 링크” 두 가지를 담는다고 말했다. 이걸 명세 수준에서 더 정확하게 보자.&lt;/p&gt;
&lt;h3 id=&quot;le-의-두-필드&quot;&gt;LE 의 두 필드&lt;/h3&gt;
&lt;p&gt;ECMAScript는 함수가 호출될 때마다 Lexical Environment 객체를 하나 만든다. 그 LE는 단 두 개의 필드를 가진다.&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;필드&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;역할&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Environment Record&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;이 스코프에서 선언된 식별자(&lt;code&gt;n&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt;, 함수 이름 등)의 실제 binding 저장소&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;외부 Environment 참조&lt;/strong&gt; (Outer)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;자기를 감싸는 바깥 LE 에 대한 참조. 없으면 &lt;code&gt;null&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이 단순한 구조가 스코프 체인 자체다. LE들이 outer 포인터로 연결되어 사슬을 이룬다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;environment-record-의-5-가지-종류&quot;&gt;Environment Record 의 5 가지 종류&lt;/h3&gt;
&lt;p&gt;스펙 (&lt;a href=&quot;https://tc39.es/ecma262/#sec-environment-records&quot;&gt;ECMA-262 §9.1&lt;/a&gt;)은 Environment Record 를 다섯 가지로 분류한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Declarative&lt;/strong&gt;: 일반 함수 스코프, 블록 스코프. &lt;code&gt;let&lt;/code&gt;/&lt;code&gt;const&lt;/code&gt;/함수 선언이 여기 산다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Function&lt;/strong&gt;: Declarative 의 특수화. &lt;code&gt;this&lt;/code&gt;, &lt;code&gt;super&lt;/code&gt;, &lt;code&gt;new.target&lt;/code&gt; 을 추가로 갖는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object&lt;/strong&gt;: &lt;code&gt;with&lt;/code&gt; 문 같은 곳에서 객체를 스코프 대상으로 쓸 때.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global&lt;/strong&gt;: 가장 바깥. 전역 객체(&lt;code&gt;globalThis&lt;/code&gt;)와 묶여 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Module&lt;/strong&gt;: 모듈 최상위. &lt;code&gt;import&lt;/code&gt; 바인딩이 여기 산다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;naturals(5)&lt;/code&gt; 를 호출하면 &lt;strong&gt;Function Environment Record&lt;/strong&gt; 가 만들어진다. 그 안에:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Environment Record:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  n        → 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  end      → 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  this     → undefined  (strict)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;외부 Environment 참조: → Module/Global Environment&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;함수-객체에-박힌-environment-슬롯&quot;&gt;함수 객체에 박힌 Environment 슬롯&lt;/h3&gt;
&lt;p&gt;여기가 클로저의 진짜 정체다.&lt;/p&gt;
&lt;p&gt;ECMAScript에서 &lt;strong&gt;함수 객체&lt;/strong&gt; 자체가 내부 슬롯으로 “Environment 슬롯” (스펙 정식 이름은 &lt;code&gt;[[Environment]]&lt;/code&gt; 슬롯이지만, 그냥 Environment 슬롯이라 부르자) 을 가진다. 이 슬롯은 &lt;strong&gt;그 함수가 정의된 시점&lt;/strong&gt;의 LE 를 가리킨다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {          &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ← 이 함수 객체가 만들어질 때&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      //   .Environment 슬롯 = naturals 의 LE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;next&lt;/code&gt; 함수 객체는 생성될 때 자기를 감싼 LE(&lt;code&gt;naturals&lt;/code&gt; 의 LE)를 자기 Environment 슬롯에 박제한다. 이후 &lt;code&gt;next&lt;/code&gt; 가 어디서 어떻게 호출되든, &lt;strong&gt;호출 시 만들어지는 next 자신의 새 LE 의 outer 참조는 항상 박제된 Environment 슬롯이 된다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이게 “&lt;strong&gt;Lexical&lt;/strong&gt; Environment” 라고 부르는 이유다. &lt;strong&gt;호출 위치가 아니라 정의 위치&lt;/strong&gt;가 외부 스코프를 결정한다.&lt;/p&gt;
&lt;h3 id=&quot;q1-의-답&quot;&gt;Q1 의 답&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;n&lt;/code&gt; 은 &lt;strong&gt;콜 스택의 Execution Context 가 아니라, 힙(heap) 에 만들어진 Function Environment Record 의 binding&lt;/strong&gt; 으로 산다. 처음부터 끝까지 힙에 있다. 함수 호출이 끝나는 순간이 아니라, 그 LE 를 더 이상 누구도 참조하지 않을 때까지 산다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;q2-함수가-끝났는데-왜-gc되지-않는가-참조-사슬&quot;&gt;Q2. 함수가 끝났는데 왜 GC되지 않는가?, 참조 사슬&lt;/h2&gt;
&lt;h3 id=&quot;가장-흔한-오해를-먼저-깬다&quot;&gt;가장 흔한 오해를 먼저 깬다&lt;/h3&gt;
&lt;p&gt;많은 글이 이렇게 말한다, “함수의 로컬 변수는 함수가 끝나면 사라진다, 단 클로저로 잡힌 것은 예외.” 정확하지 않다.&lt;/p&gt;
&lt;p&gt;정확한 그림은 이렇다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;함수가 종료되면 &lt;strong&gt;콜 스택에 있던 Execution Context 만 사라진다.&lt;/strong&gt; 변수가 실제로 사는 LE 는 처음부터 &lt;strong&gt;힙에 있는 객체&lt;/strong&gt; 다. 누군가 참조하고 있는 한 끝까지 살아남는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;참조-사슬-추적&quot;&gt;참조 사슬 추적&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;naturals(5)&lt;/code&gt; 가 반환된 시점에서 일어나는 일을 추적해 보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;naturals&lt;/code&gt; 의 Execution Context 가 콜 스택에서 pop 된다.&lt;/li&gt;
&lt;li&gt;그러나 그 Execution Context 가 사용하던 &lt;strong&gt;Lexical Environment 자체&lt;/strong&gt; 는 콜 스택에 없었다. 처음부터 힙에 있는 객체다.&lt;/li&gt;
&lt;li&gt;힙에는 다음 객체들이 남는다: &lt;strong&gt;naturals LE&lt;/strong&gt;, &lt;strong&gt;반환된 객체&lt;/strong&gt; &lt;code&gt;{ next: ... }&lt;/code&gt;, &lt;strong&gt;next 함수 객체&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;사용자 코드에는 &lt;code&gt;const it = naturals(5)&lt;/code&gt;, 변수 &lt;code&gt;it&lt;/code&gt; 이 GC Root 다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 GC 가 Root 부터 도달 가능한 객체를 마킹한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;it (Root)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;반환 객체 { next: &amp;#x3C;fn&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ↓ .next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;next 함수 객체&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ↓ Environment 슬롯&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;naturals 의 LE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ↓ Environment Record&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;n: 1, end: 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;체인 끝의 LE 까지 도달 가능 → &lt;strong&gt;회수 불가&lt;/strong&gt;. 따라서 &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt; 도 살아남는다.&lt;/p&gt;
&lt;h3 id=&quot;변수의-수명--클로저-함수의-수명&quot;&gt;변수의 수명 = 클로저 함수의 수명&lt;/h3&gt;
&lt;p&gt;이 사슬이 끊어지면 어떻게 될까?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  it.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 블록 종료 → it 사라짐 → 반환 객체 사라짐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//           → next 사라짐 → LE 사라짐 → n, end 회수됨&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때서야 비로소 GC가 LE 까지 회수한다. &lt;strong&gt;클로저로 잡혀 있던 변수의 수명은 클로저 함수의 수명과 일치한다&lt;/strong&gt; 는 게 핵심 규칙이다.&lt;/p&gt;
&lt;h3 id=&quot;q2-의-답&quot;&gt;Q2 의 답&lt;/h3&gt;
&lt;p&gt;GC는 마법이 아니다. &lt;strong&gt;&lt;code&gt;it → 반환 객체 → next → Environment 슬롯 → LE → n, end&lt;/code&gt;&lt;/strong&gt; 라는 도달 가능 경로가 살아있는 한, GC는 LE 를 회수하지 않는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;q3-외부에-let-n--100이-있어도-안전한가-스코프-체인-워킹&quot;&gt;Q3. 외부에 &lt;code&gt;let n = 100&lt;/code&gt;이 있어도 안전한가?, 스코프 체인 워킹&lt;/h2&gt;
&lt;h3 id=&quot;그림&quot;&gt;그림&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 전역&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Infinity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 로컬 (전역 n과 완전히 별개)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { value: n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, done: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();      &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 1, done: false }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 100, 영향 없음&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;직관과 일치한다. 그런데 &lt;strong&gt;왜 그렇게 동작하는지&lt;/strong&gt; 를 메커니즘으로 보면 클로저 이해가 단단해진다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;한-단계씩-따라가기&quot;&gt;한 단계씩 따라가기&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;it.next()&lt;/code&gt; 안에서 &lt;code&gt;n&lt;/code&gt; 을 만나는 순간 엔진은 다음을 수행한다 (스펙 함수: &lt;a href=&quot;https://tc39.es/ecma262/#sec-resolvebinding&quot;&gt;&lt;code&gt;ResolveBinding&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;next 자신의 LE 의 Environment Record&lt;/strong&gt; 에서 &lt;code&gt;n&lt;/code&gt; 찾기 → 없음.&lt;/li&gt;
&lt;li&gt;next 자신의 LE 의 &lt;strong&gt;외부 Environment 참조&lt;/strong&gt; 로 이동 → 이게 &lt;code&gt;next&lt;/code&gt; 의 Environment 슬롯이 가리키던 &lt;code&gt;naturals&lt;/code&gt; 의 LE.&lt;/li&gt;
&lt;li&gt;그 LE 의 Environment Record 에서 &lt;code&gt;n&lt;/code&gt; 찾기 → &lt;strong&gt;있음! 값 1.&lt;/strong&gt; 여기서 멈춤.&lt;/li&gt;
&lt;li&gt;발견한 binding 으로 읽기/쓰기 수행.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;전역의 &lt;code&gt;n = 100&lt;/code&gt; 은 4단계까지 가지도 못한다. &lt;strong&gt;가장 가까운 binding 이 이긴다 (lexical shadowing).&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;정의-위치-기준이라는-게-강력한-보장이다&quot;&gt;정의 위치 기준이라는 게 강력한 보장이다&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;code&gt;next&lt;/code&gt; 의 Environment 슬롯은 &lt;strong&gt;정의 위치&lt;/strong&gt; 에 박제된다. &lt;code&gt;next()&lt;/code&gt; 를 다른 모듈로 옮겨 거기서 호출해도, 호출 위치의 변수들이 끼어들 수 없다. &lt;strong&gt;외부 격리가 어휘적으로 보장된다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;q3-의-답&quot;&gt;Q3 의 답&lt;/h3&gt;
&lt;p&gt;안전하다. 변수 조회는 항상 &lt;strong&gt;정의 시점에 박제된 Environment 슬롯부터 시작하여 외부 참조를 따라 바깥으로 진행&lt;/strong&gt; 한다. 전역의 &lt;code&gt;n=100&lt;/code&gt; 은 가장 가까운 &lt;code&gt;n=1&lt;/code&gt; 이 가려버리기 때문에 닿을 수 없다.&lt;/p&gt;
&lt;p&gt;만약 로컬 &lt;code&gt;let n = 1&lt;/code&gt; 이 없었다면 한 단계 더 올라가서 Global Environment 의 &lt;code&gt;n=100&lt;/code&gt; 을 썼을 것이다. 이 케이스가 가장 흔한 클로저 버그다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;한-그림으로-세-질문-세-메커니즘&quot;&gt;한 그림으로, 세 질문, 세 메커니즘&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;질문&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;답&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;메커니즘&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Q1&lt;/strong&gt;. &lt;code&gt;n&lt;/code&gt; 은 어디 사는가?&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;힙&lt;/strong&gt; 에 있는 Function Environment Record 의 binding&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;LE = Environment Record + 외부 Environment 참조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Q2&lt;/strong&gt;. 왜 GC 되지 않는가?&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;it → 객체 → next → Environment 슬롯 → LE&lt;/code&gt; 사슬이 살아있어서&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;함수 객체의 Environment 슬롯&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Q3&lt;/strong&gt;. 외부 &lt;code&gt;n=100&lt;/code&gt; 이 있어도 왜 안전한가?&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;정의 시점에 박제된 Environment 슬롯부터 외부 참조를 따라가니까&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ResolveBinding 의 스코프 체인 워킹&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;JavaScript 의 클로저는 마법이 아니다. &lt;strong&gt;함수 객체에 박힌 Environment 슬롯, 힙에 떠 있는 Lexical Environment, 외부 Environment 참조로 이어지는 체인&lt;/strong&gt;, 이 세 가지의 조합이다.&lt;/p&gt;
&lt;p&gt;이 그림을 한 번 머릿속에 새겨두면 &lt;code&gt;this&lt;/code&gt;, 화살표 함수, 모듈, 이터레이터, async/await 의 동작이 모두 같은 언어로 설명된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;더-들어가기-흔한-패턴과-함정&quot;&gt;더 들어가기, 흔한 패턴과 함정&lt;/h2&gt;
&lt;h3 id=&quot;각-호출은-독립된-클로저를-만든다&quot;&gt;각 호출은 독립된 클로저를 만든다&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; naturals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 1, done: false }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 2, done: false }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;b.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// { value: 1, done: false }, b의 n은 별개!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;왜? &lt;code&gt;naturals(3)&lt;/code&gt; 을 호출할 때마다 &lt;strong&gt;새로운 Function Environment Record&lt;/strong&gt; 가 힙에 만들어진다. 같은 함수 코드가 실행되더라도, 호출 한 번당 LE 하나. 두 LE 는 메모리상 완전히 별개의 객체이며 서로 간섭하지 않는다.&lt;/p&gt;
&lt;p&gt;함수형 모듈 패턴의 기반이기도 하다. 같은 팩토리를 여러 번 호출하면 매번 독립된 “private state” 가 만들어진다.&lt;/p&gt;
&lt;h3 id=&quot;for-루프와-var-의-악연&quot;&gt;&lt;code&gt;for&lt;/code&gt; 루프와 &lt;code&gt;var&lt;/code&gt; 의 악연&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; fns&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  fns.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fns[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fns[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fns[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;var i&lt;/code&gt; 는 함수 스코프이므로 LE 가 &lt;strong&gt;루프 전체에 단 하나&lt;/strong&gt; 만 존재한다. 세 콜백 모두 같은 LE 를 Environment 슬롯으로 가진다. 루프가 끝났을 때 그 LE 의 &lt;code&gt;i&lt;/code&gt; 는 3.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;let&lt;/code&gt; 은 이를 해결한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; fns&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  fns.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fns[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fns[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fns[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스펙 (&lt;a href=&quot;https://tc39.es/ecma262/#sec-for-statement-runtime-semantics-labelledevaluation&quot;&gt;ECMA-262 §14.7.4.3&lt;/a&gt;) 상 &lt;code&gt;for&lt;/code&gt; 문은 &lt;code&gt;let&lt;/code&gt; 변수의 경우 &lt;strong&gt;매 iteration 마다 별도 Lexical Environment&lt;/strong&gt; 를 만들어 직전 값을 복사한 뒤 진입한다. 그래서 세 콜백이 가리키는 LE 가 셋 다 다르고, 각자 0, 1, 2 를 가진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;let&lt;/code&gt; 이 단순히 “블록 스코프” 라는 것 이상의 의미를 갖는 지점.&lt;/p&gt;
&lt;h3 id=&quot;클로저-메모리-누수&quot;&gt;클로저 메모리 누수&lt;/h3&gt;
&lt;p&gt;DOM 이벤트 핸들러나 setTimeout 콜백에 클로저를 넣을 때 흔히 발생한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; attachHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; hugeData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1_000_000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;x&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  document.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;btn&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;clicked&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// hugeData를 직접 쓰지 않음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hugeData&lt;/code&gt; 를 콜백 본문에서 쓰지 않으므로 GC 된다고 생각하기 쉽다. 실제 엔진 동작은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;V8 등 대부분의 모던 엔진: &lt;strong&gt;escape analysis&lt;/strong&gt; 로 클로저가 실제로 참조하는 변수만 보관 → &lt;code&gt;hugeData&lt;/code&gt; 는 회수됨.&lt;/li&gt;
&lt;li&gt;그러나 같은 LE 안에서 &lt;code&gt;eval()&lt;/code&gt; 이나 &lt;code&gt;new Function()&lt;/code&gt; 이 가능할 가능성이 있으면 엔진은 안전을 위해 &lt;strong&gt;LE 전체를 통째로 보존&lt;/strong&gt; 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;큰 객체는 명시적으로 끊자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; attachHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hugeData &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1_000_000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;x&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(hugeData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  hugeData &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 명시적 해제&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  document.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;btn&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;clicked&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;모듈-패턴-js-의-진짜-private&quot;&gt;모듈 패턴, JS 의 진짜 private&lt;/h3&gt;
&lt;p&gt;ES Modules 이전에 “사적 변수” 를 만드는 정통 패턴이었고, 지금도 유효하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; counter&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ← 외부에서 절대 접근 불가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    inc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;count,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    reset&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;counter.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;inc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;counter.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;inc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;counter.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;counter.count;  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// undefined, 직접 접근 불가&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;count&lt;/code&gt; 는 IIFE 호출 시 만들어진 LE 의 Environment Record 에만 존재한다. 반환된 객체의 메서드들이 그 LE 를 Environment 슬롯으로 가지므로 동작은 하지만, 외부에는 노출되지 않는다. &lt;strong&gt;타입스크립트의 &lt;code&gt;#private&lt;/code&gt; 필드가 등장하기 전부터 JavaScript 는 이미 진짜 사적 상태를 가지고 있었다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;p&gt;기초 개념부터 쌓아 다시 보면, 세 질문은 결국 클로저의 세 면이었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;n&lt;/code&gt; 이 어디 사는가&lt;/strong&gt; 는 &lt;em&gt;Lexical Environment 의 구조&lt;/em&gt; 문제다. 호출마다 새로 만들어지는 변수 저장소 + 외부 링크.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;왜 GC 되지 않는가&lt;/strong&gt; 는 &lt;em&gt;함수 객체의 Environment 슬롯&lt;/em&gt; 과 &lt;em&gt;힙 참조 사슬&lt;/em&gt; 의 문제다. 콜 스택 pop ≠ 객체 회수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;외부 변수와 어떻게 격리되는가&lt;/strong&gt; 는 &lt;em&gt;ResolveBinding 의 스코프 체인 워킹&lt;/em&gt; 이 &lt;em&gt;정의 위치&lt;/em&gt; 에 박제된 외부 Environment 참조를 따라가는 메커니즘이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;세 메커니즘을 한 그림으로 머릿속에 두면, “this 가 왜 이렇게 동작하는지”, “화살표 함수와 일반 함수의 차이”, “async 함수 안에서 await 후에 변수가 어떻게 보존되는지” 같은 후속 질문에도 같은 언어로 답할 수 있게 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ 이 글은 ECMAScript 명세(ECMA-262 §9.1, §10.2, §14.7) 및 MDN 자료를 참조하여 &lt;strong&gt;AI(Claude)와 함께 작성&lt;/strong&gt; 되었습니다. 핵심 메커니즘 설명은 명세 기준이며, V8 의 escape analysis 동작은 엔진 구현 디테일이므로 버전에 따라 다를 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ECMA-262 §9.1 &lt;a href=&quot;https://tc39.es/ecma262/#sec-environment-records&quot;&gt;Environment Records&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ECMA-262 §10.2 &lt;a href=&quot;https://tc39.es/ecma262/#sec-ecmascript-function-objects&quot;&gt;ECMAScript Function Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ECMA-262 §9.4 &lt;a href=&quot;https://tc39.es/ecma262/#sec-execution-contexts&quot;&gt;Execution Contexts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ECMA-262 §14.7.4.3 &lt;a href=&quot;https://tc39.es/ecma262/#sec-for-statement-runtime-semantics-labelledevaluation&quot;&gt;for statement, per-iteration bindings for &lt;code&gt;let&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MDN, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures&quot;&gt;Closures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MDN, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Scope&quot;&gt;Scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;V8 Blog, &lt;a href=&quot;https://v8.dev/blog&quot;&gt;JavaScript engine internals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>javascript</category><category>typescript</category><category>closure</category><category>lexical-environment</category><category>ecmascript</category><category>internals</category><author>koa (김신건)</author></item><item><title>정렬·해시는 메모리가 부족하면 어디로 새는가, PGA, work_mem, Workspace Memory</title><link>https://shinkeonkim.com/posts/2026-06-13-sort-hash-memory-spill/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-13-sort-hash-memory-spill/</guid><description>&quot;SQL 레벨업&quot;이 한 문장으로 압축한 정렬·해시의 메모리 spill을, 알고리즘부터 Oracle·PostgreSQL·SQL Server의 실제 동작까지 단계별로 풀어본다.</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;한-문장에서-시작된-의문&quot;&gt;한 문장에서 시작된 의문&lt;/h2&gt;
&lt;p&gt;“SQL 레벨업”을 읽다가 다음 문단이 눈에 들어왔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oracle에서는 정렬 또는 해시를 위해 PGA라는 메모리 영역을 사용합니다. 이때 PGA 크기가 집약 대상 데이터양에 비해 부족하면, 일시 영역(저장소)을 사용해 부족한 만큼 채웁니다.&lt;/p&gt;
&lt;p&gt;저자주: PostgreSQL에서는 work_mem, Microsoft SQL Server에서는 Workspace Memory라는 메모리 영역이 정렬 또는 해시에 사용됩니다. 모두 메모리 영역이 부족해지면 부족한 만큼 보충하고자 일시 영역(물리적으로는 저장소의 파일)을 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;세 개의 DBMS, 세 개의 다른 이름. 책은 “부족한 만큼 채운다”는 결과만 말하고 그 사이의 메커니즘은 다루지 않는다.&lt;/p&gt;
&lt;p&gt;이 글에서는 다음을 차례로 풀어본다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;정렬·해시는 &lt;strong&gt;왜&lt;/strong&gt; 그렇게 메모리에 욕심을 부리나? → 알고리즘&lt;/li&gt;
&lt;li&gt;메모리가 부족할 때 정확히 &lt;strong&gt;무엇이&lt;/strong&gt; 어떻게 디스크로 흘러가나? → 3-tier 모델&lt;/li&gt;
&lt;li&gt;Oracle·PostgreSQL·SQL Server는 이 문제를 &lt;strong&gt;각각 어떻게&lt;/strong&gt; 다르게 다루나? → 구현 비교&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;한 층씩 내려가자.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정렬해시spill의-알고리즘적-본질&quot;&gt;정렬·해시·Spill의 알고리즘적 본질&lt;/h2&gt;
&lt;p&gt;대부분의 SQL 연산은 &lt;strong&gt;스트리밍&lt;/strong&gt;으로 동작한다. &lt;code&gt;WHERE&lt;/code&gt; 필터, 단순 Index Scan, Nested Loop Join은 한 번에 한 행씩 흘려보내며 처리한다. 입력이 1억 건이든 한 행씩만 메모리에 두면 끝난다.&lt;/p&gt;
&lt;p&gt;정렬과 해시는 다르다. &lt;strong&gt;블로킹(blocking) 연산자&lt;/strong&gt;라고 부른다. 입력 한 쪽(또는 양쪽)을 &lt;strong&gt;전부 모은 뒤에야&lt;/strong&gt; 첫 출력을 낼 수 있다.&lt;/p&gt;
&lt;p&gt;왜 그런지 직관적으로 보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ORDER BY value&lt;/code&gt;, 마지막 행이 들어오기 전까진 어떤 행이 첫 번째인지 알 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GROUP BY user_id&lt;/code&gt;, 같은 &lt;code&gt;user_id&lt;/code&gt;가 흩어져 있을 수 있어 다 모아야 그룹이 완성된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Hash Join&lt;/code&gt;의 빌드 쪽, 작은 테이블 전체를 해시 테이블로 만들어둬야 큰 테이블 행이 들어올 때 즉시 매칭 검색이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 “전체를 모아야 한다”는 요구가 메모리 압박의 근원이다. 그리고 메모리가 부족할 때 두 알고리즘이 어떻게 변신하는지가 이 글의 핵심 주제다.&lt;/p&gt;
&lt;h3 id=&quot;정렬-quicksort--external-merge-sort&quot;&gt;정렬: Quicksort → External Merge Sort&lt;/h3&gt;
&lt;p&gt;메모리에 다 들어가는 작은 데이터라면 RDBMS는 [[Quick Sort|Quicksort]] (또는 &lt;code&gt;LIMIT N&lt;/code&gt; 최적화 시 [[Heap Sort|Heapsort]]) 를 쓴다. O(n log n) 시간, 디스크 I/O 0. (정렬 알고리즘 전반의 비교는 [[정렬 알고리즘]] 참고.)&lt;/p&gt;
&lt;p&gt;문제는 메모리에 안 들어가는 큰 데이터다. 이때 [[External Merge Sort]] 가 등장한다. 직역하면 “외부(=디스크) [[Merge Sort|머지 정렬]]” 이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;알고리즘은 두 단계다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1단계, Run 생성(Run Generation)&lt;/strong&gt;: 데이터를 메모리 크기만큼 청크로 잘라 각 청크를 메모리에서 Quicksort한 뒤 디스크에 “Run”이라는 이름의 정렬된 파일로 쓴다. 메모리는 비우고 다음 청크를 받는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2단계, K-way Merge&lt;/strong&gt;: 모든 Run이 만들어지면, 각 Run의 첫 행만 메모리에 둔 채(이게 핵심이다) 가장 작은 값을 골라 출력한다. 출력한 Run의 다음 행을 가져와서 다시 비교. 입력 Run이 K개라면 K개의 작은 “head 버퍼”만 메모리에 두면 된다.&lt;/p&gt;
&lt;p&gt;이 두 단계가 외부 정렬의 정체다. 메모리는 청크 정렬용으로 한 번, 머지 head 버퍼용으로 또 한 번 재사용된다.&lt;/p&gt;
&lt;h3 id=&quot;해시-in-memory-hash--grace-hash--hybrid-hash&quot;&gt;해시: In-memory Hash → Grace Hash → Hybrid Hash&lt;/h3&gt;
&lt;p&gt;Hash Join도 비슷하다. 메모리에 들어가는 작은 빌드 테이블이면 그냥 메모리 해시 테이블 하나 만들고 끝. 큰 빌드 테이블이면 어떻게 할까?&lt;/p&gt;
&lt;p&gt;답이 &lt;strong&gt;Grace Hash Join&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심 아이디어: &lt;strong&gt;두 테이블을 같은 해시 함수로 N개 파티션으로 나눠 디스크에 쓴다.&lt;/strong&gt; 같은 키는 같은 번호의 파티션에 들어간다. 그러면 R의 i번째 파티션은 S의 i번째 파티션과만 조인해야 한다. &lt;strong&gt;R의 i번째 파티션이 메모리에 들어가는 크기가 되도록 N을 정한다.&lt;/strong&gt; 그 뒤 i=1,2,…,N에 대해 (Ri, Si) 쌍을 메모리에서 in-memory Hash Join한다.&lt;/p&gt;
&lt;p&gt;큰 빌드 전체를 메모리에 못 올려도, N등분된 조각은 메모리에 올라간다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hybrid Hash Join&lt;/strong&gt;은 한 가지 최적화다. 첫 번째 파티션(R1)을 디스크에 쓰지 않고 메모리에 그대로 보관한다. S 행이 들어올 때 첫 번째 파티션이면 메모리의 R1과 즉시 조인, 나머지 파티션이면 디스크로 spill. 빌드가 메모리에 살짝만 초과하는 경우 디스크 I/O를 절반 가까이 절약한다. PostgreSQL과 SQL Server가 모두 이 방식을 쓴다.&lt;/p&gt;
&lt;h3 id=&quot;두-알고리즘의-공통-메시지&quot;&gt;두 알고리즘의 공통 메시지&lt;/h3&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;연산&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;충분할 때&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;약간 부족할 때&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;심하게 부족할 때&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sort&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quicksort (I/O 0)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;External Merge Sort, 1 머지 패스&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;External Merge Sort, 다중 머지 패스&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Hash&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;단일 메모리 해시&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hybrid Hash (일부만 spill)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hash Recursion (파티션을 또 분할)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;핵심: &lt;strong&gt;메모리가 부족하면 단순히 느려지는 게 아니라 알고리즘이 통째로 바뀐다.&lt;/strong&gt; 이 표가 다음 절의 3-tier 비용 모델의 본질이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-tier-모델-optimal--one-pass--multi-pass&quot;&gt;3-tier 모델: Optimal · One-pass · Multi-pass&lt;/h2&gt;
&lt;p&gt;알고리즘 본 절 뒤끝에 본 세 단계, “충분/약간 부족/심하게 부족”, 에 이름을 붙여보자. Oracle은 명세적으로 이 세 모드를 정의했고, 우리는 그 용어를 보편 모델로 빌려 쓸 수 있다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;optimal-그냥-메모리에-들어간다&quot;&gt;Optimal, “그냥 메모리에 들어간다”&lt;/h3&gt;
&lt;p&gt;가장 빠른 모드. 디스크 I/O가 &lt;strong&gt;0&lt;/strong&gt;이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정렬: Quicksort, O(n log n) CPU만 사용&lt;/li&gt;
&lt;li&gt;해시: 단일 해시 테이블, Probe는 평균 O(1)&lt;/li&gt;
&lt;li&gt;요건: Work Area ≥ 입력 데이터 + 보조 구조(해시 버킷 헤더, 정렬 비교 버퍼 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OLTP의 거의 모든 쿼리가 여기 해당한다.&lt;/p&gt;
&lt;h3 id=&quot;one-pass-한-번만-디스크-왕복&quot;&gt;One-pass, “한 번만 디스크 왕복”&lt;/h3&gt;
&lt;p&gt;이 모드의 비용을 정확히 따져보면 왜 “꽤 저렴하다”는 평이 가능한지 보인다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정렬: 청크를 메모리에서 정렬해 Run으로 디스크에 쓴다(데이터 1× write). 마지막에 모든 Run을 한 번의 머지 패스로 읽어 출력한다(데이터 1× read). &lt;strong&gt;추가 I/O = 2 × 데이터 크기&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;해시: Hybrid Hash로 빌드와 프로브를 한 번씩 디스크 왕복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이게 One-pass라는 이름의 의미다, “한 번의 머지 패스로 끝남”.&lt;/p&gt;
&lt;h3 id=&quot;그런데-왜-1-gb-정렬이-22-mb-메모리로-충분한가&quot;&gt;그런데 왜 1 GB 정렬이 22 MB 메모리로 충분한가&lt;/h3&gt;
&lt;p&gt;Oracle 공식 문서가 든 예시가 처음 보면 비현실적으로 들린다. &lt;strong&gt;1 GB 정렬에 22 MB 메모리만 있어도 One-pass로 처리 가능하다&lt;/strong&gt;고 말한다. 1/45 크기로 어떻게? 외부 정렬의 머지 단계가 무엇을 필요로 하는지 한 번 더 들여다보면 답이 나온다.&lt;/p&gt;
&lt;p&gt;K-way Merge 단계의 메모리 요구를 식으로 적어보자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;입력 데이터 크기 = D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;가용 메모리 = M&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1단계에서 만들어지는 Run 개수 = K = D / M (각 Run 크기는 M)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2단계 머지 시 필요한 메모리:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  각 Run 당 한 페이지 분량의 &quot;head 버퍼&quot; + 출력 버퍼 1개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  머지 메모리 = (K + 1) × 페이지 크기&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              ≈ (D / M) × 8 KB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One-pass 가능 조건은 “이 머지 메모리가 가용 메모리 M에 들어맞아야 한다”이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(D / M) × 8 KB  ≤  M&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ M² ≥ D × 8 KB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;→ M  ≥ √(D × 8 KB)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;D = 1 GB로 넣으면 M ≥ √(1 GB × 8 KB) = √(8 × 10¹²) ≈ &lt;strong&gt;약 2.8 MB&lt;/strong&gt;. 수학적 하한은 3 MB도 안 된다.&lt;/p&gt;
&lt;p&gt;Oracle이 보수적으로 22 MB라고 말하는 건 보조 구조와 안전 마진을 합친 실용값일 뿐이다. 같은 식에 D = 10 GB를 넣으면 √(10 GB × 8 KB) ≈ 9 MB → Oracle은 40 MB를 권장한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;strong&gt;K-way Merge의 메모리는 데이터 크기에 비례하지 않는다.&lt;/strong&gt; 데이터 크기 D, 페이지 크기 P일 때 √(D × P)에 비례한다. 그래서 한 자릿수 GB의 정렬도 수십 MB로 충분히 처리된다. 외부 머지 정렬의 가장 우아한 성질.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;multi-pass-여러-번-디스크-왕복&quot;&gt;Multi-pass, “여러 번 디스크 왕복”&lt;/h3&gt;
&lt;p&gt;메모리가 너무 작아 머지 단계조차 한 번에 못 끝낼 때다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정렬: Run의 일부만 머지해 더 큰 중간 Run을 만들고 다시 디스크에 → 그걸 또 머지 → … 패스 수 K번.
&lt;ul&gt;
&lt;li&gt;추가 I/O = &lt;strong&gt;K × 2 × 데이터 크기&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;해시: Hash Recursion. 파티션 하나가 여전히 메모리에 안 들어가면 그 파티션을 더 잘게 나눠 다시 spill.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;10 GB를 5 MB 메모리로 정렬한다고 가정해보자. Run은 √(D × P) 식에서 벗어나 머지 패스가 둘 이상 필요해진다. 패스 수가 K일 때 비용은 K × 2 × 데이터 크기, 즉 K가 2만 돼도 디스크 I/O가 4배다. K=3이면 6배. &lt;strong&gt;응답 시간이 메모리 부족량에 대해 거의 지수적으로 증가&lt;/strong&gt;한다.&lt;/p&gt;
&lt;h3 id=&quot;그래서-왜-optimal--90-one-pass--10-multi-pass--0인가&quot;&gt;그래서 왜 “Optimal &gt; 90%, One-pass &amp;#x3C; 10%, Multi-pass = 0%“인가&lt;/h3&gt;
&lt;p&gt;비용 비대칭을 보면 답이 나온다.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;모드&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;추가 디스크 I/O&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;운영 정책&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Optimal&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;0&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;표준 케이스로 만들어야 함&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;One-pass&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;2 × 데이터&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;가끔(대용량 보고서 등) 허용 가능&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Multi-pass&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;K × 2 × 데이터 (K ≥ 2)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;거의 모든 경우 SLO 파괴, 절대 피해야 함&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;One-pass는 평소 0이던 디스크 I/O가 2배로 갑자기 늘어나니까 한 자릿수 초로 끝날 쿼리가 두 자릿수 초로 늘어난다. 가끔이라면 수용 가능하다. Multi-pass는 K가 늘어날 때마다 2배씩 곱해진다. 응답 시간 SLO를 즉시 깨뜨린다.&lt;/p&gt;
&lt;p&gt;운영의 안전 목표 “Optimal &gt; 90%, One-pass &amp;#x3C; 10%, Multi-pass = 0%“는 이 비용 함수에서 자연스럽게 도출된 임계값이다. &lt;strong&gt;가끔 비싼 건 받아들이고, 통제 불능으로 비싼 건 금지한다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;세-dbms는-같은-문제를-다르게-푼다&quot;&gt;세 DBMS는 같은 문제를 다르게 푼다&lt;/h2&gt;
&lt;p&gt;여기까지가 보편 이론이다. 이제 각 DBMS가 이 보편 모델을 어떻게 구체화했는지 본다. 세 가지 결정 축이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;메모리 할당 단위&lt;/strong&gt;: 연산자당? 노드당? 쿼리당?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예약 시점&lt;/strong&gt;: 실행 중 동적으로? 컴파일 시점에 정적으로?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모니터링 인터페이스&lt;/strong&gt;: 전용 뷰? 실행 계획 출력? 이벤트?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Oracle, PostgreSQL, SQL Server는 이 세 축에서 모두 다른 답을 선택했다. 그 차이가 실무에서 만나는 함정의 차이로 이어진다.&lt;/p&gt;
&lt;p&gt;먼저 가장 성숙한 자동 관리를 가진 Oracle부터.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;oracle-pga와-자동-관리&quot;&gt;Oracle, PGA와 자동 관리&lt;/h2&gt;
&lt;h3 id=&quot;왜-sga가-아니라-pga인가&quot;&gt;왜 SGA가 아니라 PGA인가&lt;/h3&gt;
&lt;p&gt;Oracle의 메모리는 두 영역으로 깔끔하게 나뉜다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SGA (System Global Area)&lt;/strong&gt;: 인스턴스 시작 시 잡히는 &lt;strong&gt;공유 메모리&lt;/strong&gt;. Buffer Cache, Shared Pool, Redo Log Buffer 등.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PGA (Program Global Area)&lt;/strong&gt;: 사용자가 접속할 때마다 spawn되는 &lt;strong&gt;서버 프로세스마다 사적인 메모리&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정렬·해시 같은 메모리 집약 연산이 SGA를 쓰지 않는 이유는 단순하다. SGA는 모든 세션이 공유하는 자원이라 한 세션이 거대한 정렬을 돌리면 나머지 모든 세션에 영향이 간다. PGA는 사적이라 한 세션이 욕심을 부려도 격리된다. 가격, 더 정확히는 메모리 할당량 관리, 을 분리하기에 자연스럽다.&lt;/p&gt;
&lt;h3 id=&quot;work-area의-4개-영역&quot;&gt;Work Area의 4개 영역&lt;/h3&gt;
&lt;p&gt;PGA 안의 Work Area는 메모리 집약 연산자별로 다음 하위 영역을 가진다.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;영역&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;사용 연산자&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sort Area&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;DISTINCT&lt;/code&gt;, sort-based &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;ROLLUP&lt;/code&gt;, 윈도우 함수&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Hash Area&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hash Join&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Bitmap Merge Area&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;여러 비트맵 인덱스 결합 (&lt;code&gt;BitmapAnd&lt;/code&gt;, &lt;code&gt;BitmapOr&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Bulk Load Buffer&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;INSERT /*+ APPEND */&lt;/code&gt; 같은 Direct Path Load&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;각 영역이 &lt;strong&gt;독립적으로&lt;/strong&gt; Optimal/One-pass/Multi-pass 상태를 가진다는 점이 중요하다. 같은 쿼리 안에서 Hash Join은 One-pass인데 Sort는 Optimal일 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;auto-pga-management-옛-sort_area_size의-졸업&quot;&gt;Auto PGA Management, 옛 SORT_AREA_SIZE의 졸업&lt;/h3&gt;
&lt;p&gt;Oracle 10g 이전엔 &lt;code&gt;SORT_AREA_SIZE&lt;/code&gt;, &lt;code&gt;HASH_AREA_SIZE&lt;/code&gt;를 DBA가 직접 잡았다. 너무 크게 잡으면 동시 세션이 많을 때 OS 메모리 고갈, 너무 작게 잡으면 모든 쿼리가 Multi-pass.&lt;/p&gt;
&lt;p&gt;10g 이후 &lt;code&gt;PGA_AGGREGATE_TARGET&lt;/code&gt; 파라미터 하나로 자동 관리된다. 옵티마이저가:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;활성 Work Area의 메모리 수요를 추적&lt;/li&gt;
&lt;li&gt;개별 Work Area 최대치(&lt;code&gt;global memory bound&lt;/code&gt;)를 실행 중 동적으로 조정&lt;/li&gt;
&lt;li&gt;가능한 한 많은 작업을 Optimal로 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DBA의 일은 이제 “PGA에 얼마를 줄 것인지” 하나로 축소됐다. 이게 Oracle이 가장 성숙한 자동 관리라고 평가받는 이유다.&lt;/p&gt;
&lt;h3 id=&quot;spill의-물리적-위치&quot;&gt;Spill의 물리적 위치&lt;/h3&gt;
&lt;p&gt;Optimal을 못 맞춰 spill이 발생하면 데이터는 &lt;strong&gt;TEMP 테이블스페이스의 sort segment&lt;/strong&gt;로 간다. Sort segment는 임시 표공간 안에 자동으로 만들어지고 세션이 끝나면 자동으로 회수된다. DBA가 별도로 정리할 필요가 없다.&lt;/p&gt;
&lt;h3 id=&quot;그래서-실제로-확인하려면-v-뷰&quot;&gt;그래서 실제로 확인하려면, V$ 뷰&lt;/h3&gt;
&lt;p&gt;자동 관리는 좋지만 “지금 잘 동작하는지”는 별도로 확인해야 한다. Oracle은 풍부한 동적 성능 뷰를 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;V$PGASTAT&lt;/strong&gt;, 인스턴스 전체의 PGA 상황:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;지표&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;aggregate PGA target parameter&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;PGA_AGGREGATE_TARGET&lt;/code&gt; 현재 값&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;global memory bound&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;개별 Work Area 최대 크기, &lt;strong&gt;&amp;#x3C; 1 MB이면 적신호&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;over allocation count&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;PGA가 target을 초과 할당한 횟수 (target 부족 신호)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;cache hit percentage&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Optimal 비율의 종합 지표&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;cache hit percentage&lt;/code&gt;의 계산식이 흥미롭다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;BP  = 처리한 전체 바이트&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;EBP = 추가 패스에서 다시 읽고 쓴 바이트 (One-pass + Multi-pass의 extra I/O)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cache hit % = BP × 100 / (BP + EBP)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;100%면 모든 Work Area가 Optimal. Multi-pass가 많아지면 EBP가 BP를 압도하면서 비율이 급락한다. &lt;strong&gt;앞 절의 비용 모델이 그대로 메트릭에 박혀있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;V$WORKAREA_ACTIVE&lt;/strong&gt;, 지금 실행 중인 Work Area들:&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;&lt;code&gt;PASS&lt;/code&gt; 컬럼이 3-tier 모델 그 자체다: &lt;code&gt;0&lt;/code&gt;이면 Optimal, &lt;code&gt;1&lt;/code&gt;이면 One-pass, &lt;code&gt;≥ 2&lt;/code&gt;면 Multi-pass. &lt;code&gt;TSIZE&lt;/code&gt;는 temp segment 사용량.&lt;/p&gt;
&lt;p&gt;운영 룰: &lt;strong&gt;PASS ≥ 2가 하나라도 보이면 PGA_AGGREGATE_TARGET을 다시 보자.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;postgresql-work_mem과-단위의-함정&quot;&gt;PostgreSQL, work_mem과 단위의 함정&lt;/h2&gt;
&lt;p&gt;Oracle은 자동 관리가 매력이었다. PostgreSQL은 정반대 철학이다, &lt;strong&gt;단순하고 예측 가능하지만, 단위가 작아서 위험하다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;쿼리당이-아니라-노드당이다&quot;&gt;”쿼리당”이 아니라 “노드당”이다&lt;/h3&gt;
&lt;p&gt;PostgreSQL 공식 문서의 한 문장:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sets the base maximum amount of memory to be used by &lt;strong&gt;a query operation&lt;/strong&gt; (such as a sort or hash table) &lt;strong&gt;before writing to temporary disk files&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“per query operation”, 쿼리 하나가 아니라 &lt;strong&gt;실행 계획의 연산 노드 하나&lt;/strong&gt;가 사용할 수 있는 양이다.&lt;/p&gt;
&lt;p&gt;예를 들어 다음 쿼리:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;COUNT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; users u&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; orders o &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; o&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;JOIN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; payments p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; o&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;order_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;region&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;KR&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GROUP BY&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DESC&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행 계획에는 보통 &lt;code&gt;Sort&lt;/code&gt; 1개 + &lt;code&gt;HashAggregate&lt;/code&gt; 1개 + &lt;code&gt;Hash Join&lt;/code&gt; 2개 = 노드 4개가 등장한다. &lt;strong&gt;각각이 따로 work_mem을 받아 쓸 수 있다.&lt;/strong&gt; 즉 한 쿼리가 최대 4 × work_mem까지 메모리를 차지한다. 거기에 parallel worker마다 같은 양이 곱해진다.&lt;/p&gt;
&lt;p&gt;기본값 work_mem = 4 MB. 별것 아닌 듯하지만 동시 세션 100개 × 노드 4개 = 1.6 GB. parallel worker 4개라면 &lt;strong&gt;6.4 GB&lt;/strong&gt;가 정렬·해시에만 잡힌다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
흔한 실수: “쿼리가 느리니 work_mem을 256 MB로 올리자.” 동시 세션 100 × 노드 4 × parallel 4 = &lt;strong&gt;400 GB&lt;/strong&gt;. OOM 직행.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oracle이 PGA 총량 하나만 잡으면 옵티마이저가 알아서 분배하는 방식이라면, PostgreSQL은 노드 하나 노드 하나에 정적인 상한을 거는 방식이다. 그래서 work_mem 값 자체보다 **“동시에 얼마나 많은 노드가 활성화되는가”**가 실제 메모리 압력을 결정한다.&lt;/p&gt;
&lt;h3 id=&quot;hash_mem_multiplier-해시만-따로-챙기기&quot;&gt;hash_mem_multiplier, 해시만 따로 챙기기&lt;/h3&gt;
&lt;p&gt;해시는 보통 정렬보다 메모리에 더 민감하다. 한 번 한도를 넘으면 Hash Recursion으로 비용이 급증하기 때문이다. PostgreSQL 13부터 별도 곱셈을 도입했다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sort 한도   = work_mem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hash 한도   = work_mem × hash_mem_multiplier  (default 2.0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해시 한도가 정렬 한도의 2배. 공식 문서는 spill이 빈번하면 multiplier를 2.0 ~ 8.0 범위로 올리라고 권장한다.&lt;/p&gt;
&lt;h3 id=&quot;explain-analyze에서-spill-신호-읽기&quot;&gt;EXPLAIN ANALYZE에서 spill 신호 읽기&lt;/h3&gt;
&lt;p&gt;Oracle은 별도 뷰가 있었지만, &lt;strong&gt;PostgreSQL은 모든 정보를 &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; 출력에 박아둔다.&lt;/strong&gt; 다른 곳을 볼 필요가 없는 대신, 출력을 정확히 읽을 줄 알아야 한다.&lt;/p&gt;
&lt;p&gt;Sort 노드:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sort  (cost=713.05..713.30 rows=100 width=488)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Sort Key: t1.fivethous&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Sort Method: quicksort  Memory: 74kB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Sort Method&lt;/code&gt;가 3-tier의 어느 모드인지 알려준다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;quicksort Memory: NN kB&lt;/code&gt; → &lt;strong&gt;Optimal&lt;/strong&gt; (메모리 내 정렬)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;top-N heapsort Memory: NN kB&lt;/code&gt; → &lt;code&gt;LIMIT N&lt;/code&gt; 최적화, 여전히 Optimal&lt;/li&gt;
&lt;li&gt;&lt;code&gt;external merge Disk: NN kB&lt;/code&gt; → &lt;strong&gt;Spill 발생!&lt;/strong&gt; 디스크에 NN kB 사용 (One-pass 또는 Multi-pass)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hash 노드:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hash  (cost=224.98..224.98 rows=100 width=244)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Buckets: 1024  Batches: 1  Memory Usage: 35kB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Batches&lt;/code&gt;가 Grace/Hybrid Hash Join의 파티션 개수다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Batches: 1&lt;/code&gt; → 빌드 전체가 메모리 (Optimal)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Batches: 2&lt;/code&gt; 이상 → Hybrid Hash로 spill (One-pass에 해당)&lt;/li&gt;
&lt;li&gt;실행 중 &lt;code&gt;Batches&lt;/code&gt;가 power-of-2로 점점 커지는 EXPLAIN → 빌드 추정이 틀려 runtime에 추가 분할 (Multi-pass의 일종)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
PG 공식 문서가 짚는 디테일: &lt;em&gt;“If the number of batches exceeds one, there will also be disk space usage involved, but that is not shown.”&lt;/em&gt;
즉 디스크 사용량은 EXPLAIN 기본 출력에 안 찍힌다. &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS)&lt;/code&gt;를 쓰면 &lt;code&gt;temp read=NN written=MM&lt;/code&gt; 카운터로 간접 확인 가능하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;시스템-차원의-spill-추적&quot;&gt;시스템 차원의 spill 추적&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 모든 임시 파일 생성을 로그에 기록&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; log_temp_files &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 데이터베이스별 누적 통계&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; datname, temp_files, pg_size_pretty(temp_bytes) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; temp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pg_stat_database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; temp_files &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;물리적 spill 파일은 &lt;code&gt;$PGDATA/base/pgsql_tmp/&lt;/code&gt;에 만들어진다. &lt;code&gt;temp_file_limit&lt;/code&gt;(기본 &lt;code&gt;-1&lt;/code&gt; = 무제한)으로 프로세스당 한도를 줄 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;pg-13의-결정적-변화&quot;&gt;PG 13의 결정적 변화&lt;/h3&gt;
&lt;p&gt;PostgreSQL 13 이전까지 &lt;code&gt;HashAggregate&lt;/code&gt;는 work_mem을 초과해도 메모리에서 끝까지 처리하려 했고, 종종 OOM을 유발했다. 13부터 &lt;strong&gt;disk-based hash aggregation&lt;/strong&gt;이 도입되어 정상적으로 spill한다. 13으로 올린 직후 “메모리 사용량이 줄어든 것 같은데 EXPLAIN에 temp가 보인다”고 놀라는 경우가 많은데, 이게 정상 동작이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;sql-server-정적-예약과-적응형-학습&quot;&gt;SQL Server, 정적 예약과 적응형 학습&lt;/h2&gt;
&lt;p&gt;Oracle도 PostgreSQL도 &lt;strong&gt;실행 중&lt;/strong&gt;에 메모리를 동적으로 할당한다. SQL Server는 이 부분에서 근본적으로 다른 길을 택했다, &lt;strong&gt;쿼리 시작 전에 필요량을 다 예약한다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;컴파일-타임에-모든-메모리를-예약한다&quot;&gt;컴파일 타임에 모든 메모리를 예약한다&lt;/h3&gt;
&lt;p&gt;쿼리가 컴파일될 때 옵티마이저가 다음을 추정한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정렬에 NN MB 필요&lt;/li&gt;
&lt;li&gt;해시 빌드에 MM MB 필요&lt;/li&gt;
&lt;li&gt;→ &lt;strong&gt;Memory Grant&lt;/strong&gt;(요청 메모리)로 합산&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;쿼리 실행이 시작되려면 먼저 &lt;strong&gt;Resource Semaphore&lt;/strong&gt;라는 게이트에서 grant를 받아야 한다. grant가 부족하면 &lt;code&gt;RESOURCE_SEMAPHORE&lt;/code&gt; 대기 상태로 대기열에 줄을 선다.&lt;/p&gt;
&lt;p&gt;이 메모리는 &lt;strong&gt;buffer pool 내 한 영역, Workspace Memory&lt;/strong&gt;에서 떼어진다. &lt;code&gt;max server memory&lt;/code&gt;로 제어되는 동일한 풀이다.&lt;/p&gt;
&lt;h3 id=&quot;row-mode-vs-batch-mode-spill의-결정적-분기&quot;&gt;Row mode vs Batch mode, Spill의 결정적 분기&lt;/h3&gt;
&lt;p&gt;이 정적 예약이 만든 한 가지 잔혹한 규칙이 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Row mode (전통적 실행)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Microsoft 공식 문서:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For row mode execution, the initial memory grant &lt;strong&gt;can’t be exceeded under any condition&lt;/strong&gt;. If more memory than the initial grant is needed to execute hash or sort operations, then the operations &lt;strong&gt;spill to disk&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;initial grant는 절대 초과할 수 없다.&lt;/strong&gt; 컴파일 타임 추정이 틀려서 실행 중 메모리가 모자라도 &lt;strong&gt;즉시 tempdb로 spill&lt;/strong&gt;한다. Oracle/PG처럼 “조금만 더”가 불가능하다. SQL Server에서 통계가 낡았을 때 spill이 폭주하는 이유다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Batch mode (컬럼스토어 + 2019부터 rowstore도 가능)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For batch mode execution, the initial memory grant &lt;strong&gt;can dynamically increase&lt;/strong&gt; up to a certain internal threshold by default.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Batch mode에서는 grant가 실행 중 동적으로 확장될 수 있다. Row/Batch mode의 차이가 SQL Server를 다른 두 DBMS와 결정적으로 갈라놓는다.&lt;/p&gt;
&lt;h3 id=&quot;spill의-두-종류&quot;&gt;Spill의 두 종류&lt;/h3&gt;
&lt;p&gt;Microsoft는 정렬과 해시의 spill 저장 형태를 따로 부른다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sort Spill, Worktable in tempdb&lt;/strong&gt; (&lt;code&gt;Sort Warning&lt;/code&gt; 이벤트)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash Spill, Workfile in tempdb&lt;/strong&gt; (&lt;code&gt;Hash Warning&lt;/code&gt; 이벤트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hash spill에는 두 하위 케이스가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hash Recursion&lt;/strong&gt;: 빌드가 메모리에 안 들어가서 파티션으로 쪼개고, 그 파티션이 또 안 들어가면 더 잘게 쪼개고… (앞 절의 Hash Recursion과 동일)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash Bailout&lt;/strong&gt;: 최대 재귀 깊이에 도달했을 때 다른 plan으로 폴백. 이때부터는 거의 sequential 스캔에 가까운 비용으로 떨어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;memory-grant-feedback-sql-server만의-적응형-학습-2017&quot;&gt;Memory Grant Feedback, SQL Server만의 적응형 학습 (2017+)&lt;/h3&gt;
&lt;p&gt;SQL Server는 정적 예약의 약점을 자체 학습 메커니즘으로 보완했다. 같은 쿼리가 반복 실행될 때 실제 사용량을 plan cache에 기록해서 다음 컴파일 시 grant를 조정한다.&lt;/p&gt;
&lt;p&gt;Row mode(2019+)에도 도입되어, “통계가 틀려서 spill하던 쿼리가 두세 번 실행 후 spill이 사라지는” 자동 학습이 가능해졌다. &lt;strong&gt;정적 예약 + 사후 피드백&lt;/strong&gt;이라는 하이브리드 전략이다.&lt;/p&gt;
&lt;h3 id=&quot;dmv로-grant-추적&quot;&gt;DMV로 grant 추적&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 진행 중인 grant들&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; session_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       requested_memory_kb,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       granted_memory_kb,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       used_memory_kb,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       ideal_memory_kb,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       wait_time_ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sys&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;dm_exec_query_memory_grants&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requested_memory_kb &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해석:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requested &gt;&gt; used&lt;/code&gt; → grant 과대 추정 (통계 부정확 또는 grant feedback이 학습 중)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;granted &amp;#x3C; requested&lt;/code&gt;로 대기 누적 → 동시성에 비해 grant가 너무 후함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RESOURCE_SEMAPHORE&lt;/code&gt; 대기 비율 상승 → 큰 쿼리들이 grant 가뭄으로 줄 서는 중&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Extended Events에서 &lt;code&gt;sort_warning&lt;/code&gt;, &lt;code&gt;hash_warning&lt;/code&gt;을 활성화하면 spill이 발생할 때마다 이벤트가 떨어진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;같은-그림-다른-이름-한-장-비교표&quot;&gt;같은 그림, 다른 이름, 한 장 비교표&lt;/h2&gt;
&lt;p&gt;세 DBMS가 같은 3-tier 비용 모델을 어떻게 다르게 구체화했는지 한 표로:&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;측면&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Oracle&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;PostgreSQL&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;SQL Server&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;메모리 영역 이름&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;PGA Work Area&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;work_mem&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Workspace Memory (Memory Grant)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;할당 단위&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;연산자당 (Work Area)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;노드당&lt;/strong&gt; (per plan node)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;쿼리당&lt;/strong&gt; (사전 합산)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;예약 시점&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;동적 (실행 중 조정)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;동적 (실행 중 조정)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;정적 (Row mode)&lt;/strong&gt; / 동적 (Batch mode)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;임시 영역 위치&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;TEMP의 sort segment&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;base/pgsql_tmp/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;tempdb의 &lt;strong&gt;Worktable&lt;/strong&gt;(Sort)·&lt;strong&gt;Workfile&lt;/strong&gt;(Hash)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;3-tier 신호&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;V$WORKAREA_ACTIVE.PASS&lt;/code&gt; (0/1/≥2)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;Sort Method: external merge&lt;/code&gt;, &lt;code&gt;Batches: N&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Spill 경고, XEvent (&lt;code&gt;sort_warning&lt;/code&gt;, &lt;code&gt;hash_warning&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;적응형 학습&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Auto PGA Management&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;,&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Memory Grant Feedback (2017+)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;주요 튜닝 파라미터&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;PGA_AGGREGATE_TARGET&lt;/code&gt;, &lt;code&gt;PGA_AGGREGATE_LIMIT&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;work_mem&lt;/code&gt;, &lt;code&gt;hash_mem_multiplier&lt;/code&gt;, &lt;code&gt;maintenance_work_mem&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;min memory per query&lt;/code&gt;, &lt;code&gt;max server memory&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;세 가지 인사이트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;은 단위가 작아 (per node × per worker) 곱이 무서움. work_mem을 함부로 못 올리는 이유.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL Server&lt;/strong&gt;는 컴파일 타임 예약이라 통계 부정확이 spill로 직결됨. Grant Feedback이 이 약점을 보완.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oracle&lt;/strong&gt;은 자동 관리가 가장 성숙하지만 &lt;code&gt;PGA_AGGREGATE_TARGET&lt;/code&gt;이 너무 작으면 multi-pass 전체로 무너짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;
&lt;p&gt;책의 한 문장 “메모리가 부족하면 일시 영역을 쓴다”의 안쪽에는 알고리즘 차원, 모드 차원, 구현 차원의 세 층이 숨어 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;알고리즘 차원&lt;/strong&gt;: 정렬·해시는 본래 메모리 내 알고리즘(Quicksort, In-memory Hash)으로 설계되었지만, 메모리가 부족해지면 External Merge Sort · Hybrid Hash Join 같은 다른 알고리즘으로 통째로 전환된다. 단순히 “조금 느려진다”가 아니라 &lt;strong&gt;다른 알고리즘이 돌아간다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;모드 차원&lt;/strong&gt;: 그 전환의 비용은 메모리가 얼마나 부족하냐에 따라 단계가 있다. &lt;strong&gt;Optimal → One-pass → Multi-pass&lt;/strong&gt;. K-way Merge의 메모리 요구는 √(D × P)에 비례하므로, 1 GB 정렬에 22 MB 정도면 One-pass로 충분히 처리된다. 하지만 Multi-pass에 빠지면 비용이 K배수로 곱해진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;구현 차원&lt;/strong&gt;: 세 DBMS가 같은 문제를 다른 방식으로 해결한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Oracle: &lt;strong&gt;자동 관리&lt;/strong&gt;, V$ 뷰로 명시적 추적, PGA per-process 격리.&lt;/li&gt;
&lt;li&gt;PostgreSQL: &lt;strong&gt;노드당 메모리&lt;/strong&gt;, EXPLAIN ANALYZE에 모든 정보, parallel 곱셈의 함정.&lt;/li&gt;
&lt;li&gt;SQL Server: &lt;strong&gt;정적 예약 + 적응형 피드백&lt;/strong&gt;, Row/Batch mode 분기, tempdb spill.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
가장 위험한 실수는 셋 다 공통이다, &lt;strong&gt;“안 보이니 없는 줄 안다.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;EXPLAIN / 동적 뷰 / XEvent에 &lt;code&gt;external merge Disk&lt;/code&gt;, &lt;code&gt;Batches: N (N&gt;1)&lt;/code&gt;, &lt;code&gt;PASS &gt;= 1&lt;/code&gt;, &lt;code&gt;Sort Warning&lt;/code&gt; 같은 신호가 보이면 메모리 부족이 발생한 것이고, 이건 단순한 느려짐이 아니라 &lt;strong&gt;알고리즘 모드 전환&lt;/strong&gt;이라는 점을 기억하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;익숙한 DB일수록 EXPLAIN 한 줄을 더 자세히 읽을 가치가 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ 이 글은 “SQL 레벨업”의 한 문단을 출발점으로 삼아, 공식 문서(Oracle Database Performance Tuning Guide, PostgreSQL Docs, Microsoft Learn)를 참조하여 &lt;strong&gt;AI(Claude)와 함께 작성&lt;/strong&gt;되었습니다. 알고리즘 설명과 비교표의 사실 관계는 아래 참고 자료 기준으로 검증했으나, 운영 환경에서 적용할 때는 각 DBMS의 해당 버전 문서를 다시 확인하길 권합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/tgdba/tuning-program-global-area.html&quot;&gt;Oracle Database Performance Tuning Guide, Tuning the Program Global Area&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/runtime-config-resource.html&quot;&gt;PostgreSQL Docs, 19.4. Resource Consumption (work_mem, hash_mem_multiplier)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/using-explain.html&quot;&gt;PostgreSQL Docs, 14.1. Using EXPLAIN (Sort Method, Buckets/Batches)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/sql/relational-databases/memory-management-architecture-guide&quot;&gt;Microsoft Learn, Memory Management Architecture Guide (SQL Server)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/sql/relational-databases/memory-management-architecture-guide#memory-grant-considerations&quot;&gt;Microsoft Learn, Memory Grant considerations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;External Merge Sort, Grace Hash Join, Hybrid Hash Join, Database Systems: The Complete Book (Garcia-Molina, Ullman, Widom) §15&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>oracle</category><category>postgresql</category><category>sqlserver</category><category>sort</category><category>hash</category><category>memory</category><category>internals</category><author>koa (김신건)</author></item><item><title>UNION vs OR vs IN 벤치마크 (1), 실험 설계와 결과</title><link>https://shinkeonkim.com/posts/2026-06-13-sparse-column-benchmark-01/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-13-sparse-column-benchmark-01/</guid><description>&quot;SQL 레벨업&quot;에서 UNION을 권장하는 패턴을 PostgreSQL/MySQL 6개 버전에서 실측한 결과. MySQL에서는 900배 차이가 난다.</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;동기-or은-인덱스를-못-탄다는-게-진짜일까&quot;&gt;동기: “OR은 인덱스를 못 탄다”는 게 진짜일까?&lt;/h2&gt;
&lt;p&gt;“SQL 레벨업”이라는 책을 읽다가 &lt;strong&gt;UNION을 사용할 수밖에 없는 경우&lt;/strong&gt;로 소개되는 사례가 눈에 들어왔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;서로 다른 테이블이라면 당연히 UNION을 사용해야 한다. 하지만 같은 테이블에서도, 서로 다른 컬럼에 대한 OR 조건이 걸리면 인덱스를 활용할 수 없으므로 UNION ALL로 분리해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PostgreSQL을 주로 쓰는 입장에서 두 가지 의문이 생겼다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IN&lt;/code&gt;으로 쓰면 오히려 더 빠르지 않을까?&lt;/strong&gt;, &lt;code&gt;(col_a, col_b) IN ((v1, v2))&lt;/code&gt; 형태가 옵티마이저에게 더 많은 정보를 줄 것 같았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OR도 생각보다 괜찮지 않을까?&lt;/strong&gt;, PostgreSQL은 뭔가 다르게 처리하는 것 같은 느낌이 있었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;추측만으로 판단하긴 싫어서, Docker Compose로 PostgreSQL 3개 버전(13/15/17)과 MySQL 3개 버전(5.7/8.0/8.4)을 띄우고 직접 벤치마크를 돌려보기로 했다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;실험-설계&quot;&gt;실험 설계&lt;/h2&gt;
&lt;h3 id=&quot;테이블-구조-sparse-multi-column&quot;&gt;테이블 구조: Sparse Multi-Column&lt;/h3&gt;
&lt;p&gt;하나의 행에 50개의 &lt;code&gt;(date_N, bool_N)&lt;/code&gt; 쌍이 있되, &lt;strong&gt;정확히 하나의 쌍만 값이 있고 나머지는 NULL&lt;/strong&gt;인 구조다. 현실에서 이벤트 타입별 메타데이터가 다른 테이블을 하나로 합친 것, 또는 다형성을 가진 엔티티 테이블과 유사하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bench_target&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BIGSERIAL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    date_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, bool_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BOOLEAN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 쌍 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    date_2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, bool_2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BOOLEAN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 쌍 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    date_50 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, bool_50 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;BOOLEAN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 쌍 50&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    payload &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 각 쌍에 복합 인덱스&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_pair_1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bench_target (date_1, bool_1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_pair_2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bench_target (date_2, bool_2);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; idx_pair_50&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bench_target (date_50, bool_50);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심은 &lt;strong&gt;각 행이 50개 쌍 중 딱 하나에만 데이터를 가진다&lt;/strong&gt;는 점이다. 따라서 &lt;code&gt;date_1 = &apos;2024-03-15&apos; AND bool_1 = TRUE&lt;/code&gt;를 찾으면 &lt;code&gt;idx_pair_1&lt;/code&gt;이 매우 효율적으로 동작해야 한다. 문제는 이런 조건을 &lt;strong&gt;여러 쌍에 대해 동시에&lt;/strong&gt; 걸 때 발생한다.&lt;/p&gt;
&lt;h3 id=&quot;세-가지-쿼리-변형&quot;&gt;세 가지 쿼리 변형&lt;/h3&gt;
&lt;p&gt;같은 데이터를 조회하는 세 가지 SQL 문법:&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;재현성을-위한-환경-통제&quot;&gt;재현성을 위한 환경 통제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker Compose로 각 버전을 격리 (CPU/mem pinned)&lt;/li&gt;
&lt;li&gt;Prepared statement 미사용 → 파싱 비용까지 포함하여 통일&lt;/li&gt;
&lt;li&gt;동일 시드로 데이터 생성 → 동일 조건으로 비교&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-DBMS 절대 비교 금지&lt;/strong&gt;: 같은 DB 내에서 변형 간 비교만 유효&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;결과-요약&quot;&gt;결과 요약&lt;/h2&gt;
&lt;h3 id=&quot;postgresql-세-변형-모두-비등&quot;&gt;PostgreSQL: 세 변형 모두 비등&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;






















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;쌍 수 (n)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Tuple IN&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;OR 조건&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;UNION ALL&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Plan&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002 ms&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.003 ms&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002 ms&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Index Scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BitmapOr / Append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BitmapOr / Append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.013&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.014&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.018&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BitmapOr / Append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.031&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.023&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.036&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BitmapOr / Append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.066&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.077&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.096&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;BitmapOr / Append&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
모든 수치가 &lt;strong&gt;0.1ms 이하&lt;/strong&gt;. 변형 간 차이는 stddev 범위 내에 있어 유의미하지 않다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mysql-union-all-압승-inor는-full-table-scan&quot;&gt;MySQL: UNION ALL 압승, IN/OR는 Full Table Scan&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;





























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;쌍 수 (n)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Tuple IN&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;OR 조건&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;UNION ALL&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;IN/OR Plan&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;UNION Plan&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref (인덱스)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;40.35&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;40.35&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.006&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ALL (풀스캔)&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;45.10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;44.55&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.013&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ALL&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56.05&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56.15&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.028&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ALL&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;77.50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;80.50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.055&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ALL&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;148.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;147.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.158&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ALL&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
쌍이 &lt;strong&gt;2개 이상&lt;/strong&gt;이면 IN/OR 모두 Full Table Scan으로 퇴화. n=50에서 UNION ALL 대비 &lt;strong&gt;약 900배&lt;/strong&gt; 느리다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mysql-버전별-비교-57--80--84&quot;&gt;MySQL 버전별 비교: 5.7 → 8.0 → 8.4&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;p&gt;버전 업으로 해결되지 않는다. 5.7/8.0/8.4 모두 동일하게 Full Table Scan을 선택한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;io-블록-비교&quot;&gt;I/O 블록 비교&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;hr&gt;
&lt;h2 id=&quot;의외의-발견-in과-or의-성능이-같다&quot;&gt;의외의 발견: IN과 OR의 성능이 같다&lt;/h2&gt;
&lt;p&gt;실험 전에는 “OR은 느리고 IN은 빠를 것”이라고 예상했다. 튜플 IN이 옵티마이저에게 더 구조화된 정보를 줄 거라 생각했기 때문이다.&lt;/p&gt;
&lt;p&gt;하지만 결과는:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;: IN과 OR 모두 동일한 BitmapOr 플랜. 플래너가 내부적으로 같은 형태로 변환.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL&lt;/strong&gt;: IN과 OR 모두 동일하게 Full Table Scan. 둘 다 “여러 컬럼에 걸친 OR”로 인식됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;(col_a, col_b) IN ((v1, v2))&lt;/code&gt; 는 결국 &lt;code&gt;col_a = v1 AND col_b = v2&lt;/code&gt;로 해석되므로, 최종적으로 OR로 연결된 것과 동일하게 처리된다. &lt;strong&gt;“IN이 OR보다 빠르다”는 통념은 이 패턴에서는 맞지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
“IN이 OR보다 빠르다”가 유효한 경우는 &lt;strong&gt;같은 컬럼에 대한 여러 값&lt;/strong&gt; (&lt;code&gt;WHERE id IN (1,2,3)&lt;/code&gt; vs &lt;code&gt;WHERE id=1 OR id=2 OR id=3&lt;/code&gt;)일 때다. 서로 다른 컬럼에 걸친 OR과는 완전히 다른 시나리오.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;실무-적용&quot;&gt;실무 적용&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;상황&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;PostgreSQL&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;MySQL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1개 쌍 조회&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;아무 방식 OK&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;아무 방식 OK&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2개+ 서로 다른 컬럼 쌍&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;아무 방식 OK&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;반드시 UNION ALL&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;50개 쌍 조회&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;0.07~0.10 ms&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION: 0.16 ms / IN·OR: &lt;strong&gt;148 ms&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
MySQL 사용자라면 서로 다른 컬럼에 걸친 OR/IN 조건이 2개 이상일 때 &lt;strong&gt;반드시 UNION ALL로 분리&lt;/strong&gt;해야 한다. ORM이 자동으로 OR을 생성한다면 raw query로 UNION을 직접 작성하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;벤치마크-코드-쿼리-생성기&quot;&gt;벤치마크 코드: 쿼리 생성기&lt;/h2&gt;
&lt;p&gt;실험의 핵심인 쿼리 생성 로직을 공유한다. 세 변형을 동일한 조건에서 생성하는 Python 코드:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; QueryGenerator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;각 쌍에 대해 세 가지 SQL 변형을 생성.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    TABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;bench_target&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; generate_conditions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, count: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; list[tuple[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;&quot;&quot;count개의 서로 다른 쌍 인덱스와 날짜를 결정론적으로 생성.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        available_pairs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; list&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;51&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;._rng.shuffle(available_pairs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        pairs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; available_pairs[:count]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        conditions &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pair_idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pairs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            days_offset &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;._rng.randint(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DATE_RANGE_DAYS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            d &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; BASE_DATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timedelta(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;days&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;days_offset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            conditions.append((pair_idx, d.isoformat()))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conditions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _build_or&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, conditions, engine):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;&quot;&quot;OR 방식: (date_N=&apos;X&apos; AND bool_N=TRUE) OR ...&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        bool_val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;TRUE&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; engine &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        clauses &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot; OR &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.join(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;(date_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;idx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; = &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos; AND bool_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;idx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bool_val&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx, dt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conditions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SELECT id, payload FROM &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TABLE}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WHERE &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;clauses&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _build_union&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, conditions, engine):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;&quot;&quot;UNION ALL 방식: 각 조건을 독립 SELECT로.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        bool_val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;TRUE&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; engine &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        queries &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SELECT id, payload FROM &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TABLE}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;WHERE date_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;idx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; = &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos; AND bool_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;idx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bool_val&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx, dt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conditions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot; UNION ALL &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.join(queries)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _build_tuple_in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, conditions, engine):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;&quot;&quot;Tuple IN 방식: (date_N, bool_N) IN ((&apos;X&apos;, TRUE)) OR ...&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        bool_val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;TRUE&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; engine &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        clauses &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot; OR &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.join(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;(date_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;idx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;, bool_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;idx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;) IN ((&apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bool_val&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;))&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx, dt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conditions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SELECT id, payload FROM &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TABLE}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; WHERE &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;clauses&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심 포인트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;동일한 &lt;code&gt;conditions&lt;/code&gt;&lt;/strong&gt; 를 세 변형에 모두 전달하여, 정확히 같은 데이터를 조회&lt;/li&gt;
&lt;li&gt;시드 고정(&lt;code&gt;seed=42&lt;/code&gt;)으로 재현성 보장&lt;/li&gt;
&lt;li&gt;50개 쌍 중 랜덤하게 n개를 선택하여 각각에 대한 날짜 조건 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;다음-글에서&quot;&gt;다음 글에서&lt;/h2&gt;
&lt;p&gt;결과는 확인했다. 그런데 &lt;strong&gt;왜&lt;/strong&gt; 이런 차이가 발생하는 걸까?&lt;/p&gt;
&lt;p&gt;PostgreSQL에서 세 변형이 모두 빠른 이유는 &lt;strong&gt;Bitmap Scan&lt;/strong&gt;이라는 고유한 메커니즘 때문이다. 다음 글에서는 BitmapOr가 어떻게 여러 인덱스를 하나로 합치는지, TID Bitmap의 내부 구조는 어떤지, 그리고 MySQL은 왜 이를 할 수 없는지를 깊이 다룬다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;측정: EXPLAIN ANALYZE actual time, warmup 3회 + 10회 반복, p50/p95/p99/stddev&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>mysql</category><category>benchmark</category><category>index</category><category>sql</category><author>koa (김신건)</author></item><item><title>UNION vs OR vs IN 벤치마크 (2), PostgreSQL BitmapOr 완전 해부</title><link>https://shinkeonkim.com/posts/2026-06-13-sparse-column-benchmark-02/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-13-sparse-column-benchmark-02/</guid><description>PostgreSQL이 서로 다른 컬럼의 OR 조건에서도 모든 인덱스를 활용할 수 있는 비밀: Bitmap Scan의 동작 원리와 TID Bitmap 내부 구조를 해부한다.</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;왜-전통적-index-scan으로는-부족한가&quot;&gt;왜 “전통적 Index Scan”으로는 부족한가&lt;/h2&gt;
&lt;p&gt;일반적인 Index Scan의 동작을 먼저 이해하자.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;인덱스 리프 노드에서 TID를 하나 꺼냄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 해당 TID가 가리키는 테이블 페이지로 이동&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 행을 읽음&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 다음 TID를 꺼냄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → 또 다른 테이블 페이지로 이동&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 TID란 &lt;strong&gt;Tuple ID&lt;/strong&gt; = &lt;code&gt;(페이지 번호, 페이지 내 오프셋)&lt;/code&gt;으로, 행의 물리적 위치를 가리키는 주소다.&lt;/p&gt;
&lt;p&gt;이 방식에는 두 가지 한계가 있다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Random I/O&lt;/strong&gt;: 결과가 여러 페이지에 흩어져 있으면, 매번 다른 페이지로 점프해야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단일 인덱스 제약&lt;/strong&gt;: 하나의 인덱스에서만 TID를 가져올 수 있음. 서로 다른 인덱스의 결과를 “합칠” 방법이 없음&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;우리 벤치마크의 상황을 생각해보자:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (date_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;X&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bool_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TRUE)    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- idx_pair_1 사용&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;   OR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (date_5 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;Y&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bool_5 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TRUE)    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- idx_pair_5 사용&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;   OR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (date_12 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;Z&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bool_12 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TRUE)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- idx_pair_12 사용&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;세 조건은 각각 다른 인덱스를 타야 한다. Index Scan은 &lt;strong&gt;하나의 인덱스만&lt;/strong&gt; 선택할 수 있으므로, 나머지 두 조건은 필터로 처리하거나 포기해야 한다. 이것이 MySQL이 Full Table Scan을 선택하는 이유이기도 하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;bitmap-scan-중간-추상-계층의-도입&quot;&gt;Bitmap Scan: 중간 추상 계층의 도입&lt;/h2&gt;
&lt;p&gt;PostgreSQL은 이 문제를 &lt;strong&gt;Bitmap&lt;/strong&gt;이라는 중간 자료구조를 도입하여 해결한다.&lt;/p&gt;
&lt;p&gt;핵심 아이디어: &lt;strong&gt;인덱스 결과를 즉시 행으로 가져오지 않고, “어떤 페이지를 방문해야 하는지”를 먼저 메모리에 기록한 뒤, 나중에 한 번에 읽는다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 아이디어 덕분에:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러 인덱스의 결과를 비트 연산으로 합칠 수 있고 (BitmapOr / BitmapAnd)&lt;/li&gt;
&lt;li&gt;페이지를 물리적 순서대로 방문하여 I/O를 최적화할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3단계로-이해하는-bitmap-scan&quot;&gt;3단계로 이해하는 Bitmap Scan&lt;/h2&gt;
&lt;h3 id=&quot;1단계-bitmapindexscan-비트맵-생성&quot;&gt;1단계: BitmapIndexScan, 비트맵 생성&lt;/h3&gt;
&lt;p&gt;각 인덱스를 B-tree로 탐색하여 조건에 맞는 행들의 **위치(TID)**를 비트맵으로 수집한다.&lt;/p&gt;
&lt;p&gt;이 비트맵은 행 자체가 아니라, “테이블의 몇 번 페이지에 결과가 있다”는 위치 정보를 담는다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idx_pair_1에서 date_1=&apos;2024-03-15&apos; AND bool_1=TRUE 스캔 결과:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → Bitmap A: 페이지 42에 행 3개, 페이지 156에 행 1개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idx_pair_5에서 date_5=&apos;2024-07-01&apos; AND bool_5=TRUE 스캔 결과:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → Bitmap B: 페이지 12에 행 1개, 페이지 42에 행 2개&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idx_pair_12에서 date_12=&apos;2024-11-20&apos; AND bool_12=TRUE 스캔 결과:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  → Bitmap C: 페이지 200에 행 1개, 페이지 891에 행 1개&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 단계에서는 &lt;strong&gt;테이블 데이터를 전혀 읽지 않는다.&lt;/strong&gt; 인덱스만 탐색한다.&lt;/p&gt;
&lt;h3 id=&quot;2단계-bitmapor-비트맵-합치기&quot;&gt;2단계: BitmapOr, 비트맵 합치기&lt;/h3&gt;
&lt;p&gt;여러 비트맵을 &lt;strong&gt;메모리 내 비트 OR 연산&lt;/strong&gt;으로 합친다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bitmap A:  {42, 156}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bitmap B:  {12, 42}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bitmap C:  {200, 891}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OR 결과:   {12, 42, 156, 200, 891}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 연산은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;순수 메모리 내 비트 연산이므로 &lt;strong&gt;디스크 I/O가 0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;시간 복잡도는 비트맵 크기에 비례 (보통 수십~수백 KB)&lt;/li&gt;
&lt;li&gt;페이지 42가 A와 B 모두에 있지만, 결과에는 &lt;strong&gt;한 번만&lt;/strong&gt; 등장 → 중복 제거 자동&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3단계-bitmapheapscan-페이지-순서대로-읽기&quot;&gt;3단계: BitmapHeapScan, 페이지 순서대로 읽기&lt;/h3&gt;
&lt;p&gt;합쳐진 비트맵의 페이지를 &lt;strong&gt;물리적 번호 순서&lt;/strong&gt;대로 방문한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 12  → 읽고 → 조건 Recheck → 매칭 행 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 42  → 읽고 → 조건 Recheck → 매칭 행 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 156 → 읽고 → 조건 Recheck → 매칭 행 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 200 → 읽고 → 조건 Recheck → 매칭 행 반환&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 891 → 읽고 → 조건 Recheck → 매칭 행 반환&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;물리적 순서대로 방문하므로:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HDD에서는 디스크 헤드 이동 최소화&lt;/li&gt;
&lt;li&gt;SSD에서도 OS read-ahead(선읽기) 효과 활용 가능&lt;/li&gt;
&lt;li&gt;결과적으로 &lt;strong&gt;Sequential I/O에 가까운 효율&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;실제-explain으로-확인&quot;&gt;실제 EXPLAIN으로 확인&lt;/h2&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;해석:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bitmap Index Scan × 5&lt;/strong&gt;: 5개의 서로 다른 인덱스를 각각 스캔. 인덱스 2블록씩 = 10블록&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BitmapOr&lt;/strong&gt;: 5개의 비트맵을 OR로 합침 (I/O 0, 시간 0.003ms 미만)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bitmap Heap Scan&lt;/strong&gt;: 합쳐진 비트맵 기준 3개 페이지만 방문 = 3블록&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;총 I/O&lt;/strong&gt;: 13블록. 10만 행 테이블에서 13블록만 읽고 완료.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;union-all-vs-bitmapor-같은-비용-다른-경로&quot;&gt;UNION ALL vs BitmapOr: 같은 비용, 다른 경로&lt;/h2&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;UNION ALL (Append)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;OR (BitmapOr)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;총 I/O 블록&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Planning Time&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.134 ms&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.042 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Execution Time&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008 ms&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이지 방문 순서&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;보장 없음&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;물리적 순서&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;중복 페이지 제거&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;불가능&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;자동&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;실행 시간 차이는 무시할 수준. Planning Time은 UNION ALL이 약간 더 높은데, 각 SELECT를 개별 최적화해야 하기 때문이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;tid-bitmap의-내부-구조&quot;&gt;TID Bitmap의 내부 구조&lt;/h2&gt;
&lt;p&gt;여기서부터가 핵심이다. PostgreSQL의 비트맵(&lt;code&gt;src/backend/nodes/tidbitmap.c&lt;/code&gt;)은 단순한 bit 배열이 아니라, 메모리 효율을 위한 정교한 구조를 가지고 있다.&lt;/p&gt;
&lt;h3 id=&quot;비트맵이란-정확히-무엇인가&quot;&gt;”비트맵”이란 정확히 무엇인가&lt;/h3&gt;
&lt;p&gt;PostgreSQL에서 테이블은 &lt;strong&gt;8KB 페이지&lt;/strong&gt;의 연속이다. 각 페이지에는 여러 행(tuple)이 담긴다. TID Bitmap은 이 구조를 이용한다.&lt;/p&gt;
&lt;p&gt;비유하자면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테이블 = 거대한 책&lt;/li&gt;
&lt;li&gt;페이지 = 책의 쪽&lt;/li&gt;
&lt;li&gt;행 = 쪽 안의 특정 줄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TID Bitmap&lt;/strong&gt; = “몇 쪽의 몇 번째 줄을 읽어야 하는지” 표시한 메모&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;두-가지-해상도-exact-vs-lossy&quot;&gt;두 가지 해상도: Exact vs Lossy&lt;/h3&gt;
&lt;p&gt;비트맵은 &lt;strong&gt;메모리 사정에 따라&lt;/strong&gt; 두 가지 모드로 동작한다.&lt;/p&gt;
&lt;h4 id=&quot;exact-모드-기본&quot;&gt;Exact 모드 (기본)&lt;/h4&gt;
&lt;p&gt;페이지 번호 + **해당 페이지 내 행의 정확한 위치(offset)**를 비트로 기록한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 42: [bit 3 = ON, bit 7 = ON, bit 9 = ON]  → 3번, 7번, 9번 행만 해당&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 156: [bit 1 = ON]                          → 1번 행만 해당&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;8KB 페이지에는 보통 수십~수백 행이 들어가므로, 한 페이지당 약 226행(MaxHeapTuplesPerPage) = 226비트 ≈ 29바이트의 비트맵이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: 페이지를 읽을 때 정확히 어떤 행이 필요한지 알므로, 불필요한 행을 건너뛸 수 있다.&lt;/p&gt;
&lt;h4 id=&quot;lossy-모드-메모리-부족-시&quot;&gt;Lossy 모드 (메모리 부족 시)&lt;/h4&gt;
&lt;p&gt;페이지 번호&lt;strong&gt;만&lt;/strong&gt; 기록한다. “이 페이지 어딘가에 결과가 있다”만 아는 상태.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 42: [전체 페이지 표시]  → 어딘가에 결과가 있음 (정확히 어디인지는 모름)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;페이지 156: [전체 페이지 표시]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: 한 페이지당 1비트만 필요. 메모리 극적으로 절약.
&lt;strong&gt;단점&lt;/strong&gt;: 페이지를 읽을 때 &lt;strong&gt;모든 행&lt;/strong&gt;에 대해 조건을 다시 확인(Recheck)해야 함.&lt;/p&gt;
&lt;h4 id=&quot;전환-메커니즘&quot;&gt;전환 메커니즘&lt;/h4&gt;
&lt;p&gt;비트맵 크기가 &lt;code&gt;work_mem&lt;/code&gt;을 초과하면, 가장 행이 많은 페이지부터 Lossy로 전환한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[BitmapIndexScan 시작] --&gt; B[TID를 비트맵에 추가]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; C{&quot;비트맵 크기 &gt; work_mem?&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|No| D[Exact 모드 유지]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|Yes| E[&quot;Lossy 모드로 전환&amp;#x3C;br/&gt;(행 offset 비트 삭제)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D --&gt; F[Heap Scan 시 해당 행만 반환]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    E --&gt; G[&quot;Heap Scan 시 페이지 전체 읽고&amp;#x3C;br/&gt;조건 Recheck&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EXPLAIN 결과에서 이를 확인할 수 있다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Heap Blocks: exact=1234 lossy=56&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;exact=1234&lt;/code&gt;: 1,234개 페이지는 행 단위 정밀도 → Recheck 불필요 (정확한 행만 반환)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lossy=56&lt;/code&gt;: 56개 페이지는 페이지 단위만 알고 있음 → 해당 페이지의 모든 행을 조건으로 다시 필터&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;메모리-효율-계산&quot;&gt;메모리 효율 계산&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exact 모드&lt;/strong&gt;: 1페이지 당 ~29바이트 → 1GB 테이블(131,072 페이지) ≈ 3.6MB 비트맵&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lossy 모드&lt;/strong&gt;: 1페이지 당 1비트 → 1GB 테이블 ≈ 16KB 비트맵&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;64GB 테이블 전체를 Lossy에서 약 1MB&lt;/strong&gt;로 표현 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대부분의 실무 쿼리에서는 결과가 테이블의 극히 일부이므로, 비트맵은 수십~수백 KB 수준이다. &lt;code&gt;work_mem&lt;/code&gt;의 기본값(4MB)으로도 충분한 경우가 대부분.&lt;/p&gt;
&lt;h3 id=&quot;왜-recheck-cond가-항상-출력되는가&quot;&gt;왜 “Recheck Cond”가 항상 출력되는가&lt;/h3&gt;
&lt;p&gt;EXPLAIN에서 Bitmap Heap Scan의 &lt;code&gt;Recheck Cond&lt;/code&gt; 표시를 본 적 있을 것이다:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bitmap Heap Scan on bench_target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Recheck Cond: ((date_1 = &apos;2024-03-15&apos;) AND (bool_1 = true)) OR ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이것은 “이 조건으로 Recheck합니다”라는 의미인데, &lt;strong&gt;항상 표시&lt;/strong&gt;된다. Exact 모드에서도 표시되지만, 실제로는 Exact 페이지에서는 Recheck를 건너뛸 수 있다. PostgreSQL은 “혹시 Lossy 페이지가 있을 수 있으니” 항상 준비해두는 것이다.&lt;/p&gt;
&lt;p&gt;실제로 Lossy가 발생했는지는 &lt;code&gt;Heap Blocks: exact=... lossy=...&lt;/code&gt;에서 확인한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;bitmapand-교집합도-가능하다&quot;&gt;BitmapAnd, 교집합도 가능하다&lt;/h2&gt;
&lt;p&gt;BitmapOr가 합집합(OR)이라면, &lt;strong&gt;BitmapAnd&lt;/strong&gt;는 교집합(AND)이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 복합 조건이 각각 다른 단일 컬럼 인덱스에 걸릴 때&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; col_a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;x&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; col_b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;y&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- idx_a의 비트맵 AND idx_b의 비트맵 → 교집합 페이지만 방문&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비트맵이라는 중간 계층 덕분에 OR/AND 어떤 조합이든 여러 인덱스를 자유롭게 결합할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;플래너는-언제-bitmap-scan을-선택하는가&quot;&gt;플래너는 언제 Bitmap Scan을 선택하는가&lt;/h2&gt;
&lt;p&gt;PostgreSQL 플래너는 비용 모델을 기반으로 세 가지 스캔 방식 중 하나를 선택한다:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;상황&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Index Scan&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Bitmap Scan&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;Seq Scan&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;결과 행 수&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;소수 (수십 이하)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간 (수십~수만)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;대량 (&gt;10-20%)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;결과 분포&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;한 곳에 집중&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;여러 페이지에 분산&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;같은 인덱스 내&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;다른 인덱스에 걸침&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;I/O 특성&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;Random (허용)&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;Sequential-like&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;Sequential&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;우리 벤치마크에서도 확인된다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;n=1&lt;/strong&gt;: 결과가 극소수 + 단일 인덱스 → Index Scan 선택&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;n≥3&lt;/strong&gt;: 여러 인덱스 필요 + 결과가 분산 → BitmapOr 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;비용 계산:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bitmap Scan 비용 = Σ(인덱스 스캔) + 비트맵 OR 비용(거의 0) + 방문 페이지 × seq_page_cost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Index Scan 비용 = 인덱스 탐색 + 결과 행 × random_page_cost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;페이지를 순서대로 읽으므로 &lt;code&gt;seq_page_cost&lt;/code&gt;(기본 1.0)가 적용되는 반면, Index Scan은 &lt;code&gt;random_page_cost&lt;/code&gt;(기본 4.0)가 적용된다. 결과가 많을수록 Bitmap Scan이 유리해진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;왜-mysql은-이것을-할-수-없는가&quot;&gt;왜 MySQL은 이것을 할 수 없는가&lt;/h2&gt;
&lt;p&gt;MySQL에도 &lt;strong&gt;Index Merge Union&lt;/strong&gt;이라는 유사한 기능이 존재한다. 하지만 근본적인 차이가 있다.&lt;/p&gt;
&lt;h3 id=&quot;mysql-index-merge의-동작-방식&quot;&gt;MySQL Index Merge의 동작 방식&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- MySQL에서 Index Merge가 작동하는 경우&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; col_a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; OR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; col_b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- idx_a 스캔 → rowid 목록A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- idx_b 스캔 → rowid 목록B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- 목록A ∪ 목록B → 행 fetch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;핵심-제약-각-or-브랜치가-단일-인덱스로-완전히-해결-가능해야&quot;&gt;핵심 제약: “각 OR 브랜치가 단일 인덱스로 완전히 해결 가능해야”&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- ❌ MySQL에서 Index Merge 불가&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (date_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;X&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bool_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TRUE)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;   OR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (date_5 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;Y&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bool_5 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TRUE)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MySQL 옵티마이저는 이 구조를 만나면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“WHERE 전체를 커버하는 단일 인덱스가 있는가?” → 없음&lt;/li&gt;
&lt;li&gt;“Index Merge로 처리할 수 있는가?” → 복합 조건(AND)이 포함된 OR은 인식 못함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포기 → Full Table Scan&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;구조적-차이-정리&quot;&gt;구조적 차이 정리&lt;/h3&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;PostgreSQL&lt;/th&gt;&lt;th&gt;MySQL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;중간 자료구조&lt;/td&gt;&lt;td&gt;TID Bitmap (범용)&lt;/td&gt;&lt;td&gt;없음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;여러 인덱스 합치기&lt;/td&gt;&lt;td&gt;BitmapOr/BitmapAnd&lt;/td&gt;&lt;td&gt;Index Merge (제한적)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;복합 조건 OR&lt;/td&gt;&lt;td&gt;각 인덱스 → 비트맵 합치기 ✓&lt;/td&gt;&lt;td&gt;&lt;strong&gt;인식 불가 → Full Scan&lt;/strong&gt; ✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;메모리 관리&lt;/td&gt;&lt;td&gt;work_mem 기반, Lossy 전환&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이지 방문 순서&lt;/td&gt;&lt;td&gt;물리적 순서 (sequential-like)&lt;/td&gt;&lt;td&gt;보장 없음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;PostgreSQL의 핵심 설계: B-tree 결과를 &lt;strong&gt;즉시 행으로 가져오지 않고&lt;/strong&gt;, 비트맵이라는 중간 계층을 두어 여러 인덱스 결과를 합친 뒤 한 번에 읽는다.&lt;/p&gt;
&lt;p&gt;MySQL은 이 중간 계층이 없으므로, 인덱스에서 결과를 가져오면 바로 행을 fetch해야 한다. “여러 인덱스 결과를 먼저 모아놓고 합치기”가 구조적으로 제한된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;개념&lt;/th&gt;&lt;th&gt;역할&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;TID&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Tuple ID = (페이지 번호, 오프셋). 행의 물리적 주소.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;TID Bitmap&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”방문할 페이지 목록”을 비트로 표현한 메모리 내 구조&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;BitmapIndexScan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;인덱스를 스캔하여 TID Bitmap 생성&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;BitmapOr&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;여러 비트맵의 합집합 (비트 OR 연산)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;BitmapAnd&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;여러 비트맵의 교집합 (비트 AND 연산)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;BitmapHeapScan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;합쳐진 비트맵의 페이지를 순서대로 방문&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Exact 모드&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;페이지+행 위치 기록. Recheck 불필요.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Lossy 모드&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;페이지만 기록. 읽을 때 조건 Recheck 필요.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;work_mem&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;비트맵 최대 크기 결정. 초과 시 Lossy 전환.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/indexes-bitmap-scans.html&quot;&gt;PostgreSQL Docs, Combining Multiple Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doxygen.postgresql.org/tidbitmap_8c_source.html&quot;&gt;PostgreSQL Source, tidbitmap.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doxygen.postgresql.org/nodeBitmapOr_8c_source.html&quot;&gt;PostgreSQL Source, nodeBitmapOr.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html&quot;&gt;MySQL Docs, Index Merge Optimization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>index</category><category>bitmap-scan</category><category>internals</category><author>koa (김신건)</author></item><item><title>UNION vs OR vs IN 벤치마크 (3), 전체 데이터와 실무 권고</title><link>https://shinkeonkim.com/posts/2026-06-13-sparse-column-benchmark-03/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-06-13-sparse-column-benchmark-03/</guid><description>PostgreSQL 3버전 + MySQL 3버전의 전체 측정 데이터와, 이 결과를 실무에 어떻게 적용할지 정리한다.</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;전체-성능-데이터&quot;&gt;전체 성능 데이터&lt;/h2&gt;
&lt;h3 id=&quot;postgresql-전-버전&quot;&gt;PostgreSQL (전 버전)&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;details&gt;
&lt;summary&gt;PostgreSQL 전 버전 상세 테이블 (접기/펼치기)&lt;/summary&gt;




















































































































































































































































































































































































































































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;DB&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;n&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;p50 (ms)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;p95 (ms)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;I/O 블록&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Plan&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.001&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.001&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.009&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.006&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.011&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.010&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.015&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.023&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.017&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.023&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.024&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.041&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.032&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.043&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.032&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.033&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.032&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.032&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.085&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.101&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;140&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.087&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.103&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;142&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.102&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.164&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;140&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.009&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.011&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.013&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.014&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.020&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.015&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.017&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.015&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.022&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.027&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.032&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.028&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.037&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.035&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.044&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.081&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.104&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;140&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.086&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.092&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;140&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg15&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.098&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.106&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;142&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.003&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.003&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.006&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;index_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.004&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.005&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.009&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.014&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.007&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.012&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;13&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.013&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.021&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.014&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.020&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.018&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.029&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;29&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.023&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.025&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.030&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.044&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;55&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.036&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.041&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.066&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.088&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;140&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.077&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.090&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;140&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;bitmap_heap_scan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;pg17&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.096&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.184&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;142&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;append&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/details&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mysql-전-버전&quot;&gt;MySQL (전 버전)&lt;/h3&gt;
&lt;!-- mdx-component: ChartJs --&gt;
&lt;details&gt;
&lt;summary&gt;MySQL 전 버전 상세 테이블 (접기/펼치기)&lt;/summary&gt;











































































































































































































































































































































































































































































































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;DB&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;n&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;p50 (ms)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;p95 (ms)&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;I/O 블록&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Plan&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;판정&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.084&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.105&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.118&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.129&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.128&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.166&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.111&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.174&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;26&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;549.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;559.6&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;552.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;556.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.195&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.221&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;68&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;557.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;563.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;560.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;569.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.459&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;162&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;579.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;623.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;619.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;754.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.730&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.948&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;306&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;603.6&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;611.2&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;680.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;710.8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;782&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;691.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;700.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql57&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;693.6&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;697.7&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;850,026&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;119&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;119&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.003&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;119&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.006&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.006&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;37.2&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;37.9&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;37.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;38.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.014&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.017&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;238&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;41.9&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;44.6&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;43.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;46.8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.028&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.042&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;425&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;51.8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;60.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;52.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;53.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.055&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.067&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;714&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;71.2&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;72.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;72.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;77.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.158&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.178&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,666&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;131.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;134.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql80&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;133.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;138.8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,153&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;221&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;221&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.002&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;221&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.006&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.008&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;40.3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;41.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;40.4&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;41.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.013&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.014&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;340&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;44.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;45.6&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;45.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;46.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.028&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.034&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;527&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;57.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;56.1&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;59.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.055&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.064&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;816&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;77.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;79.8&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;80.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;81.5&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;UNION ALL&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.158&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;0.174&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,768&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ref&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✓&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OR 조건&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;147.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;149.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;mysql84&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Tuple IN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;148.0&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;148.6&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1,700,255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;ALL&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✗&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/details&gt;
&lt;hr&gt;
&lt;h2 id=&quot;실무-권고&quot;&gt;실무 권고&lt;/h2&gt;
&lt;h3 id=&quot;postgresql-사용자&quot;&gt;PostgreSQL 사용자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;편한 문법을 쓰면 된다.&lt;/strong&gt; 플래너가 BitmapOr로 최적화한다.&lt;/li&gt;
&lt;li&gt;“OR은 느리니 UNION으로 분리해라”는 조언을 맹목적으로 따를 필요 없다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;work_mem&lt;/code&gt;이 너무 작으면 Lossy 비트맵이 많아져 Recheck 비용이 증가할 수 있다. 대규모 쿼리에서 &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS)&lt;/code&gt;의 &lt;code&gt;Heap Blocks: lossy=&lt;/code&gt;를 확인하자.&lt;/li&gt;
&lt;li&gt;PG 13 이상이면 모두 동일하게 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;mysql-사용자&quot;&gt;MySQL 사용자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;서로 다른 컬럼에 대한 OR/IN이 &lt;strong&gt;2개 이상&lt;/strong&gt;이면 &lt;strong&gt;반드시 UNION ALL로 분리&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;이 제약은 MySQL 5.7 → 8.0 → 8.4에서 &lt;strong&gt;변하지 않았다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ORM 생성 쿼리를 확인하라. 자동으로 OR을 생성하고 있다면 raw query로 교체.&lt;/li&gt;
&lt;li&gt;IN이 OR보다 안전하다는 생각도 버리자. 둘 다 동일하게 퇴화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;책의-조언은-틀렸나&quot;&gt;책의 조언은 틀렸나?&lt;/h3&gt;
&lt;p&gt;“SQL 레벨업”의 UNION 권장은 &lt;strong&gt;범용적으로 안전한 조언&lt;/strong&gt;이다. MySQL에서는 여전히 유효하고, Oracle에서도 비슷한 제약이 있을 수 있다.&lt;/p&gt;
&lt;p&gt;다만 PostgreSQL이라면 이미 자유롭다. 불필요하게 UNION ALL로 쿼리를 복잡하게 만들 필요 없다. &lt;strong&gt;쓰는 DB의 옵티마이저를 이해하고, EXPLAIN으로 확인하는 습관&lt;/strong&gt;이 가장 중요하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;참고-자료&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/indexes-bitmap-scans.html&quot;&gt;PostgreSQL Docs, Combining Multiple Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doxygen.postgresql.org/tidbitmap_8c_source.html&quot;&gt;PostgreSQL Source, tidbitmap.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html&quot;&gt;MySQL Docs, Index Merge Optimization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>postgresql</category><category>mysql</category><category>benchmark</category><category>sql</category><author>koa (김신건)</author></item><item><title>IaC 튜토리얼 (1)</title><link>https://shinkeonkim.com/posts/iac-tutorial/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/iac-tutorial/</guid><description>IaC(Infrastructure as Code) 개념과 도구들을 소개하는 튜토리얼</description><pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;iac란&quot;&gt;IaC란?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
한줄 정리
서버·네트워크·DB 같은 인프라를 사람이 손으로 클릭해 만드는 대신, 버전 관리되는 코드(설정 파일)
로 정의하고 자동으로 프로비저닝하는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;어떻게 만들지(절차)가 아니라 무엇을 원하는지(상태)를 적는 선언형 방식이 IaC의 주류다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/uploads/20260611172849-makqga-image.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;요약-정리&quot;&gt;요약 정리&lt;/h3&gt;
&lt;p&gt;IaC는 인프라의 &lt;strong&gt;“원하는 상태(desired state)”&lt;/strong&gt; 를 텍스트 파일로 기술하고, 도구가 그 정의대로 실제 클라우드/온프레미스 자원을 만들고 변경·삭제하도록 하는 운영 패러다임입니다.&lt;/p&gt;
&lt;p&gt;콘솔에서 수동으로 만든 자원은 누가/언제/무엇을 바꿨는지 추적이 어렵고, 재현이 불가능합니다. IaC는 이 과정을 코드로 옮겨 Git으로 이력을 남기고, 코드 리뷰를 거치고, 동일한 정의로 몇 번이든 같은 환경을 재생산할 수 있습니다.&lt;/p&gt;
&lt;p&gt;결과적으로 인프라가 소프트웨어 개발의 모범 사례(버전 관리·테스트·CI/CD· 리뷰)를 그대로 누리게 됩니다.&lt;/p&gt;
&lt;h3 id=&quot;코드로-정의하면-따라오는-것들&quot;&gt;코드로 정의하면 따라오는 것들&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Single Source of Truth : 실제 인프라의 구조는 코드 저장소가 말해줍니다.&lt;/li&gt;
&lt;li&gt;리뷰 가능성: 인프라 변경이 PR로 들어와, 동료의 검토를 받습니다. (이제 AI의 작성과 리뷰도 받을 수 있습니다.)&lt;/li&gt;
&lt;li&gt;자동화: apply 한번으로 수십 개의 자원을 일관되게 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;iac의-이점&quot;&gt;IaC의 이점&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;이점&lt;/th&gt;&lt;th&gt;무엇을 해결하는가?&lt;/th&gt;&lt;th&gt;구체적인 효과&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;재현성 / 멱등성&lt;/td&gt;&lt;td&gt;환경 간 불일치, 재해 복구&lt;/td&gt;&lt;td&gt;동일 코드로 dev-alpha-production 등의 환경에 동일하게 생성 가능. 여러 번 apply해도 결과가 동일함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;버전 관리&lt;/td&gt;&lt;td&gt;”누가 이걸 지웠는가?”&lt;/td&gt;&lt;td&gt;Git Diff로 변경 추적이 가능함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;협업 / 리뷰&lt;/td&gt;&lt;td&gt;사일로화된 인프라 지식&lt;/td&gt;&lt;td&gt;인프라 변경이 코드 리뷰 대상이 되어 지식이 팀에 공유된다.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;속도 / 자동화&lt;/td&gt;&lt;td&gt;느린 수동 프로피저닝&lt;/td&gt;&lt;td&gt;CI/CD 파이프라인에서 plan / apply를 자동 실행&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;일관성&lt;/td&gt;&lt;td&gt;휴먼 에러, Contribution Drift&lt;/td&gt;&lt;td&gt;클릭 실수 제거, 정책을 코드로 강제할 수 있음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;문서화&lt;/td&gt;&lt;td&gt;오래된 위키&lt;/td&gt;&lt;td&gt;코드 자체가 항상 최신 인프라를 명세하는 명세서 역할을 함&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;비용 / 보안 가시성&lt;/td&gt;&lt;td&gt;유령 자원, 미흡한 점검&lt;/td&gt;&lt;td&gt;정적 분석 (tfsec, Checkov) 으로 배포 전에 위험, 낭비를 탐지할 수 있음&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;IaC에서 멱등성은 매우 중요합니다. 명령형 스크립트를 통해 인프라 생성시, 단순하게 두 번 실행하면 자원을 2개 만들어낼 수 있습니다. 선언형 IaC는 “이미 원하는 상태” 라면 아무 일도 하지 않습니다. 이 성질로 “안전하고 다시 돌릴 수 있는” 자동화를 가능하게 만들어줍니다.&lt;/p&gt;
&lt;h2 id=&quot;명령형-vs-선언형-그리고-state에-대해&quot;&gt;명령형 vs 선언형 그리고 state에 대해&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/uploads/20260611173623-yu9tr8-image.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;명령형imperative&quot;&gt;명령형(imperative)&lt;/h4&gt;
&lt;p&gt;“EC2 인스턴스를 생성하라 → 보안그룹을 붙여라 → 태그를 달아라”처럼 수행할 단계를 순서대로 기술합니다.
(예: 셸 스크립트, AWS CLI 나열) 다시 실행하면 중복 생성·오류가 날 수 있어 멱등하지 않습니다.&lt;/p&gt;
&lt;h4 id=&quot;선언형declarative&quot;&gt;선언형(declarative)&lt;/h4&gt;
&lt;p&gt;“t3.micro EC2가 1대, 이 보안그룹과 함께, 이 태그로 존재해야 한다”처럼 결과만 기술합니다.
도구가 현재와 비교해 필요한 동작을 스스로 도출합니다. (ex. Terraform, CloudFormation, Kubernetes manifest)&lt;/p&gt;
&lt;h3 id=&quot;선언형-iac-도구가-동작하는-방식&quot;&gt;선언형 IaC 도구가 동작하는 방식&lt;/h3&gt;
&lt;p&gt;선언형 도구는 세 가지를 비교합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;코드에 적힌 목표 상태(desired)&lt;/li&gt;
&lt;li&gt;도구가 기억하는 마지막 상태(state file)&lt;/li&gt;
&lt;li&gt;클라우드의 실제 상태(real)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;plan 단계에서 이 셋을 대조하여 “무엇을 만들고/바꾸고/지울지”를 실행 계획으로 보여줍니다. apply 단계에서 그 계획만큼만 실제 API를 호출합니다.
코드와 실제가 어긋나는 현상을 드리프트(drift)라 하며, 도구는 이를 감지해 다시 코드 기준으로 맞춰 줍니다. 이 “목표-현재 차이 계산” 모델이 IaC가 멱등하고 안전한 이유입니다.&lt;/p&gt;</content:encoded><category>tutorial</category><category>IaC</category><author>koa (김신건)</author></item><item><title>멀티 패러다임 프로그래밍 책을 읽으며 끄적이기</title><link>https://shinkeonkim.com/posts/books/multi-paradigm-programming/multi-paradigm-programming-note/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/books/multi-paradigm-programming/multi-paradigm-programming-note/</guid><pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;각-챕터별-인상적이거나-기억에-남기고-싶은-구문&quot;&gt;각 챕터별 인상적이거나 기억에 남기고 싶은 구문&lt;/h2&gt;
&lt;h3 id=&quot;추천의-말&quot;&gt;추천의 말&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“구조의 문제는 객체지향으로, 로직의 문제는 함수형으로” (p.6)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1장---멀티패러다임이-현대-언어를-확장하는-방법&quot;&gt;1장 - 멀티패러다임이 현대 언어를 확장하는 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ArrayLike의 이터레이터에서의 reverse 구현&lt;/li&gt;
&lt;li&gt;“언어를 만든 사람이 아닌, 언어의 사용자가 언어를 발전시킨 것입니다.” (p.48)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;지연-평가&quot;&gt;지연 평가&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;필요한 시점까지 계산을 미루어 메모리 사용량과 연산 비용을 쥴여 향상하는 기법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;generator&quot;&gt;generator&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;이터레이터를 생성하는 문법적 장치로서 명령형 스타일로도 Iteration 로직을 구현할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;function*, yield 키워드를 사용하여 값을 하나씩 반환함으로써 지연 평가가 필요한 로직을 간결하고 명확하게 표현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;iteration에-대한-정리&quot;&gt;Iteration에 대한 정리&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Iterator: &lt;code&gt;{ value: T, done: boolean }&lt;/code&gt; 객체를 반환하는 next() 메서드를 가진 객체&lt;/li&gt;
&lt;li&gt;Iterable: Iterator를 반환하는 &lt;code&gt;[Symbol.iterator]()&lt;/code&gt; 메서드를 가진 값&lt;/li&gt;
&lt;li&gt;IterableIterator: Iterator이자 Iterable인 값&lt;/li&gt;
&lt;li&gt;Iteration Protocol: Iterable을 for … of 문, 전개 연산자 등과 함께 동작하도록 하는 규약&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;인터페이스와-클래스-상속&quot;&gt;인터페이스와 클래스 상속&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;인터페이스는 규약을 제시하여 다양한 클래스가 동일한 형식의 동작을 구현하도록 유도합니다.&lt;/li&gt;
&lt;li&gt;상속은 공통 기능을 직접 구현한 뒤, 적절히 확장하는 데 초점을 둡니다.&lt;/li&gt;
&lt;li&gt;인터페이스는 언어나 표준 라이브러리 설계 단계에서 주로 사용됩니다.&lt;/li&gt;
&lt;li&gt;상속은 주로 SDK나 애플리케이션 레벨에서 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2장---함수형-프로그래밍과-타입-시스템-그리고-lisp&quot;&gt;2장 - 함수형 프로그래밍과 타입 시스템 그리고 LISP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;아래와 같은 함수 타입 별칭을 정의할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; subtract&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;subtract&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;이터레이션-프로토콜-복습-체크리스트-내용&quot;&gt;이터레이션 프로토콜 복습 체크리스트 내용&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;for … of 문으로 순회할 수 있는 값은 오직 Iterable입니다.&lt;/li&gt;
&lt;li&gt;전개 연산자로 배열을 만들 수 있는 값은 오직 Iterable입니다.&lt;/li&gt;
&lt;li&gt;IterableIterator를 만드는 함수를 구현할 때 반환값을 &lt;code&gt;{ next() {...}, [Symbol.Iterator]() {...} }&lt;/code&gt; 형식으로 구현할 수 있으며, 이 객체는 이터레이터이자 동시에 이터러블입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;map을-generator와-iterable-패턴으로-구현하기&quot;&gt;Map을 Generator와 Iterable 패턴으로 구현하기&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;iterable&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Iterable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IterableIterator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iterable) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    yield&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 예제 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; arr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;2&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;3&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; mapped&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; parseInt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(x), arr);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; array2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;mapped];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(array2); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// [1, 2, 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 예제 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toUpperCase&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(head); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// &apos;A&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;23&quot;&gt;2.3&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Meta Programming 이란 프로그램이 자기 자신이나 다른 프로그램을 데이터처럼 바라보며 분석, 번형, 생성하거나 실행하는 프로그래밍 기법을 의미한다.&lt;/li&gt;
&lt;li&gt;LISP의 가장 큰 특징: “코드가 데이터이고, 데이터가 코드이다”라는 개념&lt;/li&gt;
&lt;/ul&gt;</content:encoded><author>koa (김신건)</author></item><item><title>CodeWithOutput 컴포넌트 데모</title><link>https://shinkeonkim.com/posts/code-with-output-showcase/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/code-with-output-showcase/</guid><description>CodeWithOutput 컴포넌트가 지원하는 모든 사용 패턴, 단일/다중 STDIN·STDOUT, 다중 언어 변형, 라벨/타이틀, 반응형, 엣지 케이스까지 한 번에 확인할 수 있도록 모아둔 데모 페이지.</description><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 &lt;code&gt;&amp;#x3C;CodeWithOutput&gt;&lt;/code&gt; 컴포넌트의 모든 표현 가능한 형태를 한 화면에서 확인하기 위한 데모입니다.
&lt;code&gt;&amp;#x3C;!-- mdx-component: CodeWithOutput --&gt;&lt;/code&gt; 는 &lt;code&gt;astro-auto-import&lt;/code&gt; 로 글로벌 등록되어 있어 글마다 import 문을 추가할 필요가 없습니다.
새 케이스가 추가될 때마다 이 페이지에 예시를 같이 추가하면 미리보기·실제 빌드·반응형 동작을 한 번에 점검할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;1-기본-코드--결과&quot;&gt;1. 기본, 코드 + 결과&lt;/h2&gt;
&lt;p&gt;가장 단순한 형태입니다. 코드 한 덩어리 + 결과 한 덩어리.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;2-stdin--stdout-한-쌍&quot;&gt;2. STDIN + STDOUT 한 쌍&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; 을 함께 넘기면 결과 패널 위에 stdin 섹션이 추가됩니다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;3-여러-테스트-케이스-cases&quot;&gt;3. 여러 테스트 케이스 (&lt;code&gt;cases&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;알고리즘 문제처럼 입력·출력 쌍이 여러 개 필요한 경우 &lt;code&gt;cases&lt;/code&gt; 배열을 사용합니다.
모든 케이스가 한 번에 보이도록 세로로 쌓이고, 케이스 사이는 굵은 경계선으로 구분합니다.
라벨을 생략하면 &lt;code&gt;예제 N&lt;/code&gt; 으로 자동 부여됩니다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;4-사용자-지정-케이스-라벨&quot;&gt;4. 사용자 지정 케이스 라벨&lt;/h2&gt;
&lt;p&gt;각 케이스에 &lt;code&gt;label&lt;/code&gt; 을 명시하면 자동 번호 대신 사용자 라벨이 보입니다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;5-다중-언어-코드-variants&quot;&gt;5. 다중 언어 코드 (&lt;code&gt;variants&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;같은 문제를 여러 언어로 풀이할 때 &lt;code&gt;variants&lt;/code&gt; 로 코드 패널에 탭을 만들 수 있습니다.
오른쪽 패널의 결과는 모든 변형이 공유합니다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;6-다중-언어--다중-케이스&quot;&gt;6. 다중 언어 + 다중 케이스&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;variants&lt;/code&gt; 와 &lt;code&gt;cases&lt;/code&gt; 는 함께 쓸 수 있습니다. 코드는 탭으로, 케이스는 세로 stack 으로.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;7-케이스마다-입력-유무가-섞여-있을-때&quot;&gt;7. 케이스마다 입력 유무가 섞여 있을 때&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cases&lt;/code&gt; 안에서 어떤 케이스는 입력이 있고 어떤 케이스는 없는 경우. 각 케이스는 독립적으로 stdin 섹션을 표시/숨김.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;8-타이틀과-커스텀-라벨&quot;&gt;8. 타이틀과 커스텀 라벨&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;outputLabel&lt;/code&gt;, 케이스별 &lt;code&gt;inputLabel&lt;/code&gt; / &lt;code&gt;outputLabel&lt;/code&gt; 까지 모두 지정한 풀 옵션 예시.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;9-좌우-비율-조절-codewidth&quot;&gt;9. 좌우 비율 조절 (&lt;code&gt;codeWidth&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;codeWidth&lt;/code&gt; prop 으로 초기 좌측 코드 패널 너비를 조절할 수 있습니다 (사용자가 splitter 를 드래그하면 localStorage 에 저장됨).&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;10-긴-코드--긴-출력-스크롤-확인&quot;&gt;10. 긴 코드 / 긴 출력 (스크롤 확인)&lt;/h2&gt;
&lt;p&gt;코드와 출력이 패널 높이를 넘으면 각 패널 내부에서 독립 스크롤됩니다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;11-긴-한-줄-가로-스크롤-확인&quot;&gt;11. 긴 한 줄 (가로 스크롤 확인)&lt;/h2&gt;
&lt;p&gt;긴 단일 라인은 패널 안에서 가로 스크롤이 동작해야 합니다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;12-다양한-언어-하이라이팅&quot;&gt;12. 다양한 언어 하이라이팅&lt;/h2&gt;
&lt;p&gt;shiki 가 인식 가능한 언어라면 자동으로 색상이 적용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“rust”
label=“Rust”
outputLanguage=“text”
code={&lt;code&gt;fn main() {\n    let v: Vec&amp;#x3C;i32&gt; = (1..=5).collect();\n    let sum: i32 = v.iter().sum();\n    println!(&quot;sum = {}&quot;, sum);\n}&lt;/code&gt;}
output={&lt;code&gt;sum = 15&lt;/code&gt;}
/&gt;&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h2 id=&quot;확인-포인트&quot;&gt;확인 포인트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;좌우 splitter 드래그로 비율 조절이 동작하는가&lt;/li&gt;
&lt;li&gt;더블클릭 시 50% 로 리셋되는가&lt;/li&gt;
&lt;li&gt;화살표 키로 미세 조정되는가&lt;/li&gt;
&lt;li&gt;모바일 폭 (640px 이하) 에서 splitter 가 사라지고 세로 stack 으로 전환되는가&lt;/li&gt;
&lt;li&gt;다크/라이트 모드 전환 시 코드 색상이 따라가는가&lt;/li&gt;
&lt;li&gt;다중 케이스에서 케이스 간 경계선이 명확한가&lt;/li&gt;
&lt;li&gt;미리보기 (에디터) 와 실제 렌더링이 시각적으로 동일한가&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>meta</category><category>demo</category><author>koa (김신건)</author></item><item><title>[Leetcode] 192 Word Frequency</title><link>https://shinkeonkim.com/posts/problem-solving/leetcode/shell/192-word-frequency/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/problem-solving/leetcode/shell/192-word-frequency/</guid><description>Leetcode 192번 문제 풀이입니다.</description><pubDate>Mon, 25 May 2026 09:35:44 GMT</pubDate><content:encoded>&lt;h2 id=&quot;문제-요약-및-풀이&quot;&gt;문제 요약 및 풀이&lt;/h2&gt;
&lt;p&gt;file.txt가 주어질 때, 각 단어의 빈도수를 계산하여 단어와 빈도수를 출력하는 한 줄짜리 [[Shell Script]]를 작성해야 한다.&lt;/p&gt;
&lt;p&gt;이때 빈도수에 따라 내림차순으로 정렬해야 하고, 빈도수가 같은 경우는 주어지지 않는다고 가정한다.&lt;/p&gt;
&lt;p&gt;스크립트로 작성하려면 어떻게 해야하나 고민하다가 sort + uniq -c 조합으로 단어의 빈도수를 계산 할 수 있다고 생각했고, 실행해보았다.
근데 빈도수의 오름차순으로 정렬이 되어서 sort -r 옵션을 추가해서 내림차순으로 정렬하도록 했다.&lt;/p&gt;
&lt;h2 id=&quot;풀이-코드&quot;&gt;풀이 코드&lt;/h2&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;</content:encoded><category>Leetcode</category><author>koa (김신건)</author></item><item><title>[Leetcode] 195 Tenth Line</title><link>https://shinkeonkim.com/posts/problem-solving/leetcode/shell/195-tenth-line/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/problem-solving/leetcode/shell/195-tenth-line/</guid><description>Leetcode 195번 문제 풀이입니다.</description><pubDate>Mon, 25 May 2026 09:14:23 GMT</pubDate><content:encoded>&lt;h2 id=&quot;문제-요약-및-풀이&quot;&gt;문제 요약 및 풀이&lt;/h2&gt;
&lt;p&gt;file.txt가 주어질 때, 정확하게 10번째 줄에 있는 텍스트를 출력하는 한 줄짜리 [[Shell Script]]를 작성해야 한다.&lt;/p&gt;
&lt;p&gt;처음에는 head와 tail로 바로 간단하게 할 수 있을 것 같았다.&lt;/p&gt;
&lt;p&gt;아래와 같이 10줄 미만의 입력이 주어지면 정확하게 10번째 라인이 아닌 마지막 라인을 출력하게 됨을 놓쳤다.&lt;/p&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;이를 수정하기 위해서 sed를 사용했고, 다음과 같은 풀이를 작성했다.&lt;/p&gt;
&lt;h2 id=&quot;풀이-코드&quot;&gt;풀이 코드&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sed&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;10p&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>Leetcode</category><author>koa (김신건)</author></item><item><title>[Leetcode] 193 Valid Phone Numbers</title><link>https://shinkeonkim.com/posts/problem-solving/leetcode/shell/193-valid-phone-numbers/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/problem-solving/leetcode/shell/193-valid-phone-numbers/</guid><description>Leetcode 193번 문제 풀이입니다.</description><pubDate>Mon, 25 May 2026 08:14:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;문제-요약-및-풀이&quot;&gt;문제 요약 및 풀이&lt;/h2&gt;
&lt;p&gt;file.txt가 주어질 때, (xxx) xxx-xxxx or xxx-xxx-xxxx 형태의 전화번호를 모두 출력하는 한 줄짜리 [[Shell Script]]를 작성해야 한다.&lt;/p&gt;
&lt;p&gt;신경써야했던 부분은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;괄호가 있는 경우와 없는 경우의 두 케이스를 구분해야 한다.&lt;/li&gt;
&lt;li&gt;앞, 뒤에 의미없는 문자들이 들어오는 케이스가 있을 수 있다.&lt;/li&gt;
&lt;li&gt;숫자가 아닌 문자들이 들어오는 케이스가 있을 수 있다.&lt;/li&gt;
&lt;li&gt;각 숫자의 개수도 정확히 맞아야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;풀이-코드&quot;&gt;풀이 코드&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -E&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;(^[0-9]{3}-[0-9]{3}-[0-9]{4}$)|^(\([0-9]{3}\) [0-9]{3}-[0-9]{4}$)&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>Leetcode</category><author>koa (김신건)</author></item><item><title>캐시 적용이 적절한 상황</title><link>https://shinkeonkim.com/posts/2026-05-24-cache-pros/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/2026-05-24-cache-pros/</guid><description>캐시 적용이 적절한 상황에 대해</description><pubDate>Sun, 24 May 2026 09:26:00 GMT</pubDate><content:encoded>&lt;p&gt;다음과 같은 조건이라면 캐시를 적용해보자&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;원본 데이터 저장소에서 원하는 데이터를 찾기 위해 검색하는 시간이 오래 걸린다.&lt;/li&gt;
&lt;li&gt;원본 데이터 저장소에서 매번 계산을 통해 데이터를 가져와야 한다.&lt;/li&gt;
&lt;li&gt;캐시에서 데이터를 가져오는 것이 원본 데이터 저장소를 통해 요청하는 것보다 빨라야 한다.&lt;/li&gt;
&lt;li&gt;캐시에 저장된(저장할) 데이터가 잘 변하지 않는 데이터다.&lt;/li&gt;
&lt;li&gt;캐시에 저장된 데이터는 자주 검색되는 데이터다.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><author>koa (김신건)</author></item><item><title>마크다운 문법 종합 테스트</title><link>https://shinkeonkim.com/posts/markdown-kitchen-sink/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/markdown-kitchen-sink/</guid><description>Astro + remark 플러그인 조합에서 어떤 마크다운 문법이 동작하는지 정리한 데모 페이지.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 글은 새 블로그에서 지원하는 마크다운 문법을 한눈에 확인하기 위한 페이지입니다.&lt;/p&gt;
&lt;h2 id=&quot;1-기본-텍스트-강조&quot;&gt;1. 기본 텍스트 강조&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;굵게&lt;/strong&gt; · &lt;em&gt;기울임&lt;/em&gt; · &lt;em&gt;&lt;strong&gt;굵게+기울임&lt;/strong&gt;&lt;/em&gt; · &lt;del&gt;취소선&lt;/del&gt; · &lt;code&gt;인라인 코드&lt;/code&gt; · &lt;u&gt;밑줄(HTML)&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;자동 링크: &lt;a href=&quot;https://astro.build&quot;&gt;https://astro.build&lt;/a&gt;
일반 링크: &lt;a href=&quot;https://docs.astro.build&quot;&gt;Astro 공식 문서&lt;/a&gt;
위키 링크: [[Astro]] · 별칭 [[Astro|Astro JS]] · 깨진 링크 [[존재하지않는위키]]&lt;/p&gt;
&lt;h2 id=&quot;2-헤딩-계층&quot;&gt;2. 헤딩 계층&lt;/h2&gt;
&lt;h3 id=&quot;3단계-헤딩&quot;&gt;3단계 헤딩&lt;/h3&gt;
&lt;h4 id=&quot;4단계-헤딩&quot;&gt;4단계 헤딩&lt;/h4&gt;
&lt;h5 id=&quot;5단계-헤딩&quot;&gt;5단계 헤딩&lt;/h5&gt;
&lt;h6 id=&quot;6단계-헤딩&quot;&gt;6단계 헤딩&lt;/h6&gt;
&lt;h2 id=&quot;3-인용과-콜아웃&quot;&gt;3. 인용과 콜아웃&lt;/h2&gt;
&lt;p&gt;평범한 블록 인용:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“단순함이 궁극의 정교함이다.”, 레오나르도 다 빈치&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub 스타일 콜아웃 (5종):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
정보, 가볍게 알아두면 좋은 부가 설명을 적습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
팁, 더 쉽게 일을 처리하기 위한 조언입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
중요, 목표 달성에 꼭 필요한 핵심 정보입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
경고, 문제를 피하기 위해 즉시 주의해야 할 내용입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
주의, 특정 행동으로 인한 위험이나 부정적 결과를 알립니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;4-리스트&quot;&gt;4. 리스트&lt;/h2&gt;
&lt;p&gt;순서 없는 리스트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;첫 번째 항목&lt;/li&gt;
&lt;li&gt;두 번째 항목
&lt;ul&gt;
&lt;li&gt;중첩 항목&lt;/li&gt;
&lt;li&gt;다른 중첩
&lt;ul&gt;
&lt;li&gt;더 깊은 중첩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세 번째 항목&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;순서 있는 리스트:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;패키지 설치&lt;/li&gt;
&lt;li&gt;설정 추가&lt;/li&gt;
&lt;li&gt;빌드 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;체크리스트 (GFM):&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; Astro 프로젝트 셋업&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; BrainDB 대체 remark 플러그인 작성&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 다크/라이트 모드 구현&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 1만 글 마이그레이션&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-코드-블록&quot;&gt;5. 코드 블록&lt;/h2&gt;
&lt;p&gt;인라인 코드: &lt;code&gt;const greeting = &quot;안녕&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;TypeScript:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  email&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/api/users/${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response.ok) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dataclasses &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dataclass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; typing &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Optional&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@dataclass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    name: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    email: Optional[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(id: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) -&gt; Optional[User]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; httpx.AsyncClient() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.get(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/api/users/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{id}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.status_code &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.json()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;data)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bash:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -euo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pipefail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; file &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; src/content/posts/*.md&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Processing &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$file&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; markdownlint&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$file&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSON + diff + plain text:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;shinkeonkim-log&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; 안녕하세요&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt; 안녕하세요, 김신건입니다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;순수 텍스트 블록은 하이라이팅이 없습니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;어떤 언어인지 명시하지 않으면 이렇게 보입니다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;6-표&quot;&gt;6. 표&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;설명&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;상태&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;다크/라이트 모드&lt;/td&gt;&lt;td&gt;localStorage + prefers-color-scheme&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;위키링크&lt;/td&gt;&lt;td&gt;자체 remark 플러그인&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;그래프 뷰&lt;/td&gt;&lt;td&gt;D3 force simulation&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;통합 검색&lt;/td&gt;&lt;td&gt;Pagefind 빌드 후 인덱싱&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이지네이션&lt;/td&gt;&lt;td&gt;Astro &lt;code&gt;paginate()&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;정렬 옵션:&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;왼쪽 정렬&lt;/th&gt;&lt;th align=&quot;center&quot;&gt;가운데 정렬&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;오른쪽 정렬&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;left&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;center&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;right&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;짧은 텍스트&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;중간 길이 텍스트&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;더 긴 텍스트&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;7-이미지&quot;&gt;7. 이미지&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;public/&lt;/code&gt; 디렉토리에 둔 이미지 (절대 경로):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/icon-192.png&quot; alt=&quot;사이트 아바타 (public/icon-192.png)&quot;&gt;&lt;/p&gt;
&lt;p&gt;리모트 이미지:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://astro.build/press/astro-logo-light.svg&quot; alt=&quot;Astro 로고&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;8-푸트노트&quot;&gt;8. 푸트노트&lt;/h2&gt;
&lt;p&gt;푸트노트도 지원합니다&lt;sup&gt;&lt;a href=&quot;#user-content-fn-astro-note&quot; id=&quot;user-content-fnref-astro-note&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. 본문이 길어질 때 출처를 정리할 때 유용합니다&lt;sup&gt;&lt;a href=&quot;#user-content-fn-korean&quot; id=&quot;user-content-fnref-korean&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&quot;9-구분선과-줄바꿈&quot;&gt;9. 구분선과 줄바꿈&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;위는 구분선입니다. 줄바꿈 두 칸으로&lt;br&gt;
강제 줄바꿈도 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;10-html-직접-삽입&quot;&gt;10. HTML 직접 삽입&lt;/h2&gt;
&lt;p&gt;마크다운 안에서 필요할 때 HTML 을 직접 쓸 수 있습니다:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;접었다 펼치는 영역&lt;/summary&gt;
&lt;p&gt;이 안에 마크다운은 동작하지 않습니다 (raw HTML). 그래도 텍스트와 일반 HTML 은 잘 들어갑니다.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;&lt;kbd&gt;⌘&lt;/kbd&gt; + &lt;kbd&gt;K&lt;/kbd&gt; 같은 단축키 표기도 가능합니다.&lt;/p&gt;
&lt;h2 id=&quot;11-코드--실행-결과-패널-codewithoutput&quot;&gt;11. 코드 + 실행 결과 패널 (&lt;code&gt;&amp;#x3C;CodeWithOutput&gt;&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;좌측에 입력 코드, 우측에 그 결과를 나란히 보여주는 컴포넌트. 화면이 좁아지면 자동으로 위아래로 쌓입니다 (lg breakpoint 기준). 실제로 실행하는 게 아니라 글쓴이가 결과를 직접 적어 둡니다, 그래서 셸, REPL, DB CLI, HTTP API 등 &lt;strong&gt;어떤 환경의 입출력이든&lt;/strong&gt; 그대로 옮길 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;111-bash&quot;&gt;11.1 Bash&lt;/h3&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;112-redis-cli&quot;&gt;11.2 Redis CLI&lt;/h3&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;113-postgresql-psql&quot;&gt;11.3 PostgreSQL (psql)&lt;/h3&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;114-http-curl--json-응답&quot;&gt;11.4 HTTP, curl + JSON 응답&lt;/h3&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;h3 id=&quot;115-python-repl&quot;&gt;11.5 Python REPL&lt;/h3&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“python”
label=“python”
outputLanguage=“text”
outputLabel=”&gt;&gt;&gt;”
title=“dataclass 한 입”
code={`from dataclasses import dataclass&lt;/p&gt;
&lt;p&gt;@dataclass
class Note:
id: str
body: str&lt;/p&gt;
&lt;p&gt;n = Note(id=“2026-05-23”, body=“hello”)
print(n)
print(n.body.upper())&lt;code&gt;}   output={&lt;/code&gt;Note(id=‘2026-05-23’, body=‘hello’)
HELLO`}
/&gt;&lt;/p&gt;
&lt;h3 id=&quot;116-멀티-언어-탭-같은-출력-여러-구현&quot;&gt;11.6 멀티 언어 탭, 같은 출력, 여러 구현&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;variants&lt;/code&gt; prop 에 언어별 구현을 나열하면 좌측에 탭으로 묶이고, 우측 결과는 공유됩니다. “같은 문제를 어떤 언어로도 풀 수 있다” 를 보여줄 때 유용합니다.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
outputLanguage=“text”
outputLabel=“stdout”
title=“피보나치 첫 10개”
variants={[
{
label: ‘python’,
language: ‘python’,
code: `def fib(n):
a, b = 0, 1
out = []
for _ in range(n):
out.append(a)
a, b = b, a + b
return out&lt;/p&gt;
&lt;p&gt;print(fib(10))&lt;code&gt;,     },     {       label: &apos;typescript&apos;,       language: &apos;ts&apos;,       code: &lt;/code&gt;function fib(n: number): number[] {
const out: number[] = [];
let [a, b] = [0, 1];
for (let i = 0; i &amp;#x3C; n; i++) {
out.push(a);
[a, b] = [b, a + b];
}
return out;
}&lt;/p&gt;
&lt;p&gt;console.log(fib(10));&lt;code&gt;,     },     {       label: &apos;rust&apos;,       language: &apos;rust&apos;,       code: &lt;/code&gt;fn fib(n: usize) -&gt; Vec&lt;u64&gt; {
let mut out = Vec::with_capacity(n);
let (mut a, mut b) = (0u64, 1u64);
for _ in 0..n {
out.push(a);
let next = a + b;
a = b;
b = next;
}
out
}&lt;/u64&gt;&lt;/p&gt;
&lt;p&gt;fn main() {
println!(”{:?}”, fib(10));
}&lt;code&gt;,     },     {       label: &apos;go&apos;,       language: &apos;go&apos;,       code: &lt;/code&gt;package main&lt;/p&gt;
&lt;p&gt;import “fmt”&lt;/p&gt;
&lt;p&gt;func fib(n int) []int {
out := make([]int, 0, n)
a, b := 0, 1
for i := 0; i &amp;#x3C; n; i++ {
out = append(out, a)
a, b = b, a+b
}
return out
}&lt;/p&gt;
&lt;p&gt;func main() {
fmt.Println(fib(10))
}&lt;code&gt;,     },   ]}   output={&lt;/code&gt;[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]`}
/&gt;&lt;/p&gt;
&lt;h3 id=&quot;117-너비-조절&quot;&gt;11.7 너비 조절&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;codeWidth&lt;/code&gt; prop 으로 좌측 코드 패널 너비(%)를 정합니다. 사용자는 가운데 핸들을 &lt;strong&gt;드래그&lt;/strong&gt; (또는 화살표 키) 로 임의 조정할 수 있고, 너비가 100% 가 되면 자동으로 상하 배치로 전환됩니다. 조정값은 컴포넌트 단위로 localStorage 에 저장됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;더블클릭 / Enter: 50% 로 리셋&lt;/li&gt;
&lt;li&gt;화살표 키: 2% 씩 (Shift+화살표: 10%)&lt;/li&gt;
&lt;li&gt;End 키: 100% (상하 배치)&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- mdx-component: CodeWithOutput --&gt;
&lt;p&gt;&amp;#x3C;CodeWithOutput
language=“bash”
label=”$ bash”
outputLanguage=“text”
outputLabel=“stdout”
title=“긴 코드, 코드 영역 넓게 (codeWidth=75)”
codeWidth={75}
code={&lt;code&gt;# 디스크 사용량 큰 디렉토리 상위 5개 du -h --max-depth=1 . 2&gt;/dev/null \\   | sort -hr \\   | head -6 \\   | awk &apos;NR&gt;1 {printf &quot;%-8s %s\\n&quot;, $1, $2}&apos;&lt;/code&gt;}
output={&lt;code&gt;189M    ./dist 42M     ./node_modules/.bin 12M     ./node_modules/three 8.4M    ./src 2.1M    ./public 1.2M    ./_posts&lt;/code&gt;}
/&gt;&lt;/p&gt;
&lt;h2 id=&quot;12-mermaid-다이어그램&quot;&gt;12. Mermaid 다이어그램&lt;/h2&gt;
&lt;p&gt;```mermaid 코드 블록은 자동으로 &lt;a href=&quot;https://mermaid.js.org&quot;&gt;Mermaid&lt;/a&gt; 다이어그램으로 렌더링됩니다. CDN ESM 으로 lazy import 되므로 이 페이지에서만 ~수백KB 로드되고, 다른 글에는 부담이 없습니다. 다크/라이트 모드 토글 시 자동으로 다시 그려집니다.&lt;/p&gt;
&lt;h3 id=&quot;121-flowchart&quot;&gt;12.1 Flowchart&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A[글쓴이] --&gt;|마크다운 작성| B(.md / .mdx)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; C{remark 파이프라인}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|wikilink| D[[Wiki 페이지]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|callout| E[Alert]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt;|mermaid| F[다이어그램]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    D &amp;#x26; E &amp;#x26; F --&gt; G((정적 HTML))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    G --&gt;|Pagefind 인덱싱| H[검색 가능]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;122-sequence-빌드-파이프라인&quot;&gt;12.2 Sequence, 빌드 파이프라인&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Dev as 개발자&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant CI as GitHub Actions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Build as Astro Build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant PF as Pagefind&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Pages as GitHub Pages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Dev-&gt;&gt;CI: git push main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CI-&gt;&gt;Build: bun install + bun run build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Build-&gt;&gt;Build: remark plugins (wikilink/alert/mermaid)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Build-&gt;&gt;Build: Shiki + Content Layer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Build--&gt;&gt;CI: dist/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CI-&gt;&gt;PF: index dist/**/*.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PF--&gt;&gt;CI: dist/pagefind/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CI-&gt;&gt;Pages: upload artifact&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Pages--&gt;&gt;Dev: https://shinkeonkim.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;123-state-diagram-사이드-드로어-상태&quot;&gt;12.3 State diagram, 사이드 드로어 상태&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;stateDiagram-v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    [*] --&gt; Closed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Closed --&gt; Opening: 햄버거 클릭&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Opening --&gt; Open: transition 끝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Open --&gt; Closing: 백드롭/ESC/링크 클릭&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Closing --&gt; Closed: transition 끝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Open --&gt; Closed: viewport &gt;= md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;124-er-diagram&quot;&gt;12.4 ER diagram&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;erDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    POST ||--o{ TAG_LINK : has&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TAG ||--o{ TAG_LINK : tagged&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    POST {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        string slug PK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        string title&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        date published_at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TAG {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        string name PK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TAG_LINK {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        string post_slug FK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        string tag_name FK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;18-인터랙티브-애니메이션&quot;&gt;18. 인터랙티브 애니메이션&lt;/h2&gt;
&lt;p&gt;본문 중간에 dev-only 스튜디오 (&lt;code&gt;/_studio/&lt;/code&gt;) 에서 만든 애니메이션을 삽입할 수 있습니다.
전체 카탈로그: &lt;a href=&quot;https://shinkeonkim.com/animations/&quot;&gt;/animations/&lt;/a&gt; · 만들기 가이드: &lt;code&gt;manual-docs/animation-studio-guide.md&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;181-http-요청--응답&quot;&gt;18.1 HTTP 요청 / 응답&lt;/h3&gt;
&lt;p&gt;가장 기본적인 패턴, Client 가 Server 에 요청, Server 가 응답.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;182-redis-캐시-적중-hit&quot;&gt;18.2 Redis 캐시 적중 (HIT)&lt;/h3&gt;
&lt;p&gt;캐시에 데이터가 있어 빠르게 응답하는 경우. DB 조회가 없어서 ~6ms.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;183-redis-캐시-미스-miss&quot;&gt;18.3 Redis 캐시 미스 (MISS)&lt;/h3&gt;
&lt;p&gt;캐시에 없어서 DB 까지 다녀와야 하고, 이후 캐시도 갱신해야 함. ~72ms.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;184-db-primary-replica-복제&quot;&gt;18.4 DB Primary-Replica 복제&lt;/h3&gt;
&lt;p&gt;쓰기는 Primary 에만, Read replica 들에는 WAL 스트림으로 비동기 복제.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;185-load-balancer-round-robin&quot;&gt;18.5 Load Balancer Round-Robin&lt;/h3&gt;
&lt;p&gt;요청들을 서버 풀에 순회하며 분산.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;186-tcp-3-way-handshake&quot;&gt;18.6 TCP 3-way Handshake&lt;/h3&gt;
&lt;p&gt;SYN → SYN+ACK → ACK 로 연결 수립 (각 노드의 상태 전이 함께 표시).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;187-oauth-20-authorization-code-flow&quot;&gt;18.7 OAuth 2.0 Authorization Code Flow&lt;/h3&gt;
&lt;p&gt;User → App → Auth Server → token → API. 표준 OAuth 시퀀스.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;19-인터랙티브-퀴즈-quiz&quot;&gt;19. 인터랙티브 퀴즈 (Quiz)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;quiz&lt;/code&gt; 코드 fence 로 4 가지 유형의 문제를 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;title&quot;: &quot;이 데모 페이지 확인 문제&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;items&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;type&quot;: &quot;multiple-choice&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;question&quot;: &quot;이 블로그가 사용하는 정적 사이트 생성기는?&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;options&quot;: [&quot;Astro&quot;, &quot;Next.js&quot;, &quot;Hugo&quot;, &quot;Jekyll&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;correct&quot;: 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;explanation&quot;: &quot;Astro + MDX + GitHub Pages 로 배포됩니다.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;type&quot;: &quot;multiple-select&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;question&quot;: &quot;본 페이지에서 다룬 marks 다운 확장 문법은? (여러 개)&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;options&quot;: [&quot;위키링크&quot;, &quot;수식(KaTeX)&quot;, &quot;Mermaid 다이어그램&quot;, &quot;React 컴포넌트&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;correct&quot;: [0, 1, 2, 3],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;explanation&quot;: &quot;모두 지원됩니다.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;type&quot;: &quot;true-false&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;question&quot;: &quot;em-dash (\u2014) 는 이 블로그에서 허용되지 않는다.&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;correct&quot;: true,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;explanation&quot;: &quot;prebuild 단계에서 U+2014 사용 시 빌드가 실패합니다.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;type&quot;: &quot;short-answer&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;question&quot;: &quot;이 블로그가 검색 인덱스로 사용하는 도구 이름은?&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;correct&quot;: &quot;Pagefind&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;explanation&quot;: &quot;정적 사이트에 최적화된 클라이언트 사이드 검색 라이브러리입니다.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-astro-note&quot;&gt;
&lt;p&gt;Astro 5의 기본 remark 설정은 &lt;code&gt;remark-gfm&lt;/code&gt; 과 &lt;code&gt;remark-smartypants&lt;/code&gt; 를 포함합니다. &lt;a href=&quot;#user-content-fnref-astro-note&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-korean&quot;&gt;
&lt;p&gt;한국어 콘텐츠에서도 푸트노트 마커가 정상 동작합니다. &lt;a href=&quot;#user-content-fnref-korean&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>meta</category><category>markdown</category><category>demo</category><author>koa (김신건)</author></item><item><title>1만 개의 글에서도 빠르게: 블로그 빌드 성능 최적화 회고</title><link>https://shinkeonkim.com/posts/scaling-blog-to-10k-posts/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/scaling-blog-to-10k-posts/</guid><description>Astro 블로그가 1만 개 문서를 안전하게 다루기 위해 O(N²) 빌드 병목을 잡고 그래프/검색을 손본 과정과 교훈.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;새 블로그를 막 띄운 다음, 곧바로 한 가지가 신경 쓰였다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;지금은 글이 몇 개뿐이지만, 1만 개로 늘어나면 빌드와 검색이 버틸까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;오래 운영할 블로그라면 답은 “버텨야 한다” 다. 마침 더미 글을 잔뜩 만들어 빌드를 돌려본 김에, 어떤 함정에 빠졌고 어떻게 빠져나왔는지 기록해 둔다.&lt;/p&gt;
&lt;h2 id=&quot;어쩌다-1만-개를-측정하게-되었나&quot;&gt;어쩌다 1만 개를 측정하게 되었나&lt;/h2&gt;
&lt;p&gt;새 [[Astro]] 블로그에 다음 기능을 차례대로 붙였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;마크다운 + 위키링크 + 백링크&lt;/li&gt;
&lt;li&gt;D3 기반 [[D3|지식 그래프]]&lt;/li&gt;
&lt;li&gt;다크/라이트 모드&lt;/li&gt;
&lt;li&gt;[[Pagefind]] 통합 검색&lt;/li&gt;
&lt;li&gt;&lt;code&gt;paginate()&lt;/code&gt; 로 글/노트/위키 페이지네이션&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-force-graph-3d&lt;/code&gt; 로 3D 그래프 뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기까지는 글이 두세 개뿐이라 모든 게 즉시 끝났다. 그래도 1만 개 시나리오에서 어떻게 되는지 보기 위해 10,000 개의 더미 마크다운을 생성하고 &lt;code&gt;pnpm build&lt;/code&gt; 를 돌렸다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
빌드가 &lt;strong&gt;돌아오지 않았다&lt;/strong&gt;. 몇 분이 지나도 끝날 기미가 안 보였다. 강제로 종료.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;여기서 “그래도 1만 개면 좀 오래 걸리겠지” 하고 그냥 기다리는 건 위험하다. 정상적으로 끝날 수 없는 코드일 가능성이 더 크기 때문이다.&lt;/p&gt;
&lt;h2 id=&quot;진단-무엇이-폭주했는가&quot;&gt;진단: 무엇이 폭주했는가&lt;/h2&gt;
&lt;p&gt;빌드 단계별 로그를 보니 각 글 페이지를 그릴 때마다 몇십 ms씩 추가로 시간이 늘어났다. 진짜 범인은 &lt;code&gt;Backlinks.astro&lt;/code&gt; 였다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;collection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; candidates&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [slug, slug.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toLowerCase&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; col&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;posts&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;notes&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;wiki&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; entries&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getCollection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(col);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; entries) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; entry.body &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; re&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\[\[&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;|#]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;)(?:#&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;|]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;(?:&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\]\]&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ((m &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; re.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;exec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(body)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; target&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (candidates.includes(target) || candidates.includes(target.toLowerCase())) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        matches.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 컴포넌트는 &lt;strong&gt;글 페이지 한 장을 그릴 때마다&lt;/strong&gt; 모든 컬렉션의 모든 본문을 정규식으로 스캔한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 페이지당 비용: O(N × 평균 본문 길이)&lt;/li&gt;
&lt;li&gt;페이지 수: N&lt;/li&gt;
&lt;li&gt;전체 비용: &lt;strong&gt;O(N²)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1만 글, 평균 본문 500자라면 어림 잡아 &lt;em&gt;50억 회의 정규식 매칭&lt;/em&gt;. JS 정규식이 초당 1억 문자를 처리한다고 가정하면 50초가 아니라, 매 페이지가 그 비용을 통째로 다시 치르므로 누적되어 &lt;strong&gt;수십 분에서 시간 단위&lt;/strong&gt;까지 늘어난다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
빌드 타임에 O(N²) 가 들어와 있으면 N 이 두 자리 수일 때까지는 &lt;strong&gt;눈에 보이지 않는다&lt;/strong&gt;. 100 → 1만으로 N 이 100배 커지면 작업은 1만 배가 된다. 처음부터 빌드 시점 비용은 페이지당이 아니라 &lt;em&gt;프로젝트당&lt;/em&gt; 으로 가져가는 편이 안전하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;설계-콘텐츠-그래프를-한-번만-만든다&quot;&gt;설계: 콘텐츠 그래프를 한 번만 만든다&lt;/h2&gt;
&lt;p&gt;해결 방향은 단순했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;전체 컬렉션을 &lt;strong&gt;딱 한 번&lt;/strong&gt; 순회하면서 &lt;code&gt;{nodes, links, backlinks, slugMap}&lt;/code&gt; 인덱스를 만든다.&lt;/li&gt;
&lt;li&gt;그 인덱스를 모듈 레벨 &lt;code&gt;Promise&lt;/code&gt; 에 캐시한다, 모든 페이지 렌더가 같은 인덱스를 본다.&lt;/li&gt;
&lt;li&gt;백링크는 Map 조회 한 번으로 끝낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// src/lib/content-graph.ts (요약)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cache&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContentGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getContentGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContentGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cache) cache &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cache;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContentGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;notes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;wiki&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    getCollection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;posts&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;data.draft),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    getCollection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;notes&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    getCollection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;wiki&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; slugMap&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContentNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; backlinks&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContentNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;posts, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wiki, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;notes]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /* 모든 별칭/제목/슬러그 → 노드 매핑 추가 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;posts, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wiki, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;notes]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /* body 한 번 스캔, 위키링크 → backlinks 에 누적 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { nodes, links, backlinks, slugMap };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 백링크 컴포넌트는 이렇게 단순해진다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;collection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; graph&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getContentGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; matches&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; graph.backlinks.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;canonicalId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(collection, slug)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;복잡도가 &lt;strong&gt;O(N²)&lt;/strong&gt; 에서 &lt;strong&gt;O(N) + O(1) 조회 × N 페이지 = O(N)&lt;/strong&gt; 으로 떨어졌다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
Astro 의 페이지 컴포넌트는 모듈처럼 평가된다. &lt;code&gt;Astro:content&lt;/code&gt; 의 &lt;code&gt;getCollection&lt;/code&gt; 호출 결과도 동일 빌드 내에서는 캐시되지만, &lt;strong&gt;거기서 파생된 계산은 따로 캐시되지 않는다&lt;/strong&gt;. 위처럼 직접 만든 인덱스를 모듈 변수에 담아두면 자연스러운 메모이제이션이 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;그래프-페이지에도-같은-함정이-있다&quot;&gt;그래프 페이지에도 같은 함정이 있다&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/pages/graph.astro&lt;/code&gt; 도 비슷한 스캔을 하지만 그쪽은 &lt;em&gt;한 번만&lt;/em&gt; 렌더링되는 페이지라 O(N) 으로 끝났다. 다만 &lt;em&gt;클라이언트에서&lt;/em&gt; 1만 노드를 D3 force simulation 으로 돌리면 브라우저가 사실상 멈춘다. 빌드는 살아도 사용자 경험이 죽는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MAX_GRAPH_NODES = 400&lt;/code&gt; 상한을 두고, 초과 시 연결도(degree) 상위 N 개만 그리며 안내한다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; degree&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; link&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; graph.links) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  degree.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(link.source, (degree.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(link.source) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  degree.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(link.target, (degree.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(link.target) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; truncated&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; graph.nodes.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MAX_GRAPH_NODES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sortedNodes&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; truncated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;graph.nodes]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (degree.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b.id) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (degree.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a.id) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;MAX_GRAPH_NODES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  :&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; graph.nodes;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;연결이 많은 허브 노드부터 보여주는 편이 첫 인상에서 더 의미 있다. 작은 위성 노드들은 그래프에서 어차피 정보량이 적다.&lt;/p&gt;
&lt;h2 id=&quot;측정-10531-페이지--3457-초&quot;&gt;측정: 10,531 페이지 / 34.57 초&lt;/h2&gt;
&lt;p&gt;수정 후 같은 시나리오로 다시 빌드.&lt;/p&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;지표&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;값&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;빌드된 페이지 수&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;10,531&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;전체 빌드 시간&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;34.57s&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;정적 페이지 생성 단계&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;14.31s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pagefind 인덱싱&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;~10s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;dist/&lt;/code&gt; 총 크기&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;189 MB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pagefind 인덱스 크기&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;42 MB (페이지당 ~4.2 KB)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pagefind 검색 (일반 쿼리)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&amp;#x3C; 100 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pagefind 검색 (10K 매치 쿼리)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;~1.2 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;그래프 페이지 SSR&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&amp;#x3C; 50 ms (400 노드 상한)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이지네이션 페이지 응답&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&amp;#x3C; 10 ms&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;“무한정 → 35 초” 변화의 거의 전부가 &lt;strong&gt;백링크 인덱스를 모듈 캐시로 옮긴 것&lt;/strong&gt; 에서 왔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
검색 응답이 가끔 1 초가 넘는 케이스는 &lt;em&gt;“성능”&lt;/em&gt; 처럼 더미 글 1만 개에 거의 모두 들어 있는 단어를 검색했을 때다. 1만 개 결과를 점수 매기느라 시간이 든다. 실제 운영에서는 한 쿼리가 1만 개 결과를 반환하는 일이 거의 없으므로 무시 가능한 코너 케이스.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;같이-손본-자잘한-것들&quot;&gt;같이 손본 자잘한 것들&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pagefind 메타데이터&lt;/strong&gt;: 본문에만 인덱스가 잡히도록 &lt;code&gt;data-pagefind-body&lt;/code&gt; 를 글/위키/노트 컨테이너에만 붙이고, 헤더·푸터·페이지네이션·홈·소개·그래프 페이지에는 &lt;code&gt;data-pagefind-ignore=&quot;all&quot;&lt;/code&gt; 을 달았다. 검색이 더 정확해지고 인덱스 크기도 줄었다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Section 필터&lt;/strong&gt;: &lt;code&gt;data-pagefind-filter=&quot;section:posts/notes/wiki&quot;&lt;/code&gt; 로 검색 UI에서 컬렉션별 필터를 쉽게 제공.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3D 그래프 lazy import&lt;/strong&gt;: &lt;code&gt;react-force-graph-3d&lt;/code&gt; 와 &lt;code&gt;three&lt;/code&gt; 는 무겁다. 2D 가 기본이고, 3D 토글을 누른 순간에만 &lt;code&gt;React.lazy()&lt;/code&gt; 로 코드를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub 스타일 콜아웃&lt;/strong&gt;: &lt;code&gt;&gt; [!NOTE]&lt;/code&gt; &lt;code&gt;&gt; [!TIP]&lt;/code&gt; &lt;code&gt;&gt; [!IMPORTANT]&lt;/code&gt; &lt;code&gt;&gt; [!WARNING]&lt;/code&gt; &lt;code&gt;&gt; [!CAUTION]&lt;/code&gt; 5종, 지금 이 글에 시연되어 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shiki dual-theme&lt;/strong&gt;: 같은 코드 블록이 &lt;code&gt;github-light&lt;/code&gt; 와 &lt;code&gt;github-dark&lt;/code&gt; 모두로 토큰화되고, CSS 변수로 테마를 갈아끼우니 다크 모드 토글이 코드 색까지 자연스럽게 따라온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;얻은-교훈&quot;&gt;얻은 교훈&lt;/h2&gt;
&lt;h3 id=&quot;1-빌드-시점-비용은-프로젝트-단위-로-묶는다&quot;&gt;1. 빌드 시점 비용은 &lt;em&gt;프로젝트 단위&lt;/em&gt; 로 묶는다&lt;/h3&gt;
&lt;p&gt;페이지 컴포넌트 안에서 “전체 콘텐츠를 한 번 훑는” 코드가 보이면, 그건 거의 항상 잘못 둔 자리다. 그 페이지가 K 개 렌더되면 K 번 훑게 된다. 같은 일이라면 &lt;strong&gt;빌드당 한 번만&lt;/strong&gt; 끝내고 결과를 공유해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;2-모듈-레벨-promise-는-단순하지만-강력하다&quot;&gt;2. 모듈 레벨 &lt;code&gt;Promise&lt;/code&gt; 는 단순하지만 강력하다&lt;/h3&gt;
&lt;p&gt;복잡한 빌드 캐시 인프라 없이도 됐다. ESM 모듈은 한 빌드 안에서 한 번만 평가되므로, 다음 한 줄이면 메모이제이션이 끝난다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cache&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;X&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cache) cache &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cache;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;추상화 단계가 적을수록 디버깅이 쉽다. [[BrainDB]] 같은 외부 도구를 쓸 수 없었던 게 오히려 단순한 해법으로 이끌었다.&lt;/p&gt;
&lt;h3 id=&quot;3-작은-n-으로-측정하면-함정을-못-본다&quot;&gt;3. 작은 N 으로 측정하면 &lt;strong&gt;함정을 못 본다&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;글 5 개일 때는 백링크 컴포넌트가 25 회 스캔을 했다. 눈에 띄지도 않는다. 안전하게 가려면 &lt;em&gt;의도적으로 큰 N&lt;/em&gt; 을 만들어 한 번쯤 돌려봐야 한다. 더미 글 생성 스크립트는 30 줄 정도면 충분하다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// scripts/generate-dummy-posts.mjs (요약)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; rand&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; rng&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  fs.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;writeFileSync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    path.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;OUT_DIR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`stress-${&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;padStart&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}.md`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `---&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;title: &quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;date: ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;---&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 한 번의 부하 테스트가 운영 단계에서의 미래의 한 시간짜리 장애를 사전에 막아 준다.&lt;/p&gt;
&lt;h3 id=&quot;4-빌드를-살려도-런타임이-살아야-한다&quot;&gt;4. 빌드를 살려도, 런타임이 살아야 한다&lt;/h3&gt;
&lt;p&gt;이번에는 그래프 페이지가 좋은 예시였다. 빌드 자체는 1만 노드를 무리 없이 직렬화하지만, 그 데이터를 받은 브라우저는 D3 force tick 첫 번째 프레임에서 죽는다. &lt;strong&gt;빌드 결과를 소비하는 쪽&lt;/strong&gt; 의 한계도 같이 봐야 한다.&lt;/p&gt;
&lt;h3 id=&quot;5-검색은-pagefind-가-그냥-동작-한다&quot;&gt;5. 검색은 Pagefind 가 “그냥 동작” 한다&lt;/h3&gt;
&lt;p&gt;Pagefind 는 빌드된 HTML 을 후처리로 인덱싱한다. 1만 페이지에 대해서도 인덱스가 42 MB, 클라이언트는 청크 단위로만 받아오므로 초기 로드도 가볍다. 한국어 토크나이징도 별도 설정 없이 &lt;code&gt;&amp;#x3C;html lang=&quot;ko&quot;&gt;&lt;/code&gt; 만으로 잘 동작했다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
Pagefind 인덱스는 빌드된 HTML 을 읽기 때문에 &lt;em&gt;HTML 에 보이지 않는 내용은 검색되지 않는다&lt;/em&gt;. 백링크 같은 동적 영역이 SSR 단계에서 잘 렌더되어 있는지 확인하는 게 중요하다. 클라이언트 hydration 으로 채워지는 영역은 검색에 빠진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 라운드의 변화는 결국 한 가지 룰로 요약된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;빌드 시점에 전체를 훑는 일은, 빌드당 한 번만 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 규칙 하나가 1만 페이지 빌드를 &lt;em&gt;못 끝남&lt;/em&gt; 에서 &lt;em&gt;35 초 안에 끝남&lt;/em&gt; 으로 바꿨다. 같은 규칙이 위키링크 인덱스(&lt;a href=&quot;https://github.com/shinkeonkim/shinkeonkim.github.io/blob/main/src/plugins/remark-wikilink.mjs&quot;&gt;&lt;code&gt;remark-wikilink.mjs&lt;/code&gt;&lt;/a&gt;)에도, 그래프 데이터(&lt;a href=&quot;https://github.com/shinkeonkim/shinkeonkim.github.io/blob/main/src/lib/content-graph.ts&quot;&gt;&lt;code&gt;content-graph.ts&lt;/code&gt;&lt;/a&gt;)에도, 태그 인덱스에도 적용된다.&lt;/p&gt;
&lt;p&gt;앞으로 콘텐츠가 늘어나면서 새로운 병목이 또 보이겠지만, 이번 회고로 일단 만 단위까지는 안심하고 글을 쌓을 수 있게 됐다. 늘 그렇듯, &lt;strong&gt;일단 기록을 하자&lt;/strong&gt;.&lt;/p&gt;</content:encoded><category>perf</category><category>build</category><category>astro</category><category>optimization</category><category>retrospective</category><author>koa (김신건)</author></item><item><title>2025년 회고록 - 개발자에서 학생으로 돌아가서, 2025년을 정리하며</title><link>https://shinkeonkim.com/posts/my-life/2025-12-31-goodbye-2025/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/my-life/2025-12-31-goodbye-2025/</guid><pubDate>Wed, 31 Dec 2025 04:30:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;개요&quot;&gt;개요&lt;/h2&gt;
&lt;p&gt;2025년… 굉장히 많은 일이 있었던 것 같습니다.&lt;/p&gt;
&lt;p&gt;퇴사부터, 대학교 복학, &lt;code&gt;멋쟁이사자처럼&lt;/code&gt;과 &lt;code&gt;SIPE&lt;/code&gt; 동아리 활동 그리고 &lt;code&gt;여러 프로젝트&lt;/code&gt;와 &lt;code&gt;자격증 취득&lt;/code&gt;까지 정말 다양한 활동을 했습니다.&lt;/p&gt;
&lt;p&gt;이전에도 써왔던 회고글들처럼, 2025년 한 해 동안 있었던 일들을 회고하고자 합니다.
다만 여러 종류의 일들이 있었던지라, 카테고리별로 정리해보려 합니다.&lt;/p&gt;
&lt;h2 id=&quot;1-퇴사와-복학&quot;&gt;1. 퇴사와 복학&lt;/h2&gt;
&lt;p&gt;올해 3월에 퇴사와, 대학교 복학을 같이 했습니다. &lt;a href=&quot;https://shinkeonkim.com/posts/my-life/2025-02-26-grepp-memoir/&quot;&gt;이전 글&lt;/a&gt; 에서 퇴사에 대한 이야기를 다루기도 했습니다.&lt;/p&gt;
&lt;p&gt;퇴사를 하고 나서도 뭘 해야 할지 여러 고민을 했습니다. &lt;code&gt;어떤 도전들을 해야 할지&lt;/code&gt;, &lt;code&gt;어떤 공부들을 해야 할지&lt;/code&gt;에 대해서. 그래서 여러 도전적인 활동을 해보려고 했고, 더 다양한 분야의 공부도 해보려고 했습니다.&lt;/p&gt;
&lt;h2 id=&quot;2-동아리-활동&quot;&gt;2. 동아리 활동&lt;/h2&gt;
&lt;h3 id=&quot;21-멋쟁이사자처럼-국민대학교-운영진&quot;&gt;2.1. 멋쟁이사자처럼 국민대학교 운영진&lt;/h3&gt;
&lt;p&gt;멋쟁이사자처럼 국민대학교 동아리에서 백엔드 파트장을 맡아 1년간 활동했습니다. 그리고 올해를 마지막으로 더 이상 멋쟁이사자처럼 동아리에서 활동은 하지 않기로 했습니다.&lt;/p&gt;
&lt;p&gt;생각해 보면, 멋쟁이사자처럼 국민대학교 동아리와의 인연이 제 커리어패스에 많은 영향을 주었던 것 같습니다.&lt;/p&gt;
&lt;p&gt;2020년에 멋쟁이사자처럼 국민대학교 동아리에 아기사자로 참여하기 시작했습니다.
그리고 2021년, 2022년, 2024년, 2025년까지 운영진을 맡았습니다.&lt;/p&gt;
&lt;p&gt;아기사자로 들어갔을 때는 처음으로 django 프레임워크를 접했고, 웹 개발에 대한 기초적인 지식을 쌓을 수 있었습니다.
그리고 운영진으로 활동하면서는 여러 사람들에게 저의 지식을 전파하는 경험을 했고, 누군가를 가르쳐야 한다는 생각에 더 많은 지식을 습득하려고 노력하는 과정도 있었습니다.&lt;/p&gt;
&lt;p&gt;올해는 특히나 백엔드 파트장을 맡으면서, django와 drf에 대한 세션 커리큘럼을 구성하고, 직접 세션도 진행했습니다. 거기에 더해 마지막 활동이라고 생각했기 때문에, 프로젝트에 참여도 하고, 프로젝트를 심사하는 경험을 다 해보면서 마무리하고 싶었습니다.&lt;/p&gt;
&lt;p&gt;그래서 대학생 참가자로, 4호선톤이라는 연합 해커톤 대회에도 참여를 했습니다. (비록 수상은 못했지만, 프로젝트 결과물은 괜찮게 나왔습니다.)
또, 교내 해커톤에서는 심사위원으로 프로젝트 심사도 하게 되었습니다.&lt;/p&gt;
&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/심사위원.jpeg&quot; alt=&quot;1학기 학점&quot; style=&quot;width:200px&quot;&gt;
&lt;h3 id=&quot;22-sipe-4기&quot;&gt;2.2. SIPE 4기&lt;/h3&gt;
&lt;p&gt;SIPE는 제가 뭘 도전해야 할지 고민하던 시기에 알게 된 동아리입니다.&lt;/p&gt;
&lt;p&gt;SIPE는 다른 개발자 동아리나 스터디 그룹과는 다르게, 개발자들이 모여 경험을 서로 공유하고 네트워킹을 하는 데에 중점을 둔 동아리입니다.&lt;/p&gt;
&lt;p&gt;그래서 제가 고민하던 “새로운 도전과 다양한 경험”에 대해서, 간접적으로 다른 사람들의 경험으로 배울 수 있는 좋은 기회였습니다. 그리고 어쩌면 몇 개의 다리를 거쳐서 알아야 하는 사람들을 바로 알게 되는 좋은 기회이기도 했습니다.&lt;/p&gt;
&lt;p&gt;거기에 더해, SIPE에서는 미션을 수행하는 활동이 있었는데, 그 중 “AI가 나를 소개해줘요” 미션과 “자라나는 새싹 키우기” 미션을 진행했습니다.&lt;/p&gt;
&lt;p&gt;“AI가 나를 소개해줘요” 미션은 제가 AI 기술에 관심을 가지고 있었고, LLM이 마치 나처럼 나를 소개하는 경험을 해보고 싶어서 발제했습니다. 많은 사이퍼들이 관심을 가져주셨고 좋은 결과물을 낼 수 있었습니다. -&gt; &lt;a href=&quot;https://singun11.notion.site/sipe-ai&quot;&gt;관련 페이지&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;“자라나는 새싹 키우기” 미션은 개발자를 꿈꾸는 고등학생들에게 멘토링을 해주는 미션이었습니다. 이전부터 교육에 관심이 많았던 터라, 이 미션도 흥미롭게 참여했습니다. 좀더 다양한 기술을 알려주고 싶었지만, 고등학생들의 수준에 맞추다보니 기본적인 내용 위주로 알려줄 수 밖에 없었던 점이 아쉬웠습니다. 그리고 교육 분야나 기술 전파에 관심을 가지고 있는 상황이니 더욱더 교안이나 장표들을 미리 많이 준비해놓아야겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;SIPE 5기에도 참여할까 고민하였지만, 2026년에는 졸업을 앞두고 캡스톤 프로젝트를 진행해야 하고 새로운 직장을 찾는 여정을 시작해야 해서 참여하지 않기로 했습니다. 6기에는 다시 참여할 수도 있을 것 같은데, 어떤 다양한 분들과 네트워킹을 할 수 있을지 기대가 벌써 되면서, 5기에 참여하지 못하는게 아쉽기도 합니다.&lt;/p&gt;
&lt;h2 id=&quot;3-대학교-3학년-생활&quot;&gt;3. 대학교 3학년 생활&lt;/h2&gt;
&lt;p&gt;이번에 대학교 복학 후 공부를 하면서 세상이 많이 바뀌었다고 느꼈습니다. 19학번으로 입학핵서 학교를 다녔던 때는 AI 기술 자체가 발전되어있지 않았고 LLM도 없었으며 무조건 직접 공부하고 찾아보고 암기하는 방식이었습니다.&lt;/p&gt;
&lt;p&gt;하지만 지금은 LLM이 있고, 다양한 AI 서비스와 도구가 있습니다. 그래서 공부하는 방식도 달라져야 한다고 생각했습니다. 제가 공부하는 방식에다가 AI 도구들을 적절히 활용하게 되었고, 이에 대한 공부 결과, 공부 방식에 대해서 정리해 보겠습니다.&lt;/p&gt;
&lt;h3 id=&quot;31-두근두근-나의-학점은&quot;&gt;3.1. 두근두근, 나의 학점은?&lt;/h3&gt;
&lt;p&gt;1학기와 2학기 모두 좋은 학점을 받았습니다.&lt;/p&gt;
&lt;div style=&quot;display:flex; gap:20px;&quot;&gt;
&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/1학기_학점.jpeg&quot; alt=&quot;1학기 학점&quot; style=&quot;width:200px&quot;&gt;
&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/2학기_학점.png&quot; alt=&quot;2학기 학점&quot; style=&quot;width:250px&quot;&gt;
&lt;/div&gt;
&lt;h3 id=&quot;32-어떻게-공부하고-있는가-그리고-왜-이렇게-공부해야-하는가&quot;&gt;3.2. 어떻게 공부하고 있는가? 그리고 왜 이렇게 공부해야 하는가?&lt;/h3&gt;
&lt;p&gt;우선 굿노트로 강의 내용들을 한 판에 정리했습니다. 저는 공부하는 내용이 제대로 구조화되어 있지 않으면 머릿속에 잘 들어오지 않습니다.&lt;/p&gt;
&lt;p&gt;그래서 일부러 강의 내용을 정리할 때, 계층 구조를 나누거나 서열을 나누는 방식을 적용합니다. 그리고 연관된 개념과 상반된 개념이 한 번에 다 보이도록 합니다.&lt;/p&gt;
&lt;p&gt;이 정리 방식은 시험을 보거나 복습을 할 때도 훨씬 도움이 됩니다. “그 개념이 어디에 있더라?” 혹은 “그건 무슨 개념이랑 연관이 있지?”라는 생각이 들면, 그 개념이 어디에 있었는지를 떠올리면서 쉽게 기억할 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;아래 이미지들은 제가 굿노트로 정리했던 내용들 중 일부입니다.&lt;/p&gt;
&lt;p&gt;하나하나가 작은 그림 또는 픽셀처럼 보이지만, 각각 PPT 슬라이드 한 장이거나 강의 필기 내용을 정리한 것들입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/%ED%95%84%EA%B8%B0-%EC%A0%95%EB%A6%AC-%EC%9D%BC%EB%B6%80.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;그러면 끝일까요? 아닙니다.&lt;/p&gt;
&lt;p&gt;위의 방식은 한 번씩 개념을 정리한 것뿐이고 아직 제 머리에는 다 들어가 있지 않은 상태입니다.&lt;/p&gt;
&lt;p&gt;여기에서 AI 도구 중 하나인 notebooklm을 활용합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/notebooklm-ex2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;notebooklm은 구글에서 제공하는 LLM 기반의 도구입니다. 여기에 제가 정리해 놓은 시험 범위 .md 또는 .txt 파일을 업로드하고, 강의 교안도 같이 업로드합니다.&lt;/p&gt;
&lt;p&gt;그러고 나서 notebooklm에서 제공하는 퀴즈 기능, 플래시카드 기능을 활용해서 무한 반복하며 복습 및 암기합니다. 만약 모르는 내용이 나오거나, 틀린 문제가 나오면, notebooklm에서 자연스럽게 해설 제공 기능을 이용하여 개념을 다시 이해할 수 있도록 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/notebooklm-ex1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이런 방식을 통해서, 머릿속에 구조화된 개념을 넣었고 그 개념을 복습하는 과정까지 거쳤습니다.&lt;/p&gt;
&lt;p&gt;여기서 끝일까요? 만약 특정 강의나 특정 개념이 코드를 작성하는 것과 관련되어 있거나 설계를 해야 하는 것을 공부해야 한다면, notebooklm 만으로는 부족합니다.&lt;/p&gt;
&lt;p&gt;그래서 저는 Claude Code, Cursor, Antigravity와 같이 IDE에서 코딩 에이전트가 동작하는 것을 프로젝트에 적용하는 것과는 또 다른 방식으로 활용해 봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/code-study-ex.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위의 이미지에서와 같이 특정 기술의 다양한 예제 혹은 어떤 프로그래밍 언어의 문법에 대한 실습 등을 프롬프트를 통해 구성하도록 했고, 결과물들을 확인하고 다르게 변주해보면서 이해하는 과정을 거쳤습니다.&lt;/p&gt;
&lt;h2 id=&quot;4-일정-관리&quot;&gt;4. 일정 관리&lt;/h2&gt;
&lt;p&gt;올해는 &lt;code&gt;todomate&lt;/code&gt;라는 일정 관리 도구를 활용했습니다. 2026년까지는 그대로 쓰지 않을까 싶습니다만.. 그 이후에는 다른 도구를 쓸 수도 있겠다는 생각이 듭니다.&lt;/p&gt;
&lt;p&gt;아무튼, 올해 2월에 친구의 추천으로 쓰기 시작했던 &lt;code&gt;todomate&lt;/code&gt; 사용은 아주 성공적이었던 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/12/31/todomate.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;처음에는 단순히 루틴적으로 해야하는 일들만 관리하려고 사용했습니다. 예를 들어, BOJ 문제 풀이, 운동, 청소 등이요.
하지만 점점 사용하면서 그냥 무작정 떠오르는 일들도 적어두고, 장기적으로 해야하는 일들은 세세하게 쪼개서 적어두는 방식으로 활용하기에는 괜찮았습니다.&lt;/p&gt;
&lt;p&gt;그래서 매일 아침마다 todomate를 켜서 오늘 할 일들을 확인하고, 자기 전에는 오늘 했던 일들을 다시 체크하는 방식으로 활용했습니다.&lt;/p&gt;
&lt;p&gt;다만, 프로젝트 단위로 일을 관리해야 하거나, 팀 단위로 관리해야 하는 일이 있다면 매우 적합하지 않았습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;제가 해야 하는 개인 일정 관리에는 좋았지만 팀 단위의 일을 같은 곳에서 보고 싶어서 중복으로 관리하다 보니 오히려 번거로울 때도 있었습니다.&lt;/li&gt;
&lt;li&gt;프로젝트 단위로 일을 관리하기에는 각 할 일마다의 순서나 의존성 관리를 할 수 없다 보니 계속해서 수작업으로 일의 순서를 조정해야 했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 때문에 좀 더 좋은 일정 관리 도구를 찾으면 바로 다른 도구로 바꾸어 사용해 보면서 실험을 해보려고 합니다.&lt;/p&gt;
&lt;p&gt;그래도 이 덕분에 올해는 일정 관리가 꽤 잘 된 한 해였던 것 같습니다. 예를 들어, 백준 스트릭 기록도 12월 31일 기준으로 729일째 이어지게 되었습니다.&lt;/p&gt;
&lt;h2 id=&quot;5-프로젝트&quot;&gt;5. 프로젝트&lt;/h2&gt;
&lt;p&gt;올해는 적당한 수의 프로젝트를 진행했던 것 같습니다. 다만 이 프로젝트들을 동시에 진행했던 적도 있어서 조금은 버거웠던 감도 있었습니다. 내년에는 동시에 최대 2개 정도로, 1개는 큰 볼륨의 프로젝트, 1개는 작은 볼륨의 프로젝트로 진행하는 게 좋을 것 같습니다.&lt;/p&gt;
&lt;h3 id=&quot;51-코드kr&quot;&gt;5.1. &lt;a href=&quot;https://xn--hy1by51c.kr/&quot;&gt;코드.kr&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;코드.kr 이라는 Obsidian 기반의 웹 블로그 프로젝트를 진행했습니다. 만드는 데에는 몇 시간 들지도 않았고, 점차 글들을 추가하면서 발전시켜 나가는 방식으로 진행하고 있습니다.&lt;/p&gt;
&lt;p&gt;먼저 obsidian에서 글을 작성하고, 이 내용을 Github repo에 올립니다. 그리고 자동으로 Cloudflare Pages에서 이 Repo를 빌드하여 정적 웹사이트로 배포하는 방식입니다. 이때 &lt;a href=&quot;https://quartz.jzhao.xyz/&quot;&gt;Obsidian Quartz&lt;/a&gt; 테마를 사용했습니다.&lt;/p&gt;
&lt;p&gt;이후에는 테마를 제 입맛에 맞게 수정하고, 디자인을 바꾸면서 더 꾸며나가려고 합니다. 그리고 글도 꾸준히 올리려고 합니다.&lt;/p&gt;
&lt;h3 id=&quot;52-깜빡이&quot;&gt;5.2. 깜빡이&lt;/h3&gt;
&lt;p&gt;깜빡이 프로젝트는 4호선톤에 참가하여 진행했던 프로젝트입니다.  좋은 팀원들을 만나서, 색다른 아이디어를 접할 수 있었던 경험이었습니다.&lt;/p&gt;
&lt;p&gt;만 5~10세 아동의 집중력 향상을 목표로 하는 아동 교육 서비스이며, 게이미피케이션 요소를 도입하여 아이들이 즐겁게 게임을 하면서도 집중력을 기르고, 그 결과에 대한 리포트를 부모님께 제공하는 서비스입니다.&lt;/p&gt;
&lt;p&gt;저는 인프라 및 백엔드 개발을 담당해서 진행했습니다.&lt;/p&gt;
&lt;p&gt;처음에는 홈랩에 있는 K8s 클러스터를 통해 배포하였지만, 나중에는 AWS로 이전하여 배포했습니다. (개발 과정 중에 한번 홈랩 k8s 클러스터가 돌아가는 미니 PC가 전원이 꺼지는 등의 이슈가 있었고 해커톤 진행 중에 서비스가 다운되면 복구하기가 어렵기 때문에 이런 결정을 했습니다.)&lt;/p&gt;
&lt;p&gt;인프라 구성은 &lt;a href=&quot;https://github.com/kkambbaki/infra&quot;&gt;이곳&lt;/a&gt;에서 다루고 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 백엔드는 django, drf, postgresql, redis, celery를 활용하여 개발했습니다. 제가 굉장히 애용하는 기술 스택이을 거의 그대로 활용했고 개발의 속도를 빠르게 낼 수 있었습니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 진행하면서 가장 신경 쓴 점은 PDF 리포트 생성 및 메일 발송 부분이었습니다.&lt;/p&gt;
&lt;p&gt;PDF로 변환해야 하는 리포트 페이지는 프론트엔드에서 구현되어있고, 이 페이지는 사용자 로그인 후 접근해야 하는 페이지였습니다. 그래서 단순히 해당 페이지의 HTML을 가져오거나, URL에 접근하는 방식으로는 PDF를 생성할 수 없었습니다.&lt;/p&gt;
&lt;p&gt;단순하게 제가 떠올린 해결책은 일회용 토큰이었습니다. 백엔드에서 PDF 생성 과정 중에 일회용 토큰을 발급하고 첨부하여 해당 페이지에 접근하게 했습니다. 프론트엔드 로직에서는 해당 토큰을 Header에 담아 백엔드 API에 요청을 보내면 이를 통해 User 인증을 처리했고, 인증이 완료된 상황에서 페이지를 렌더링 하여 PDF로 변환하는 방식을 적용했습니다. (약간 회사에서 경험했던 내용을 간단하게 해커톤 프로젝트에 적용해본 셈이었는데, 나름 잘 동작해서 다행이었습니다.)&lt;/p&gt;
&lt;p&gt;이 과정에서 celery를 활용하여 비동기적으로 PDF 생성 작업을 처리했고, PDF를 첨부하여 메일을 발송했습니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트에 대한 자세한 내용은 &lt;a href=&quot;https://github.com/kkambbaki&quot;&gt;이곳&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;53-athena-프로젝트&quot;&gt;5.3. Athena 프로젝트&lt;/h3&gt;
&lt;p&gt;Athena 프로젝트는 1학기에 수강한 웹서버컴퓨팅 과목의 AD 과제 겸 SIPE 미션에서 AI를 활용하는 프로젝트로 진행했습니다. 저 혼자 프로젝트 설계와 개발을 모두 진행했습니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트는 알고리즘 문제 풀이를 도와주는 AI 기반의 웹 서비스입니다. 백준 온라인 저지 문제를 기반으로 사용자가 코드를 제출하면, 먼저 코드를 실행하고 테스트케이스를 통과하는지 확인합니다. 만약 테스트케이스를 통과하지 못한다면 반례를 찾아냅니다. 반례가 없었다면 AI 모델을 활용하여 코드의 문제점을 분석하고 사용자에게 피드백을 제공합니다.&lt;/p&gt;
&lt;p&gt;사용자는 계속해서 피드백을 적용하면서 코드를 개선할 수 있게 됩니다. 이 과정에서 반례 데이터나 피드백 데이터가 계속 수집되게 되고, 이를 통해 AI 모델이 점점 더 정확한 피드백을 제공할 수 있도록 합니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트에서는 docker-compose와 Docker를 기반으로 로컬에서 동작하는 환경까지만 구축했습니다. 이후에는 k8s 클러스터에서 동작하는 과정으로 확장할 수도 있지만, 마이크로서비스에 대해서 좀 더 경험을 쌓으면서 진행하는 게 좋을 것 같아서 일단은 여기서 마무리했습니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트는 아래와 같이 여러 개의 서비스들로 나누어 구성되어 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shinkeonkim/AD_project_athena&quot;&gt;https://github.com/shinkeonkim/AD_project_athena&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shinkeonkim/code-judger&quot;&gt;https://github.com/shinkeonkim/code-judger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shinkeonkim/code-executor&quot;&gt;https://github.com/shinkeonkim/code-executor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shinkeonkim/code-testcase-generator&quot;&gt;https://github.com/shinkeonkim/code-testcase-generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;웹앱은 django의 MTV 패턴을 그대로 사용하여 구성했습니다. 코드 실행은 Rust로 작성된 code-executor 서비스에서 처리했습니다. 그리고 이 서비스를 활용하여 코드 채점을 처리하는 code-judger 서비스를 구성했으며, 테스트케이스 생성을 위해 LLM 서비스와 채점 기능을 활용하는 code-testcase-generator 서비스를 구성했습니다. 그리고 이 모든 서비스들은 Docker container로 구성되었고, grpc를 통해 서로 통신하도록 설계했습니다.&lt;/p&gt;
&lt;p&gt;완성도가 높지 않았다고 생각했고 실제로 서비스가 운영되기에는 어려운 점이 있었던 것 같습니다. 그래도 &lt;a href=&quot;https://playzone.kookmin.ac.kr/user/compe/viewAwardContents.do?currentPageNo=&amp;#x26;dataSeq=1010037&amp;#x26;parentSeq=1010037&quot;&gt;국민대학교 2025학년도 1학기 소프트웨어융합대학 크리에이터 경진대회&lt;/a&gt;에서 1등을 수상하는 성과를 거두기도 하면서 나름 의미 있는 프로젝트였던 것 같습니다. 그리고 혼자서 진행한 프로젝트였지만, 여러모로 배운 점도 많았기에 재미있게 진행했던 것 같습니다.&lt;/p&gt;
&lt;h3 id=&quot;54-palette&quot;&gt;5.4. Palette&lt;/h3&gt;
&lt;p&gt;Palette 프로젝트는 사주 기반의 소개팅 서비스입니다. 아직 실제 사용자에게 배포가 되지는 않았습니다.&lt;/p&gt;
&lt;p&gt;이 프로젝트에서는 실제 사용자들의 사주 데이터를 기반으로, 사주 궁합이 좋은 사람에 대해 매칭을 해주는 기능을 제공하고 있고, LLM을 활용하여 사주 궁합을 설명해 주는 기능도 제공합니다.&lt;/p&gt;
&lt;p&gt;PASS 인증이나 토스 페이먼츠 등을 활용해서 인증 및 결제 기능까지 구현해 두었고, k8s 클러스터 배포 및 CI/CD 파이프라인도 구축했습니다. 이후에 실제 사용자가 사용하고 여러 피드백과 이슈를 처리하면서 내용을 정리하여 다시 글을 작성해 보겠습니다.&lt;/p&gt;
&lt;h2 id=&quot;6-자격증&quot;&gt;6. 자격증&lt;/h2&gt;
&lt;p&gt;올해도 마찬가지로 자격증 시험들을 치렀습니다.&lt;/p&gt;
&lt;p&gt;정보처리기사, ADsP, 제한무선통신사, PCCP, 운전면허를 취득했습니다. 그리고 리눅스마스터 필기시험도 통과했지만, 실기 시험은 미루다가 결국 2026년으로 넘어가게 되었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정보처리기사 : 미루고 미루다가 무난하게 합격했습니다. 공부 기간이 그렇게 길지는 않았고 전공 개념을 복습하는 느낌으로 공부했습니다.&lt;/li&gt;
&lt;li&gt;ADsP : 데이터 분석에 대한 기초적인 개념들을 공부했습니다. 실제 업무를 진행하는 과정에서 데이터 분석 업무를 진행하시는 분들과 협업하거나 소통할 때 도움이 되지 않을까 싶어서 취득했습니다. 이후에 인공지능 공부나 데이터 용어를 접하면서 도움이 되었습니다.&lt;/li&gt;
&lt;li&gt;제한무선통신사 : 군대에서 무선 통신을 옆에서 지켜보면서 관심을 가지게 되어 취득하게 되었습니다. 단순히 기초 개념 강의를 듣고 간단한 문제 풀이로 취득할 수 있는 자격증이었습니다. 이후에 아마추어무선기사 자격증 쪽으로도 도전해 볼까 생각 중입니다. 취미로도 괜찮을 것 같고, 무선 통신에 대한 이해도를 높이는 데에도 도움이 될 것 같습니다.&lt;/li&gt;
&lt;li&gt;PCCP : 국민대학교 내에서 해당 자격증의 점수가 필요한 경우가 있어서 취득하게 되었습니다. 코딩테스트는 평소에 미리 계속 연습해 오던 터라, Lv4로 무난하게 취득했습니다. 시간 관리를 신경 쓰면서 풀었으면 Lv5도 가능했을 것 같다는 생각이 듭니다.&lt;/li&gt;
&lt;li&gt;운전면허: 너무 오랫동안 미루다가 취득하게 되었습니다. 1종 보통 면허를 취득했고, 클러치 조작이 처음에 너무 어려웠는데, 연습하다 보니 금방 적응했던 것 같습니다. 하지만 자동 차량을 운전할 것 같고.. 장롱면허가 될 것 같기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;결론&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;올해는 많은 사건, 사고가 있었습니다. 그 속에서 다양한 경험을 했고, 많은 사람을 만났습니다. 그리고 개발자로서의 커리어를 쌓다가 학생으로 돌아가서 다시 공부에 전념하기도 했습니다.&lt;/p&gt;
&lt;p&gt;내년에는 진짜 졸업을 앞두고 있습니다. 캡스톤 프로젝트를 진행해야 하고, 새로운 직장을 찾아야 합니다.
그래서 2026년에는 좀 더 집중해서 공부하고, 준비를 해야 할 것 같습니다.&lt;/p&gt;
&lt;p&gt;Java / Spring 공부, Golang, Django와 Rails 근본 파헤치기, 캡스톤 프로젝트 등등. 할 게 많이 떠오릅니다. 어찌 되었든 2026년에도 열심히 달려보려고 합니다.&lt;/p&gt;
&lt;p&gt;모두 좋은 한 해를 보내셨길 바라며, 그리고 2026년에도 좋은 일들만 가득하시길 바랍니다.&lt;/p&gt;
&lt;p&gt;저의 넋두리같은 글을 읽어주셔서 감사합니다!&lt;/p&gt;</content:encoded><category>회고록</category><author>koa (김신건)</author></item><item><title>Atcoder Beginner Contest 399 후기</title><link>https://shinkeonkim.com/posts/atcoder/2025-03-30-abc-399/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/atcoder/2025-03-30-abc-399/</guid><description>2025년 3월 30일에 진행된 Atcoder Beginner Contest 399에서 풀이한 문제들과 풀이 방법, 그리고 대회 경험을 공유하는 후기입니다.</description><pubDate>Sun, 30 Mar 2025 10:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;개요&quot;&gt;개요&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc399&quot;&gt;대회 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;어제 (03/29) 저녁 9시부터 10시 40분까지 진행된 [[Atcoder]] Beginner Contest 399 후기입니다.&lt;/p&gt;
&lt;p&gt;총 7문제 중 4문제를 풀었습니다.&lt;/p&gt;
&lt;p&gt;A번부터 D번까지 풀이하는데 19분을 소모했습니다.
이후 E번과 F번을 풀기 위해서 대회 종료시각까지 트라이를 했지만 풀이하지 못했습니다.
rating이 떨어지나 걱정했지만, 다행히도 rating이 약간 올라서 안심했습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc399/editorial&quot;&gt;공식 에디토리얼 (해설)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;풀이&quot;&gt;풀이&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;해설은 꼭 문제를 한번 읽고 확인해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;a---hamming-distance&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc399/tasks/abc399_a&quot;&gt;A - Hamming Distance&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;두 문자열이 주어진다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;두 문자열의 해밍 거리 (서로 다른 문자의 개수)를 구하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-1&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;문자열을 순회하면서 서로 다른 문자의 개수를 구하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;b---ranking-with-ties&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc399/tasks/abc399_b&quot;&gt;B - Ranking with Ties&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-1&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;N 개의 score가 주어집니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;각 score마다 RANK() 를 구하면 됩니다. (sql의 RANK()와 같이)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-2&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;정렬을 사용하거나, map을 사용하거나 기타 등등 RANK()를 구현하기만 하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;c---make-it-forest&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc399/tasks/abc399_c&quot;&gt;C - Make it Forest&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-2&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;무향 그래프 F가 주어집니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;간선을 지워 cycle이 없는 그래프가 만드려고 합니다. 이때, 지워야 하는 간선의 최소 개수를 구해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-3&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;처음에 이렇게 풀어도 되는건가? 좀 헷갈렸다.
몇 가지 그래프를 그리다보니 확신을 하게 되었고, 아래와 같이 구성을 했다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 ~ N 까지 정점을 탐색하는데, 각 정점이 방문되지 않은 경우 DFS 탐색을 한다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;그리고, 만약 이전에 방문했음에도 다시 방문하게 되면 카운팅한다. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;단, 부모로 다시 탐색을 하러 돌아가는 경우는 배제해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;d---bonfire&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_d&quot;&gt;D - Bonfire&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-3&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;같은 숫자임에도 서로 인접하지 않은 두 숫자를 고른다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;두 숫자의 위치를 서로 바꾸어봤을 때, 각 숫자가 인접하게 된다면 카운팅한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-4&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;솔직히 이 문제가 D번이 맞나? 싶었다.
생각보다 너무 쉽게 풀리는 문제였다.&lt;/p&gt;
&lt;p&gt;그냥 각 숫자마다 위치 2개를 따로 hash 또는 배열에 저장해둔다.&lt;/p&gt;
&lt;p&gt;그리고 앞에서부터 순회하면서 인접한 두 숫자에 대해서 저장해둔 위치로 조건을 판단해보면 된다.&lt;/p&gt;
&lt;h2 id=&quot;e---replace&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc399/tasks/abc399_e&quot;&gt;E - Replace&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-4&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;문자열 S와 T가 주어진다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;문자열 S를 T로 바꾸길 원한다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;다만, 문자열 S에 존재하는 임의의 문자를 모두 하나의 문자로 바꾸는 연산만 가능한 상황이다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;이때, S를 T로 바꾸는 것이 가능한지, 가능하다면 최소 연산의 수가 어떻게 되는지 구해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-5&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;결국 풀이하지 못한 채로 대회가 종료되었다.&lt;/p&gt;
&lt;p&gt;내 접근은 S-&gt;T로 가기 위한 과정을 유향 그래프화 시키는 것이었다.
만약 ab -&gt; cb 라면 a -&gt; c 라는 간선을 추가하는 것처럼 말이다.&lt;/p&gt;
&lt;p&gt;이 과정에서 cycle이 없다면 간선의 수가 답이었고, cycle이 있다면 cycle로 변하는 과정에서 임의의 temp 문자로 변환되었다가 맨마지막에 변환되는 식이 되지 않을까 해서 간선의 수 + 1 을 더하는 방식이었다.&lt;/p&gt;
&lt;p&gt;다만 이 과정을 구하는게 까다로웠고, 현재 temp 문자로 사용할 수 있는 문자가 있는지 등을 체크하기 위해 SCC와 위상정렬을 막 적용하다가 시간이 끝났다…&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;7문제 중 4문제를 풀이하고 끝나서 굉장히 찝찝한 contest였다.
트리나 그래프 문제가 나오면 아직도 망설임이 좀 있는 것 같다. 관련 문제와 아이디어들을 좀 더 접할 필요가 있는 것 같다.&lt;/p&gt;</content:encoded><category>atcoder</category><category>문제풀이</category><author>koa (김신건)</author></item><item><title>Atcoder Beginner Contest 398 후기</title><link>https://shinkeonkim.com/posts/atcoder/2025-03-23-abc-398/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/atcoder/2025-03-23-abc-398/</guid><description>2025년 3월 22일에 진행된 Atcoder Beginner Contest 398에서 풀이한 문제들과 풀이 방법, 그리고 대회 경험을 공유하는 후기입니다.</description><pubDate>Sun, 23 Mar 2025 02:20:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;개요&quot;&gt;개요&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398&quot;&gt;대회 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;어제 (03/22) 저녁 9시부터 10시 40분까지 진행된 [[Atcoder]] Beginner Contest 398 후기입니다.&lt;/p&gt;
&lt;p&gt;총 7문제 중 6문제를 풀었습니다.&lt;/p&gt;
&lt;p&gt;A번부터 D번까지 풀이하는데 20분을 소모했고,
E번이 interactive 문제였고, 아이디어가 떠오르지 않아 F번 먼저 풀이하였고 이때 20분 정도 시간이 걸렸습니다.
그 이후, E번 풀이를 떠올리고 풀이하는데 25분 가량 걸렸습니다.
그리고도 시간이 남아서 G번을 확인하였습니다.
다만, 이때까지도 모든 문제를 풀이한 사람이 없던 상황이었고, G번의 난이도가 쉽지 않다는 것을 알게 되었습니다.
여러 접근을 했지만 풀이를 알아내지 못하고 대회가 종료되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/editorial?editorialLang=ja&quot;&gt;공식 에디토리얼 (해설)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;풀이&quot;&gt;풀이&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;해설은 꼭 문제를 한번 읽고 확인해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;a---doors-in-the-center-&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_a&quot;&gt;A - Doors in the Center &lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- 와 = 로 구성된 팰린드롬 문자열을 만드는 문제이다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-1&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;주어지는 N이 홀수인지, 짝수인지에 따라 문자열을 구성하면 된다.&lt;/p&gt;
&lt;h2 id=&quot;b---full-house-3&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_b&quot;&gt;B - Full House 3&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-1&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;7개의 숫자가 주어진다. 이 숫자를 조합하여 풀하우스를 만들 수 있는지 구하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-2&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;서로 다른 숫자 a, b에 대해, a의 개수는 3개 이상, b의 개수는 2개 이상인지 검사하면 된다.ㄴ&lt;/p&gt;
&lt;h2 id=&quot;c---uniqueness&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_c&quot;&gt;C - Uniqueness&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-2&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 ~ N 번 사람이 각자 숫자를 가지고 있다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;다른 사람들이 가지고 있지 않는 사람들 중에, 가지고 있는 숫자가 가장 큰 사람의 번호를 반환해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-3&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;솔직히 지문을 읽고, 뭔 이상한 소리인가 싶었고, 예제 또한 이상하게 주어져있어서 조금 당황했다.
discussion에서 다른 사람들도 헷갈릴 여지가 있었던 것으로 보인다.&lt;/p&gt;
&lt;p&gt;위에 문제 내용을 그대로 구현하면 된다.&lt;/p&gt;
&lt;h2 id=&quot;d---bonfire&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_d&quot;&gt;D - Bonfire&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-3&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;모닥불에서 연기가 생성된다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;각 시간(틱)마다 바람이 분다. 이때 특정 (R, C)에 있는 사람에게 연기가 닿았는지 확인하여 모든 시간마다의 결과를 출력한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-4&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;원래라면 모닥불로 하여금 생긴 연기를 계속 움직여가며 판단할 수 있다.
하지만 연기의 최대 개수가 200,000 개 이기 때문에 N^2이 되는 순간 해결할 수가 없다.
그래서 문제를 보자마자 바로 구현하기 시작한건 연기를 고정시키고 모닥불(0, 0)과 사람 (R, C)을 계속 이동시키자였다.&lt;/p&gt;
&lt;p&gt;아래와 같이 구현했다. 다만 좌표를 저장할때 특정 int value로 담아두고 싶어서 좌표를 변환하여 했다. 지금 생각해보면 그냥 tuple로 저장해도 되었을 텐데라는 아쉬움이 남는다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DIFF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 400000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;K &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; to_axis&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(y, x):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; K &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  n, r, c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mii()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inp()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  maker &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DIFF&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DIFF&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  dy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;N&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;S&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;W&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;E&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  dx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;N&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;S&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;W&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;E&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; DIFF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; DIFF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  smokes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    direction &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    axis &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; to_axis(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;maker)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    smokes[axis] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    maker[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dy[direction]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    maker[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dx[direction]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dy[direction]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dx[direction]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; smokes.get(to_axis(r, c), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ans)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;e---tree-game&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_e&quot;&gt;E - Tree Game&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-4&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;트리가 주어진다. 이때 트리에 간선을 추가하는 액션을 각 턴마다 진행한다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;단, 간선을 추가함으로서 홀수개의 노드로 구성된 cycle이 생기면 안된다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;더이상 추가할 수 있는 간선이 없는 경우 패배한다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;상대와 게임을 진행함에 있어서, 선공/후공을 정하여 승리해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-5&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;처음에는 인터랙티브 문제이기도 하고 풀이가 잘 떠오르지 않았다.
그러던 중 예시를 여러 개 만들어보다가 떠오른 생각이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;처음에 주어지는 트리 상에서, 서로의 거리가 홀수로 떨어져 있는 경우면서 바로 이어지지 않은 경우가 결국 간선을 만들어낼 수 있는 경우 아닌가?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;정점의 개수 N이 최대 100까지였기 때문에 구현을 빠르고 쉽게하기 위해, 플로이드-와샬로 미리 거리를 구했고,
&lt;code&gt;서로의 거리가 홀수로 떨어져 있는 경우면서 바로 이어지지 않은 경우&lt;/code&gt; == &lt;code&gt;ar[x][y] % 2 == 1 &amp;#x26;&amp;#x26; ar[x][y] &gt; 1&lt;/code&gt;에 대한 간선을 미리 구해두었다.&lt;/p&gt;
&lt;p&gt;그래서 이 간선들을 토대로 내 턴에는 아무거나 출력하고, 상대 턴에는 주어지는 간선을 제거하면서 처리해봤다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ll ar[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;110&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;110&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ll N;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  cin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ar[i][j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; INF;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ar[i][i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a, b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ar[a][b] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ar[b][a] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; z &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; z &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; z&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; x&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; y&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ar[x][y] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ar[x][z] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ar[z][y]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          ar[x][y] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ar[x][z] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ar[z][y];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  vector &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pii&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; x&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; N; y&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ar[x][y] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ar[x][y] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push_back&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ x, y });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Second&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; endl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;First&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; endl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].first &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot; &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].second &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; endl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;erase&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a, b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, b);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      pii current &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(current.first &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; current.second &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;erase&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].first &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot; &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans_list[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].second &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; endl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;erase&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ans_list.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;f---abcba&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_f&quot;&gt;F - ABCBA&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-5&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;문자열 S가 주어졌을 때, 문자열 S를 prefix로 하는 팰린드롬을 만들어야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-6&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;보자마자 manacher 알고리즘이 떠올랐다.
manacher 알고리즘은 특정 위치를 중심으로 팰린드롬의 길이를 구할 수 있는 알고리즘이다.
만약 이렇게 어떤 위치에서 구한 팰린드롬이 문자열 끝까지 도달할 수 있다면, 문제에서 원하는 문자열 S를 prefix로 하는 팰린드롬을 만들 수 있는 상황일 것이다. 따라서 앞에서부터 순차적으로 확인하다가 해당 경우인경우 바로 출력하게 했다.&lt;/p&gt;
&lt;p&gt;단, 이때 홀수 길이의 팰린드롬과 짝수 길이의 팰린드롬인지에 따라 약간의 구현 처리가 필요하다.&lt;/p&gt;
&lt;p&gt;아래 코드는 manacher 알고리즘의 일부 구현 내용을 생략하여 문제에 맞게 활용한 코드이다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;manacher&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), R &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; R) A[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(A[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i], R&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A[i]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A[i]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A[i]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A[i]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) A[i]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; R) R &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A[i], p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; A;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; space&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    string t;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c: s) t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c, t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos; &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    t.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pop_back&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; t;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  string s, o;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  cin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  o &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; space&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  vector &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ret_1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; manacher&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ret_1.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ret_1.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ret_1[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; org_idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; org_idx; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; o[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; org_idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; o[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; org_idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ans &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; org_idx; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; o[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; org_idx; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; o[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;g---not-only-tree-game&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc398/tasks/abc398_g&quot;&gt;G - Not Only Tree Game&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;문제-내용-6&quot;&gt;문제 내용&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;E번에서 주어지는 문제 조건과 유사하다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;단, 해당 문제에서는 트리가 아닌 그래프가 주어진다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;심지어 서로 연결되어 있지 않는 경우도 존재한다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;이때 2명의 플레이어가 게임을 진행한다고 했을 때 어떤 플레이어가 이기는지 출력해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;풀이-7&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;풀이가 전혀 떠오르지 않았다.
에디토리얼은 &lt;a href=&quot;https://atcoder.jp/contests/abc398/editorial/12521&quot;&gt;여기&lt;/a&gt; 있다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;그래도 7문제중 6문제를 무난하게 풀이한 contest 였다.
오랫동안 굳었던 머리가 다시 풀리는 느낌이라 좋았던 대회지만, 대회 자체의 문제 난이도와 배치는 조금 이상했던 것 같다.&lt;/p&gt;</content:encoded><category>atcoder</category><category>문제풀이</category><author>koa (김신건)</author></item><item><title>Atcoder Beginner Contest 396 후기</title><link>https://shinkeonkim.com/posts/atcoder/2025-03-09-abc-396/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/atcoder/2025-03-09-abc-396/</guid><description>2025년 3월 8일에 진행된 Atcoder Beginner Contest 396에서 풀이한 문제들과 풀이 방법, 그리고 대회 경험을 공유하는 후기입니다.</description><pubDate>Sun, 09 Mar 2025 09:20:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;개요&quot;&gt;개요&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396&quot;&gt;대회 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;어제 (03/08) 저녁 9시부터 10시 40분까지 진행된 [[Atcoder]] Beginner Contest 396 후기입니다.&lt;/p&gt;
&lt;p&gt;총 7문제 중 5문제를 풀었습니다.
A ~ D번까지 문제를 보자마자 바로 풀이하였고, 21분 소요했습니다. 여전히 구현이 느립니다. 더 땡겨야 합니다.
E번 문제를 대강 풀이하고 제출했는데, 최적화된 해답을 출력하지 않았고, 이를 해결하는데 시간을 오래 사용했습니다.
마지막 제출을 대회 종료 5초전에 했고, 대회가 종료된 후 AC를 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396/editorial&quot;&gt;공식 에디토리얼 (해설)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;풀이&quot;&gt;풀이&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;해설은 꼭 문제를 한번 읽고 확인해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;a---triple-four-&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396/tasks/abc396_a&quot;&gt;A - Triple Four &lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3번 연속된 숫자가 있는 경우, Yes 아니면 No를 출력한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문제에 주어지는대로 바로 구현하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;b---card-pile&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396/tasks/abc396_b&quot;&gt;B - Card Pile&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;스택에 카드를 넣는 연산과 맨 위의 카드를 확인한 후, 빼는 연산 구현&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문제에서 주어지는대로 스택을 사용하면 됩니다. 단, 100개의 0을 이미 넣어놓은채 시작하는 전제조건을 확인해야 합니다.&lt;/p&gt;
&lt;h2 id=&quot;c---buy-balls&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396/tasks/abc396_c&quot;&gt;C - Buy Balls&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;검정색 공들과 흰색 공들이 숫자가 적혀진 채로 주어진다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;각 공을 고르고, 고른 공의 숫자들을 합쳤을 때 최대가 되도록 해야 한다. 단, 고른 검정색 공의 수가 흰색 공의 수보다 크거나 같아야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아래와 같이 그리디하게 최대한 검정, 흰색을 동시에 선택하다가, 흰색공을 선택하지 않아도 되는 경우에는 검정색만 고르도록 했다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  B &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sorted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(B, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;reverse&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  W &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sorted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(W, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;reverse&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n, m)):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; W[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; W[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; W[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    elif&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n, m), n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;d---minimum-xor-path&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396/tasks/abc396_d&quot;&gt;D - Minimum XOR Path&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;그래프가 주어진다. 그래프의 간선에 가중치가 주어진다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;다만, 해당 가중치를 더하는 연산이 아닌, XOR 연산을 수행했을 때, 1 ~ N 까지 단순 경로로 도달했을 때 최소 값을 구하라&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;N이 크지 않게 주어졌다. DFS로 모든 단순 경로를 탐색하도록 했다. 아래에 일부 코드를 적어두었다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;edges &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;mn &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;chk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dfs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(node, label):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n, m, edges, mn, chk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chk[node]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  chk[node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  mn[node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mn[node], label)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; edge &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; edges[node]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dfs(edge[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], label &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; edge[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  chk[node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n, m, edges, mn, chk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  dfs(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mn[n])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;e---min-of-restricted-sum&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc396/tasks/abc396_e&quot;&gt;E - Min of Restricted Sum&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;문제가 복잡하게 주어졌지만, 요약하자면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;노드 번호1, 노드번호2, 두 노드 사이의 가중치가 나열된다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;각 노드마다의 값을 구해야 한다. 이때 각 노드 사이의 값 끼리 XOR연산을 했을 때 주어지는 가중치와 동일하도록 해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문제를 보다보니, XOR 연산의 특성을 고민해보다 결국 시작 노드에서 아무 값으로 시작해도 그래프가 정상적으로 주어졌다면 어떻게든 값이 나온다는 사실을 알았다.
그래서 무작정 각 노드마다 탐색을 해서, 탐색이 되지 않은 경우 0으로 시작해서 그래프를 탐색하도록 했다.&lt;/p&gt;
&lt;p&gt;하지만, 이는 틀린 답이었고, 이유를 찾지 못하다가 뒤늦게 문제 상 주어진 조건을 놓쳤음을 알게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;If there are multiple good sequences with the same minimum sum, printing any of them is accepted.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;그럼 최소를 어떻게 찾아야할까&lt;/code&gt;를 계속 고민하다가, 각 그래프 탐색을 하면서 탐색이 된 노드들을 그룹지어놓고,
각 그룹마다 1, 2, 4, 8, 16, … 를 모든 노드값에 XOR 연산을 하면서 최소값이 된 경우 갱신을 하는식으로 구현을 추가했다.&lt;/p&gt;
&lt;p&gt;해당 구현을 하다가 조금 구현 실수를 하면서 WA를 계속 맞았지만, 대회 종료 직전에 제출한 코드가 AC를 받으면서 종료되었다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;이번에는 E번 문제를 좀 쉽게 접근하고나서, 더 생각이 떠오르지 않아 망설임이 많았습니다.
XOR 연산에 대해서 좀더 잘 알게 된 competition이었던 것 같습니다.&lt;/p&gt;</content:encoded><category>atcoder</category><category>문제풀이</category><author>koa (김신건)</author></item><item><title>Atcoder Beginner Contest 395 후기</title><link>https://shinkeonkim.com/posts/atcoder/2025-03-02-abc-395/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/atcoder/2025-03-02-abc-395/</guid><description>2025년 3월 1일에 진행된 Atcoder Beginner Contest 395에서 풀이한 문제들과 풀이 방법, 그리고 대회 경험을 공유하는 후기입니다.</description><pubDate>Sun, 02 Mar 2025 02:50:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;개요&quot;&gt;개요&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395&quot;&gt;대회 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;어제 (03/01) 저녁 9시부터 10시 40분까지 진행된 [[Atcoder]] Beginner Contest 395 후기입니다.&lt;/p&gt;
&lt;p&gt;총 7문제 중 5문제를 풀었습니다.
A ~ E번까지 문제를 보자마자 해답은 바로 떠올랐지만, 코드를 작성하는 시간이 오래 걸렸습니다.
F번, G번 문제를 읽고나서 도저히 바로 해답이 안떠올라서 대회를 그대로 종료했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2025/03/02/atcoder-abc-395-result.png&quot; alt=&quot;result&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/editorial?editorialLang=ja&quot;&gt;공식 에디토리얼 (해설)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;풀이&quot;&gt;풀이&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;해설은 꼭 문제를 한번 읽고 확인해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;a---strictly-increasing&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_a&quot;&gt;A - Strictly Increasing?&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;N 개의 숫자가 주어집니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;N 개의 숫자들이 엄밀하게 증가하는 수열로 주어졌는지 판단하면 됩니다. &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그냥 구현하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;b---make-target&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_b&quot;&gt;B - Make Target&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;자연수 N이 주어집니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;문제 규칙에 맞게 `#`과 `.`을 찍으면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;수식으로 바로 해결해도 되고, N의 범위가 작기 때문에 반복문으로 돌려도 충분히 해결됩니다.&lt;/p&gt;
&lt;h2 id=&quot;c---shortest-duplicate-subarray&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_c&quot;&gt;C - Shortest Duplicate Subarray&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;N 개의 숫자가 주어집니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;주어지는 수열에서 `중복되는 숫자가 있는 부분 수열 중 최소 길이`를 구해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;언뜻보면 브루트포싱으로 할 수 있겠다 싶지만, 숫자 범위가 그렇지 않습니다.
곧바로 각 숫자마다 분류하여 index를 저장했고, 각 index를 훑으면서 최소 격차를 가진 곳을 찾아 반환했습니다.&lt;/p&gt;
&lt;p&gt;(처음에 문제를 잘못 읽어서 중복된 숫자가 없는 수열로 보았다가 시간 낭비를 했습니다.)&lt;/p&gt;
&lt;h2 id=&quot;d---pigeon-swap&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_d&quot;&gt;D - Pigeon Swap&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;해당 문제에서는 3가지의 연산이 있습니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. 비둘기 한 마리가 이동한다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 두 둥지에 있는 비둘기들이 서로 다 바꾼다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. A번 비둘기가 어디있는지 출력한다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저는 이 문제를 보자마자, 그냥 어? 둥지 안에 있는 비둘기를 서로 바꾸는게 아니라 둥지에 가상의 라벨이 붙어있다고 생각하고, 그 라벨을 바꿔 붙였다고 생각하면 되는거 아닌가? 라고 생각을 했습니다.&lt;/p&gt;
&lt;p&gt;그래서 비둘기마다 위치하는 둥지 위치, 각 둥지(번호를 매겨놓은)마다 붙어 있는 라벨, 각 라벨마다 붙어있는 둥지 원래 번호를 각각 저장했고
제 연산마다 적재적소로 활용해서 문제를 풀이했습니다.&lt;/p&gt;
&lt;p&gt;어찌보면 포인터를 활용했다고 할 수 있겠네요.&lt;/p&gt;
&lt;p&gt;아래는 코드의 일부 내용입니다&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  N, Q &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mii()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  pigeon_place &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, N)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  place_label &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, N)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  place_label_rev &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; place_label:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    place_label_rev[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Q):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    commands &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mii()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; commands[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      a, b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; commands[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      pigeon_place[a] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; place_label_rev[b]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    elif&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; commands[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      a, b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; commands[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      A &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; place_label_rev[a]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      B &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; place_label_rev[b]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      place_label[A], place_label[B] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; place_label[B], place_label[A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      place_label_rev[a], place_label_rev[b] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; B, A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; commands[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(place_label[pigeon_place[a]] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;e---flip-edge&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_e&quot;&gt;E - Flip Edge&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;그래프가 주어집니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;이때 1번 노드에서 N번 노드로 이동하면서 최단 거리를 알아내야 합니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;단, 각 간선의 길이는 무조건 1입니다.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;그리고 모든 간선의 방향을 뒤바꾸는 연산을 하면서 X 비용을 낼 수도 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저는 문제를 보고, 이건 무조건 다익스트라 알고리즘으로 하면 되겠다 였고, 단 거리를 저장하는데 있어서
&lt;code&gt;Distance[어느 방향으로 이 노드에 도달했는가? (0: 정방향, 1: 역방향)][노드 번호]&lt;/code&gt;와 같이 거리를 저장하기로 했고
각 노드에서 다시 방향을 유지하여 갈때는 원래 비용인 1을 사용하고, 방향을 바꿔서 갈때는 X + 1을 사용하여 가기로 설정했습니다.&lt;/p&gt;
&lt;p&gt;아래는 코드의 일부 내용입니다.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ll node;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ll cost;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ll direction;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ll&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ll&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; cost&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ll&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; direction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(node), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cost), &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(direction) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  bool&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; operator&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Edge&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; to.cost;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ll N, M, X;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; edges;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ll dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;220000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ll&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dijkstra&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ll&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  priority_queue&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pq;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ll&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s, X, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][s] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][s] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; X;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 모든 엣지를 역방향으로 바꾸면서 시작하는 경우&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;empty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Edge cur &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (cur.cost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist[cur.direction][cur.node]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;continue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 만약 정방향으로 간다면&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nxt : edges[cur.direction][cur.node]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dist[cur.direction][nxt.node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.cost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nxt.cost) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dist[cur.direction][nxt.node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.cost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nxt.cost;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(nxt.node, dist[nxt.direction][nxt.node], cur.direction));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 만약 역방향으로 바꿔서 간다면&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nxt : edges[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.direction][cur.node]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.direction][nxt.node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.cost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; X &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nxt.cost) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.direction][nxt.node] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.cost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; X &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nxt.cost;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        pq.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(nxt.node, dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.direction][nxt.node], &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cur.direction));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; min&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][N], dist[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][N]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; solve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  for1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, M) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ll a, b; cin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    edges[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][a].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push_back&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    edges[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][b].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push_back&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Edge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  cout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dijkstra&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;f---smooth-occlusion&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_f&quot;&gt;F - Smooth Occlusion&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;풀이 X..&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;g---minimum-steiner-tree-2&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc395/tasks/abc395_g&quot;&gt;G - Minimum Steiner Tree 2&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;풀이 X..&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;무난하게 5문제를 풀이하고나서, F, G번을 트라이할만한 시간이 남은 competition이었습니다.
다만, 오랜 기간동안을 손을 놓아서인지 구현 속도가 느리기도 했고 조금이라도 어려운 요소가 섞이니 접근이 잘 안되고 있습니다.
백준에서 골드/플레 문제 풀이를 해야할 것 같습니다..&lt;/p&gt;</content:encoded><category>atcoder</category><category>문제풀이</category><author>koa (김신건)</author></item><item><title>새로운 시작을 준비하며, 그렙을 떠나 다시 학교로 돌아갑니다</title><link>https://shinkeonkim.com/posts/my-life/2025-02-26-grepp-memoir/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/my-life/2025-02-26-grepp-memoir/</guid><description>그렙에서의 지난 여정을 돌아보며, 앞으로의 새로운 도전을 위해 대학으로 복학하기로 한 결심과 그 배경에 대해 공유하는 회고록입니다.</description><pubDate>Wed, 26 Feb 2025 04:25:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;새로운-시작을-준비하며-그렙을-떠나-다시-학교로-돌아갑니다&quot;&gt;새로운 시작을 준비하며, 그렙을 떠나 다시 학교로 돌아갑니다&lt;/h2&gt;
&lt;p&gt;안녕하세요, 백엔드개발자 김신건, Koa 입니다.
저는 2019년부터 함께한 그렙(Grepp)을  떠나, 다시 대학교로 돌아가기로 결심했습니다.&lt;/p&gt;
&lt;p&gt;오랜 시간 인턴부터 정직원까지 쌓아온 경험과 애정이 깊은 회사지만, 지금은 제 자신을 더 깊이 탐구하기 위해 돌아가야 할 시점이라 느끼게 되었습니다.
이 결정은 분명 쉽지 않았습니다. 이 결정이 또 다른 성장의 기회가 될 거라 믿습니다.&lt;/p&gt;
&lt;p&gt;그렙과의 지난 여정을 정리하면서, 저의 지난 발자취에 대해서도 공유해보고자 합니다.&lt;/p&gt;
&lt;h2 id=&quot;c-언어-하나로-바뀐-꿈&quot;&gt;C 언어 하나로 바뀐 꿈&lt;/h2&gt;
&lt;p&gt;저는 개발자를 꿈꾸던 사람이 아닙니다.&lt;/p&gt;
&lt;p&gt;저는 어릴 적부터 기계공학자를 꿈꾸었고, 기계나 로봇을 설계하고, 설계한 것이 실제로 움직이는 걸 보면 가슴이 뛰는 아이였습니다. 하지만 “과연 내가 기계를 아주 잘 설계하고, 활용할 수 있는 사람인가?” 하는 근본적인 의문이 늘 따라다녔습니다. 그런 고민을 품고 지내던 중, 고등학교(한민고등학교) 방과 후 수업에서 C 프로그래밍을 처음 접했습니다.&lt;/p&gt;
&lt;p&gt;처음에는 그저 호기심으로 시작했던 C 언어 공부였지만, 코드업(CodeUp) 사이트의 기초 100제를 풀어나가는 과정이 꽤 재미있었습니다. &lt;code&gt;“나, 이거 꽤 잘하는 것 같은데?”&lt;/code&gt; 라는 작은 자신감이 쌓인 후, 300문제, 400문제를 연달아 풀었습니다. 그리고 곧장 정보선생님에게 찾아가 지금 당장 해야하는 공부에 댈해서 질문했습니다. 그렇게 알고리즘과 자료구조에도 본격적으로 입문했습니다. (그렇게 내신 성적은 점점 떨어졌지만요)&lt;/p&gt;
&lt;p&gt;이때부터 여러 알고리즘 대회에 참가하기 시작했습니다. 다른  대회 참가자들에 비해 늦게 출발한 만큼 화려한 수상 성적을 거두진 못했습니다. 그래도 꾸준히 노력한 덕분에 가끔씩 장려상이나 동상을 받았고, 그 경험들이 저에겐 큰 동기부여가 되었습니다. 그리고 그 많은 대회 속에서 프로그래머스(Programmers) 사이트를 통해 대회가 진행되었는데, 이 플랫폼을 운영하는 기업이 바로 &lt;code&gt;그렙&lt;/code&gt;이라는 사실을 훗날 알게 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;소프트웨어-특기자-전형으로-대학에-입학하다&quot;&gt;소프트웨어 특기자 전형으로 대학에 입학하다&lt;/h2&gt;
&lt;p&gt;고등학교 시절 학업 성적은 점점 떨어졌지만, 그동안 쌓아온 대회 수상 기록, 소논문 등으로 소프트웨어 특기자 전형에 도전했습니다. 6개의 대학에 지원했지만 그 중 5개는 고배를 마셨고, 마지막 한 곳에서 극적으로 합격 통지를 받았습니다. 그렇게 대학교에 입학해, 1학년 1학기를 보내며 여러 수업을 들었고, 방학 시즌에 현장실습을 하면 학점과 인턴 월급을 함께 받을 수 있다는 사실을 알게 되었습니다.&lt;/p&gt;
&lt;p&gt;당시 여러 회사에 지원했지만, 최종적으로 그렙의 알고리즘 콘텐츠 제작 인턴으로 합격하여 방학을 그렙에서 보내게 되었습니다. 이 시기가 저와 그렙의 인연이 본격적으로 시작된 때입니다.&lt;/p&gt;
&lt;h2 id=&quot;1학년-1학기-방학-그렙에서-첫-인턴십&quot;&gt;1학년 1학기 방학, 그렙에서 첫 인턴십&lt;/h2&gt;
&lt;p&gt;처음 회사로 출근하여, &lt;code&gt;“내가 지금까지 공부해온 게 실제로 이렇게 쓰이는구나!”&lt;/code&gt;라는 사실이 무척 신기했습니다. 특히 제가 대회에서 사용했던 프로그래머스 서비스 내의 알고리즘 콘텐츠/문제들을 제작하는 과정에 기여할 수 있었다는 점이 큰 보람으로 다가왔습니다.&lt;/p&gt;
&lt;p&gt;그래서 저는 최대한 열심히 업무를 수행했고, 저 스스로의 최대한의 역량을 발휘했습니다. 그리고 그렙에서 인연을 이어가며, 2학년 1학기 방학 때까지 알고리즘 콘텐츠 제작자로 계속 근무할 기회를 얻었습니다.&lt;/p&gt;
&lt;h2 id=&quot;2학년-이후-웹-개발로-시야를-넓히다&quot;&gt;2학년 이후, 웹 개발로 시야를 넓히다&lt;/h2&gt;
&lt;p&gt;1학년에서 2학년 사이 기간 동안 스스로 한계를 느끼기도 했습니다. &lt;code&gt;“내가 잘하는 건 알고리즘 문제 풀이뿐인가?”&lt;/code&gt;라는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;1학년의 저는 알고리즘만 아는 코더였던거죠. 그냥 문제 풀이만 할 줄 아는 사람이었습니다. 그것도 깊게 하지 못하는.
굉장히 스스로 부끄러웠고, 발전이 되고 있지 않았다는 것을 느꼈습니다. 마침 주변 지인들의 조언으로 웹 개발 동아리에 들어가게 됐고, 그곳에서 처음 웹 프레임워크와 프로젝트 구축 방법을 익혔습니다. 2학년 2학기가 끝나갈 즈음, “알고리즘 관련 말고, 웹 개발로 인턴을 할 수 있는 곳이 없을까?” 고민하던 차에, 그렙에서 개발 인턴으로 근무해볼 생각이 없는지 제안을 받았습니다. 덕분에 그렙과의 인연은 또 다른 국면에 접어들었습니다.&lt;/p&gt;
&lt;h2 id=&quot;정직원으로-그렙에-합류-그리고-군-복무&quot;&gt;정직원으로 그렙에 합류, 그리고 군 복무&lt;/h2&gt;
&lt;p&gt;방학 인턴으로 웹 개발에 본격 뛰어들면서, 제가 실제로 사용하던 프로그래머스 서비스를 직접 개발하게 된다는 점이 참으로 매력적이었습니다. &lt;code&gt;Ruby on Rails&lt;/code&gt;를 처음 접했고, &lt;code&gt;Vue.js&lt;/code&gt;도 함께 다루면서 프론트엔드와 백엔드 양쪽을 모두 맛볼 수 있었습니다.&lt;/p&gt;
&lt;p&gt;방학이 끝난 후 선택지가 생겼습니다. “계속 학교를 다닐까, 아니면 휴학 후 정직원으로 일하며 더 빠르게 성장할까?” 고민 끝에 저는 그렙에서 정직원으로 근무하기로 했습니다. 이후 2021년, 2022년에 걸쳐 &lt;code&gt;채용사업부&lt;/code&gt;의 개발자로서 &lt;code&gt;프로그래머스 채용 서비스&lt;/code&gt;와 비즈니스 서비스를 구축하고 개선하는 업무를 맡았습니다. 점차 백엔드 분야에 더 흥미를 느끼게 되어 서버 쪽을 심도 있게 파고들면서, 개발자로서의 시야를 넓혀갔습니다.&lt;/p&gt;
&lt;p&gt;그러나 그 시기에 산업기능요원 제도가 바뀌면서, 저처럼 현역 판정인 사람은 산업체 복무가 불가능해졌습니다. 결국 군대를 더 이상 미룰 수 없다고 판단해 갑작스럽게 군대를 가게 되었습니다. 인생 계획이 어그러진 느낌이었지만, 한편으로는 “군대에서 조금이라도 휴식과 함께 생각을 정리할 수 있는 기회가 있겠지?”라는 희망도 있었습니다. (물론 자대 배치 이후의 현실은 그렇게 간단하지 않았지만요. &lt;a href=&quot;https://shinkeonkim.com/posts/my-life/2024-05-28-army-memoir/&quot;&gt;군 복무 이야기&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;전역-후-다시-그렙으로-복직--그리고-또-한-번의-고민&quot;&gt;전역 후, 다시 그렙으로 복직 … 그리고 또 한 번의 고민&lt;/h2&gt;
&lt;p&gt;군 복무를 마치고 2024년 7월, 다시 그렙에 복직했습니다. 이번에는 교육솔루션팀에 합류해 교육 서비스를 개발하는 일을 맡았습니다. 8개월 정도 근무하면서, 안정적인 루틴과 급여 덕분에 편안함을 느꼈지만, 그 편안함 한편에서 &lt;code&gt;“내가 진짜 하고 싶은 건 뭘까?”&lt;/code&gt;라는 고민이 점점 커졌습니다.&lt;/p&gt;
&lt;p&gt;계속해서 새로운 기능을 만드는 데도 점차 흥미를 잃어가고, 동시에 사적인 프로젝트나 지식 탐구에도 예전 같은 열정을 쏟지 못하고 있다는 걸 깨달았습니다. 군대에 가기 전, “나는 성장하고 싶어!”라고 불타올랐던 그 마음이 어딘가 녹슬어버린 느낌이었습니다. 그러다 보니, &lt;code&gt;&quot;알고리즘만 아는 코더&quot;&lt;/code&gt; 시절처럼 정체된 제가 부끄러웠습니다. &lt;code&gt;“안정감만 쫓고 있으면, 결국 제자리걸음 아닌가?”&lt;/code&gt;라는 생각과 후회감이 들었고, 이번에는 스스로를 다시 새로운 환경에 던져 몰아붙여야겠다는 결심을 하게 되었습니다.&lt;/p&gt;
&lt;p&gt;그래서 2025년 2월을 기점으로, 저는 그렙을 떠나 다시 &lt;code&gt;대학으로 복학&lt;/code&gt;하기로 마음먹었습니다. 학교에서 좀 더 이론적인 공부를 보완하고, 새롭게 떠오르는 기술들에 집중해보고 싶습니다. 그 이후, 또 새로운 곳에 뛰어들어 가보고 싶습니다.&lt;/p&gt;
&lt;p&gt;모든 선택에는 리스크가 따르지만, 지금이 아니면 안 될 것 같습니다. 물론 이 선택이 나중에 후회로 남을 수도 있습니다. 하지만 지금 이 시점에서 &lt;code&gt;“기회비용을 감수하면서도 성장을 택하겠다”&lt;/code&gt;라는 다짐이, 오히려 저에겐 강력한 동력이 될 것 같다는 생각이 듭니다.&lt;/p&gt;
&lt;h2 id=&quot;떠남은-끝이-아니라-또-다른-도약&quot;&gt;떠남은 끝이 아니라 또 다른 도약&lt;/h2&gt;
&lt;p&gt;저는 아직도, 계속해서 제 자신을 &lt;code&gt;“부족한 개발자”&lt;/code&gt;라고 생각합니다.&lt;/p&gt;
&lt;p&gt;특별히 잘하는 한 분야가 확고하게 잡혀 있는 것도 아니고, 일에 완전히 몰입하는 것도 잠시 주춤했던 때가 있었습니다.
그럼에도 &lt;code&gt;&quot;스스로를 몰아붙이고, 낯선 환경에서 새로운 경험에 뛰어드는 것&quot;&lt;/code&gt; 만큼은 잘한다고 단언할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 결정이 누군가에게는 이해되지 않을 수도 있고, 저조차 훗날 후회할지 모릅니다. 하지만 그 후회조차도 분명 저를 더 나은 길로 이끄는 &lt;code&gt;&quot;새로운 동기부여&quot;&lt;/code&gt;가 될 것이라 믿습니다.&lt;/p&gt;
&lt;p&gt;그렙에서 함께했던 동료들, 그리고 프로그래머스를 통해 만났던 많은 분들께 감사 인사를 전합니다.
&lt;code&gt;&quot;새로운 도전에 뛰어들려는 지금 이 순간&quot;&lt;/code&gt;이 훗날 제가 더 큰 성장을 할 시작점이 되리라 믿습니다.&lt;/p&gt;
&lt;p&gt;감사합니다.&lt;/p&gt;</content:encoded><category>회고록</category><category>그렙</category><category>프로그래머스</category><author>koa (김신건)</author></item><item><title>Atcoder Beginner Contest 394 후기</title><link>https://shinkeonkim.com/posts/atcoder/2025-02-23-abc-394/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/atcoder/2025-02-23-abc-394/</guid><description>2025년 2월 22일에 진행된 Atcoder Beginner Contest 394에서 풀이한 문제들과 풀이 방법, 그리고 대회 경험을 공유하는 후기입니다.</description><pubDate>Sun, 23 Feb 2025 05:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;개요&quot;&gt;개요&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394&quot;&gt;대회 링크&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;어제 (2/22) 저녁 9시부터 10시 40분까지 진행된 [[Atcoder]] Beginner Contest 394 후기입니다.&lt;/p&gt;
&lt;p&gt;총 7문제 중 5문제를 풀었습니다.
A ~ D까지 4문제는  대회 시작후 13분 안에 풀이했지만 E번 정확한 풀이를 생각해내는데 60분을 썼고, F번 문제를 읽고 끝났습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/editorial&quot;&gt;공식 에디토리얼 (해설)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;풀이&quot;&gt;풀이&lt;/h1&gt;
&lt;h2 id=&quot;a---22222&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_a&quot;&gt;A - 22222&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;단순히 주어지는 숫자 문자열에서 2의 개수만큼 다시 2를 출력하는 문제입니다.
빠르게 구현하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;b---cat&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_b&quot;&gt;B - cat&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;주어지는 문자열을 문자열의 길이를 기준으로 정렬 후 차례대로 붙여 출력하는 문제입니다.
빠르게 구현하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;c---debug&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_c&quot;&gt;C - Debug&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;주어지는 문자열에서 WA 라는 문자열을 AC 라는 문자열로 계속해서 대체할 때, 최종 문자열을 구하는 문제입니다.&lt;/p&gt;
&lt;p&gt;예를 들어, WWA 라는 문자열이 있으면 WWA → WAC → ACC 로 변합니다.&lt;/p&gt;
&lt;p&gt;저는 단순히 처음에는 스택을 활용할까 생각했지만, 문자가 변하는 양상을 보니 &lt;code&gt;W*X개+A*1개 &lt;/code&gt; 의 형태를 갖춰야만 바뀐다는 걸 캐치하고, W의 개수를 세다가 A 문자가 등장하면 대체하는 식으로 코드를 구성했습니다.&lt;/p&gt;
&lt;h2 id=&quot;d---colorful-bracket-sequence&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_d&quot;&gt;D - Colorful Bracket Sequence&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;전형적인 안정적인 괄호 문자열 문제입니다. 다만, 괄호의 종류를 (), &amp;#x3C;&gt;, [] 로 해두었습니다.
따라서, 관련 처리를 하면서 스택으로 해결하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;e---palindromic-shortest-path&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_e&quot;&gt;E - Palindromic Shortest Path&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;그래프가 주어집니다. 단, 정점이 아닌 “간선”마다 문자가 기록되어 있는 그래프입니다.
이때 모든 정점 사이의 페어마다, 간선의 문자열을 나열해서 팰린드롬이 되도록 하는 경로의 최소 길이를 구해야 합니다.&lt;/p&gt;
&lt;p&gt;처음에 저는 BFS로 정점마다 출발하여 팰린드롬인지 파악하는 완전탐색으로도 1억 내외로 연산 수가 떨어지면 시간안에 들지 않을까라는 생각으로 트라이했습니다. 하지만 TLE가 나왔고, 다른 방법을 생각해야 했습니다.&lt;/p&gt;
&lt;p&gt;이때 굳이 간선의 정보가 주어지고 있는데, 정점 기준으로 탐색할 필요가 있을까 생각이 들었습니다.
그래서 모든 간선은 일단 문자가 1개로 구성된 팰린드롬이고, 양 옆에 같은 문자를 이어 붙이면 팰린드롬이 되지 않을까 라는 생각으로 알고리즘을 구성했습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;먼저 정방향 간선 정보와 역방향 간선 정보를 저장한다.&lt;/li&gt;
&lt;li&gt;모든 간선을 큐에 넣는다. (해당 간선은 길이가 1인 팰린드롬이기 때문에, { 시작점, 끝점, 길이=1 }) 와 같이 넣는다.&lt;/li&gt;
&lt;li&gt;아래에서 설명 추가 과정 필요&lt;/li&gt;
&lt;li&gt;큐의 맨 앞에 있는 간선에서 정방향 간선 정보와 역방향 간선 정보를 참조해, 앞 뒤로 같은 문자을 붙일 수 있느지, 붙일 수 있다면 그것이 현재 최소 길이인지 확인해서 갱신 후 큐에 다시 넣는다.&lt;/li&gt;
&lt;li&gt;갱신된 모든 길이를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 방법의 이점은 일단 간선의 정보만 다룬다는 것이고, 만들어진 문자열들이 팰린드롬인지를 체크할 필요가 없다는 것입니다.&lt;/p&gt;
&lt;p&gt;그리고 이 방식은 홀수 길이인 경우만 알아낼 수 있는데 따라서 3번 과정에서 모든 i에 대해  i → i 로 가는 가상의 경로에 현재까지의 팰린드롬 길이가 0이다를 집어넣어놓으면 됩니다. { 출발점=i, 끝점=i, 길이=0} 과 같이 말입니다.&lt;/p&gt;
&lt;h2 id=&quot;f---alkane&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_f&quot;&gt;F - Alkane&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;그래프 정보가 주어집니다.
이때 아래 조건을 만족하는 부분 그래프의 최대 크기를 구합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해당 그래프는 무향 그래프다.&lt;/li&gt;
&lt;li&gt;모든 정점의 차수는 1 또는 4이다. (해당 정점에 이어지는 간선 수)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;딱히 떠오르는 해답이 없었습니다…&lt;/p&gt;
&lt;h2 id=&quot;g---dense-buildings&quot;&gt;&lt;a href=&quot;https://atcoder.jp/contests/abc394/tasks/abc394_g&quot;&gt;G - Dense Buildings&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;문제조차 읽지 못했습니다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot;&gt;결론&lt;/h1&gt;
&lt;p&gt;7문제 중 5문제를 풀이하면서 점차 예전의 실력을 되찾아가는 느낌이 들었습니다.
언젠가 모든 문제를 막힘없이 자연스럽게 다 푸는 실력까지 도달하고 싶습니다.&lt;/p&gt;</content:encoded><category>atcoder</category><category>문제풀이</category><author>koa (김신건)</author></item><item><title>2022년 11월부터 시작된 1년 6개월 회고록</title><link>https://shinkeonkim.com/posts/my-life/2024-05-28-army-memoir/</link><guid isPermaLink="true">https://shinkeonkim.com/posts/my-life/2024-05-28-army-memoir/</guid><description>군대에서의 1년 6개월을 돌아보며, 그 기간 동안의 공부와 앞으로의 계획에 대해 정리한 회고록입니다.</description><pubDate>Tue, 28 May 2024 06:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;길면서 짧았던 군 생활을 마무리하며, 회고록을 작성한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;서론&quot;&gt;서론&lt;/h2&gt;
&lt;p&gt;2022년 11월 28일, 논산 훈련소로 입대했습니다.&lt;/p&gt;
&lt;p&gt;같이 오신 부모님과 되게 쿨한 작별 인사를 하고, 터벅 터벅 들어갔습니다. 이전에는 입소식도 했다고 하지만, 저는 코로나 막바지라 그런 행사 없이 바로 입소 관련 절차를 시작했습니다.&lt;/p&gt;
&lt;p&gt;훈련소 내에서도 여러 일이 있었지만.. 시간이 바로 흘러 어느덧 수료식이었습니다. 수료식이 끝나고 부모님과 외출/면회를 하면서 저의 자대 배치 결과를 확인했습니다.&lt;/p&gt;
&lt;p&gt;특전사령부.&lt;/p&gt;
&lt;p&gt;????????? 저는 굉장히 당황스러웠고, &lt;code&gt;왜? 내가 어째서? 이게 맞아?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;일단 특전사령부는 어디있지 하고 찾아보았고, 경기도 이천에 있다는 것을 확인했습니다. 와, 그래도 집 앞에 있는 부대로 가는구나 싶어서 신났습니다. (본가가 이천입니다)&lt;/p&gt;
&lt;p&gt;그렇게 외출에서 복귀하고 자대 배치일을 기다려서, 그날이 왔습니다.&lt;/p&gt;
&lt;p&gt;하지만 뭔가 이상했습니다. 이천으로 가는 길이라고 하기엔 너무 멀었고, 담당 간부님은 제가 전남 담양으로 간다고 말씀하셨습니다. 거기는 어디지..? 하고 물어보니 11공수특전여단이었고, 특전사령부의 예하 여단으로 가는 것이었습니다. &lt;code&gt;아.. 예하 부대는 표기가 안되는 거였구나&lt;/code&gt; 싶었고, 그렇게 전남 담양에서의 군 생활이 시작되었습니다.&lt;/p&gt;
&lt;p&gt;자대 배치 후, 군 생활이 새로 시작되었다 할만큼 굉장히 많은 업무와 생활 룰(?)을 배웠고, 숙지했습니다.&lt;/p&gt;
&lt;p&gt;그 이후, 여러 훈련도 하고, 업무/공부/운동을 반복하다 보니 1년 반이 흘렀습니다. (자세한 내용은 스킵. 알고 싶으면 술 사주면서…ㅎㅎ)&lt;/p&gt;
&lt;p&gt;이제 한번 어떤 걸 공부했었는지 정리해보려 합니다. (군 관련 내용은 일부러 안 적었습니다.)&lt;/p&gt;
&lt;h2 id=&quot;군대에서-공부하기&quot;&gt;군대에서 공부하기&lt;/h2&gt;
&lt;h3 id=&quot;자격증을-땄다&quot;&gt;자격증을 땄다.&lt;/h3&gt;
&lt;p&gt;네트워크관리사 2급 자격증을 군생활 막바지에 땄습니다.&lt;/p&gt;
&lt;p&gt;사실 필기 시험은 진작에 통과했었습니다. &lt;code&gt;나중에 따야지..따야지&lt;/code&gt; 하다가 막바지에 진짜 이제는 해야한다는 생각을 가지고 나중에서야 땄습니다..&lt;/p&gt;
&lt;p&gt;그리고 전역 이틀 전에 SQLD 시험을 응시했고, 아마.. 합격이지 않을까 생각합니다.&lt;/p&gt;
&lt;h3 id=&quot;백준-문제라도-풀자&quot;&gt;백준 문제라도 풀자.&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/Untitled.png&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/Untitled%201.png&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;입대 전(2451문제) → 24.05.01(4034문제)로 약 1500문제 이상을 풀었습니다.  그리고 군생활 도중에 다이아5에서 다이아4로도 승급했습니다.&lt;/p&gt;
&lt;p&gt;뭐.. 최대한 많은 문제와 할 수 있는 공부는 다 해보자라는 생각으로 계속 했던 것 같습니다. 그 덕분에 시간도 금방 간 것 같습니다.&lt;/p&gt;
&lt;h3 id=&quot;강의를-듣자&quot;&gt;강의를 듣자.&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://code.plus&quot;&gt;code.plus&lt;/a&gt; 사이트(스타트링크)에서 제공하는 알고리즘 고급으로 가는 연결고리 강의를 들었습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/code.plus_course_47_certification.png&quot; alt=&quot;code.plus_course_47_certification.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/b08e135b-b20b-4809-8f51-c043a0f6860f.png&quot; alt=&quot;code.plus_course_46_certification.png&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인프런 - &lt;a href=&quot;https://www.inflearn.com/course/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%95%B5%EC%8B%AC%EC%9D%B4%EB%A1%A0-%EA%B8%B0%EC%B4%88/dashboard&quot;&gt;외워서 끝내는 네트워크 핵심 이론 - 기초&lt;/a&gt;, &lt;a href=&quot;https://www.inflearn.com/course/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%95%B5%EC%8B%AC%EC%9D%B4%EB%A1%A0-%EC%9D%91%EC%9A%A9/dashboard&quot;&gt;외워서 끝내는 네트워크 핵심 이론 - 응용&lt;/a&gt; 강의를 수강했습니다. 네트워크 관리사 자격증을 따면서 더 좋은 지식을 얻을 수 있을까 싶어 강의를 들었습니다. 상당히 좋은 강의였고 추천합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/Untitled%202.png&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/Untitled%203.png&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인프런 - &lt;a href=&quot;https://www.inflearn.com/course/%EC%99%B8%EC%9B%8C%EC%84%9C-%EB%81%9D%EB%82%B4%EB%8A%94-%EC%95%94%ED%98%B8%EA%B8%B0%EC%88%A0/dashboard&quot;&gt;외워서 끝내는 SSL과 최소한의 암호기술&lt;/a&gt; 강의를 수강했습니다. 위 네트워크 강의와 이어지는 부분이 많았고, 아직도 다시 찾아보면서 내용을 흡수하고 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://shinkeonkim.com/img/posts/2024/05/28/Untitled%204.png&quot; alt=&quot;Untitled&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;생활 코딩 - &lt;a href=&quot;https://www.inflearn.com/course/%EC%83%9D%ED%99%9C%EC%BD%94%EB%94%A9-%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D/dashboard&quot;&gt;정규표현식&lt;/a&gt;, &lt;a href=&quot;https://www.inflearn.com/course/web2-oauth2/dashboard&quot;&gt;Web2 : Oauth 2.0&lt;/a&gt; 강의를 수강했습니다. 이전부터 조금씩 내가 제대로 알고 있는 게 맞나 싶었던 부분을 긁어주었던 강의였습니다. 전역 후 다시 TIL 글로 정리해보려고 합니다.&lt;/li&gt;
&lt;li&gt;인프런 - &lt;a href=&quot;https://www.inflearn.com/course/sync-async-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4/dashboard&quot;&gt;[그림으로 이해하는] 동기 비동기의 개념에 대한 가장 직관적인 이해&lt;/a&gt;라는 강의를 수강했습니다. 사실 거의 아는 내용이 나왔고, 조금 아쉬웠습니다. 차라리 이 강의를 몇 년 전에 비동기에 대한 이해가 없었을 때 봤다면 더 좋았겠다 싶습니다.&lt;/li&gt;
&lt;li&gt;인프런 - &lt;a href=&quot;https://www.inflearn.com/course/mastering-svg/dashboard&quot;&gt;SVG 마스터&lt;/a&gt; 강의를 수강했습니다. 뭔가 유익했지만 군대라는 현실에 실습을 거의 못해봤습니다. 조금 아쉽기에 이걸 활용한 사이드 프로젝트를 진행할 수 있을까 고민해봅니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;앞으로는-뭘-하지&quot;&gt;앞으로는 뭘 하지?&lt;/h2&gt;
&lt;p&gt;남은 2024년 동안에는 아래 목표를 하나씩 차근차근 해보려 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;일간 목표&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;아침 또는 저녁마다 운동을 한다. (안 되면 산책이라도)&lt;/li&gt;
&lt;li&gt;매일 백준 문제 풀기 (적어도 1문제)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;월간 목표&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;적어도 2권의 책을 읽는다. (종류 상관 X)&lt;/li&gt;
&lt;li&gt;매주 일요일 혹은 매달 마지막에 무엇을 했는지 간단하게 정리하자.&lt;/li&gt;
&lt;li&gt;TIL Repo에 꾸준히 기록물을 남긴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;연간 목표&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;회사를 재밌게 다닌다.&lt;/li&gt;
&lt;li&gt;연간 백준 500 AC를 도전한다.&lt;/li&gt;
&lt;li&gt;자격증 / 운전 면허증 등의 자격증을 꾸준히 딴다.&lt;/li&gt;
&lt;li&gt;Codeforces / Atcoder Contest에 주기적으로 참여한다. (Blue를 계속 유지할 수 있는 실력이 될 수 있도록)&lt;/li&gt;
&lt;li&gt;Kaggle을 시작한다.&lt;/li&gt;
&lt;li&gt;간단한 사이드 프로젝트를 적어도 2개 진행한다.&lt;/li&gt;
&lt;li&gt;Spring과 K8s를 공부한다.&lt;/li&gt;
&lt;li&gt;재미있는 자취 생활을 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;아무튼&quot;&gt;아무튼&lt;/h2&gt;
&lt;p&gt;드디어 끝났습니다.&lt;/p&gt;
&lt;p&gt;제가 거창하게 나라를 지켰다고는 못하겠습니다.&lt;/p&gt;
&lt;p&gt;저는 누군가가 계속 이어서 지켜오던 한 자리를 지켰고, 자리를 지키는 것 만큼은 최선을 다했다 생각합니다.&lt;/p&gt;
&lt;p&gt;저는 앞으로 좀 더 다양한 경험을 사회에서 하면서 저의 길을 다시 걸어 가보겠습니다.&lt;/p&gt;
&lt;p&gt;지금까지, 그리고 앞으로 나라와 각자의 자리를 지켜주시는 군인 여러분 감사합니다.&lt;/p&gt;
&lt;p&gt;단결.&lt;/p&gt;</content:encoded><category>회고록</category><category>군대</category><author>koa (김신건)</author></item></channel></rss>