<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>0AndWild_log</title><link>https://0andwild.com/</link><description>Recent content on 0AndWild_log</description><generator>Hugo -- gohugo.io</generator><language>ko-KR</language><lastBuildDate>Fri, 20 Feb 2026 22:35:24 +0900</lastBuildDate><atom:link href="https://0andwild.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Baekjoonhub 크롬 익스텐션 커스텀</title><link>https://0andwild.com/posts/260220_baekjoonhub/</link><pubDate>Fri, 20 Feb 2026 22:35:24 +0900</pubDate><guid>https://0andwild.com/posts/260220_baekjoonhub/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Baekjoonhub 크롬 익스텐션 커스텀" /&gt;&lt;h1 id="baekjoonhub를-직접-커스텀하게-된-이유"&gt;&lt;a href="#baekjoonhub%eb%a5%bc-%ec%a7%81%ec%a0%91-%ec%bb%a4%ec%8a%a4%ed%85%80%ed%95%98%ea%b2%8c-%eb%90%9c-%ec%9d%b4%ec%9c%a0" class="header-anchor"&gt;&lt;/a&gt;BaekjoonHub를 직접 커스텀하게 된 이유
&lt;/h1&gt;&lt;p&gt;최근에 백준을 본격적으로 사용하게 되면서 &lt;code&gt;BaekjoonHub&lt;/code&gt; 익스텐션을 알게 됐음.&lt;br&gt;
처음 써보니 “풀이를 자동으로 GitHub에 올려준다”는 점은 정말 편했지만, 실제로 내 작업 방식에 맞추기엔 몇 가지 불편한 부분이 있었음.&lt;/p&gt;
&lt;p&gt;그래서 먼저 공식 저장소 이슈를 찾아봤음.&lt;br&gt;
확인해보니 나와 비슷한 고민을 가진 분들이 이미 있었고, 관련 기능을 추가하려는 PR도 올라와 있었음. 다만 당시 기준으로는 해당 기능을 바로 반영할 계획이 없다는 흐름이 보여서, 기다리기보다 내가 필요한 부분을 직접 커스텀해서 쓰기로 결정함.&lt;/p&gt;
&lt;p&gt;원본 저장소: &lt;a class="link" href="https://github.com/BaekjoonHub/BaekjoonHub" target="_blank" rel="noopener"
 &gt;https://github.com/BaekjoonHub/BaekjoonHub&lt;/a&gt;&lt;br&gt;
커스텀 저장소: &lt;a class="link" href="https://github.com/0AndWild/baekjoonhub_custom" target="_blank" rel="noopener"
 &gt;https://github.com/0AndWild/baekjoonhub_custom&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="내가-겪은-불편함"&gt;&lt;a href="#%eb%82%b4%ea%b0%80-%ea%b2%aa%ec%9d%80-%eb%b6%88%ed%8e%b8%ed%95%a8" class="header-anchor"&gt;&lt;/a&gt;내가 겪은 불편함
&lt;/h2&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;Java 파일명과 실행 엔트리(&lt;code&gt;Main.java&lt;/code&gt;)를 수동으로 맞춰야 하는 경우 발생&lt;/li&gt;
&lt;li&gt;기존에 풀어둔 문제를 한 번에 정리해서 올리기 어려움&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="그래서-커스텀에서-바꾼-부분"&gt;&lt;a href="#%ea%b7%b8%eb%9e%98%ec%84%9c-%ec%bb%a4%ec%8a%a4%ed%85%80%ec%97%90%ec%84%9c-%eb%b0%94%ea%be%bc-%eb%b6%80%eb%b6%84" class="header-anchor"&gt;&lt;/a&gt;그래서 커스텀에서 바꾼 부분
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Base Directory&lt;/code&gt; 지정 기능 추가&lt;/li&gt;
&lt;li&gt;티어 경로를 &lt;code&gt;Bronze/V&lt;/code&gt;처럼 세분화&lt;/li&gt;
&lt;li&gt;문제 디렉토리명 정규화&lt;/li&gt;
&lt;li&gt;Java 파일명 &lt;code&gt;Main.java&lt;/code&gt; 고정 + &lt;code&gt;package&lt;/code&gt; 자동 삽입&lt;/li&gt;
&lt;li&gt;백준 맞은 문제 전체 업로드(벌크 업로드) 기능 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="정리"&gt;&lt;a href="#%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;정리
&lt;/h2&gt;&lt;p&gt;이번 커스텀은 “기능을 새로 많이 만들자”보다는,&lt;br&gt;
실제로 문제를 풀고 정리하는 과정에서 반복되는 불편함을 줄이는 데 초점을 맞춤.&lt;/p&gt;
&lt;p&gt;공식 저장소에서도 이미 비슷한 요구가 있었던 만큼, 같은 고민을 가진 분들에게는 이 커스텀 방향이 참고가 될 수 있다고 생각함.&lt;/p&gt;</description></item><item><title>Websocket 이란? (RFC 6455)</title><link>https://0andwild.com/posts/260210_websocket/</link><pubDate>Wed, 21 Jan 2026 15:38:26 +0900</pubDate><guid>https://0andwild.com/posts/260210_websocket/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Websocket 이란? (RFC 6455)" /&gt;&lt;h2 id="서론"&gt;&lt;a href="#%ec%84%9c%eb%a1%a0" class="header-anchor"&gt;&lt;/a&gt;서론
&lt;/h2&gt;&lt;p&gt;이전 포스트인 웹소켓 이전의 양방향 통신에 이어서 Websocket 프로토콜 문서인 RFC 6455 를 읽어보며 Websocket 에 대해 상세하게 다뤄보고자 함.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;내용 중 자세한 설명을 위한 예시들은 AI를 이용하여 생성하였음.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="rfc-6455-the-websocket-protocol"&gt;&lt;a href="#rfc-6455-the-websocket-protocol" class="header-anchor"&gt;&lt;/a&gt;&lt;a class="link" href="%22https://datatracker.ietf.org/doc/html/rfc6455%22" &gt;RFC 6455&lt;/a&gt; (The Websocket Protocol)
&lt;/h2&gt;&lt;p&gt;Websocket 프로토콜의 목적은 서버와의 양방향 통신이 필요한 브라우저 기반 애플리케이션에 다중 Http 연결 (ex: XmlHttpRequest, long polling)에 의존하지 않는 매커니즘을 제공하는 것이라고 함. Websocket 은 TCP 위의 기본 메시지 프레이밍에 이어지는 초기 핸드쉐이크로 구성되어짐.&lt;/p&gt;
&lt;p&gt;과거 클라이언트와 서버간 양방향 통신이 필요했던 어플리케이션의 경우 HTTP 를 남용하여 서버 업데이트를 폴링하면서 그 위에 단에서의 알림을 별개의 HTTP 호출로 전송해야 했음 (RFC 6202).&lt;/p&gt;
&lt;p&gt;이 문제를 해결하는 방법은 양방향 트래픽에 단일 TCP 연결을 사용하는 방법임. Websocket 은 이 방식을 지원함.
websocket API 문서: &lt;a class="link" href="https://websockets.spec.whatwg.org//" target="_blank" rel="noopener"
 &gt;https://websockets.spec.whatwg.org//&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Websocket 은 프록시, 필터링, 인증 과 같은 기존 인프라 구조의 이점을 이용하기 위해 HTTP 를 전송계층으로 사용한는 양방향 통신기술을 대체하기위해 고안되었음.
기존 기술은 효율성과 신뢰성 사이의 절충안으로 구현되었음 그 이유는 HTTP 가 애초에 양방향 통신을 위해 구현된게 아니기 때문이라고 함.&lt;/p&gt;
&lt;p&gt;Websocket 프로토콜은 HTTP 인프라 환경에서 기존의 양방향 HTTP 기술들의 목표를 해결하기위한 시도를 함.
따라서 HTTP 80, 443 port 를 통해 작동하도록 설계되었으며, 현재 환경에서 복잡성을 수반하더라도 HTTP 프록시 및 중개자를 지원함.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;그렇다고 이 설계가 웹소켓을 HTTP 로 제한하지 않음. 향후 구현에서는 전체 프로토콜울 재구축하지 않고도 전용 포트를 사용하되 전체 프로토콜을 재구축하지 않고도 가능하다고 함&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;문서에서 이부분을 강조하는데 이는 대화형 메시징의 트래픽 패턴이 표준 HTTP 트래픽과 유사하지 않아 일부 구성 요소에 비정상적인 부하를 유발할 수 있기 때문에 중요하다고 함.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="protocol-overview"&gt;&lt;a href="#protocol-overview" class="header-anchor"&gt;&lt;/a&gt;Protocol Overview
&lt;/h2&gt;&lt;p&gt;Websocket 프로토콜은 handshake, data transfer 이렇게 두 파트로 나뉨.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Client 의 handshake 요청
GET /chat HTTP/1.1
 Host: server.example.com
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
 Origin: http://example.com
 Sec-WebSocket-Protocol: chat, superchat
 Sec-WebSocket-Version: 13

// Server 의 handshake 응답
HTTP/1.1 101 Switching Protocols
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
 Sec-WebSocket-Protocol: chat
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Client 는 Request-Line 형식을 따르고 Server 는 Status-Line 형식을 따름. (&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc2616#page-35" target="_blank" rel="noopener"
 &gt;RFC 2616&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Client 와 Server 양쪽 모두 handshake 를 보내고 성공이 되었다면 data transfer 가 시작됨.
이것이 각자 독립적으로 어떠한 요청 없이 자신의 의지로 데이터를 전송할 수 있는 양방향 통신 채널임.&lt;/p&gt;
&lt;p&gt;이제 Client 와 Server 는 websocket spec 에서 message 라고 지칭하는 단위로 데이터를 주고받음.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Websocket message 는 특정 네트워크 계층의 프레임과 일치하지 않을 수 있음.&lt;/code&gt; 그 이유는 중간 장치에 의해서 &lt;em&gt;분할된 메세지가 병합되거나 그 반대의 경우가 발생&lt;/em&gt;할 수 있기 때문이라고 함.&lt;/p&gt;
&lt;p&gt;동일한 message에 속하는 각 프레임은 동일한 유형의 데이터를 포함함. 크게 &lt;code&gt;textual data&lt;/code&gt;, &lt;code&gt;binary data&lt;/code&gt;, &lt;code&gt;control frames&lt;/code&gt;(어플리케이션 데이터 전달이 아님 ex 프로토콜 수준의 신호용으로 연결 종료 신호) 가 있음.&lt;/p&gt;
&lt;p&gt;Websocket 프로토콜은 여섯 가지 프레임 유형을 정의하고 향후 사용을 위한 열 가지를 예약을 해둔다고 함.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Opcode&lt;/th&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;0x0&lt;/td&gt;
 &lt;td&gt;데이터&lt;/td&gt;
 &lt;td&gt;Continuation&lt;/td&gt;
 &lt;td&gt;이전 프레임의 연속 데이터&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0x1&lt;/td&gt;
 &lt;td&gt;데이터&lt;/td&gt;
 &lt;td&gt;Text&lt;/td&gt;
 &lt;td&gt;UTF-8 텍스트 데이터&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0x2&lt;/td&gt;
 &lt;td&gt;데이터&lt;/td&gt;
 &lt;td&gt;Binary&lt;/td&gt;
 &lt;td&gt;바이너리 데이터&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0x3~0x7&lt;/td&gt;
 &lt;td&gt;데이터&lt;/td&gt;
 &lt;td&gt;Reserved&lt;/td&gt;
 &lt;td&gt;향후 데이터 프레임 확장용 (5개)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0x8&lt;/td&gt;
 &lt;td&gt;제어&lt;/td&gt;
 &lt;td&gt;Close&lt;/td&gt;
 &lt;td&gt;연결 종료 요청&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0x9&lt;/td&gt;
 &lt;td&gt;제어&lt;/td&gt;
 &lt;td&gt;Ping&lt;/td&gt;
 &lt;td&gt;연결 상태 확인 (heartbeat)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0xA&lt;/td&gt;
 &lt;td&gt;제어&lt;/td&gt;
 &lt;td&gt;Pong&lt;/td&gt;
 &lt;td&gt;Ping에 대한 응답&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0xB~0xF&lt;/td&gt;
 &lt;td&gt;제어&lt;/td&gt;
 &lt;td&gt;Reserved&lt;/td&gt;
 &lt;td&gt;향후 제어 프레임 확장용 (5개)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="분할된-메세지가-병합되거나-반대의-경우가-발생할-수-있다"&gt;&lt;a href="#%eb%b6%84%ed%95%a0%eb%90%9c-%eb%a9%94%ec%84%b8%ec%a7%80%ea%b0%80-%eb%b3%91%ed%95%a9%eb%90%98%ea%b1%b0%eb%82%98-%eb%b0%98%eb%8c%80%ec%9d%98-%ea%b2%bd%ec%9a%b0%ea%b0%80-%eb%b0%9c%ec%83%9d%ed%95%a0-%ec%88%98-%ec%9e%88%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;분할된 메세지가 병합되거나 반대의 경우가 발생할 수 있다?
&lt;/h3&gt;&lt;p&gt;라는 부분이 이해가 잘 안갈 수 있는데 예시를 들어보겠음.&lt;/p&gt;
&lt;p&gt;우선 왜 분할(fragmentaion)이 발생하는지? 그 이유는 아래와 같음&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;MTU(Maximum Transmission Unit) 제한(네트워크 패킷 크기 제한 : 보통 1500 bytes)
일반적으로 1500 bytes 보다 큰 패킷은 경로상의 중간장치에서 더 작은 조각으로 나뉘어져 전송(단편화)됨. 예시로 지하터널의 높이 제한을 생각하면 됨.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;서버에서 특정 크기로 쪼개서 보내도록 설정되어 있을 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1번과 같은 맥락인데 프록시, 로드밸러서, API 게이트웨이 같은 중간장치가 큰 프레임을 쪼갬&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;메모리 효율: 대용량 데이터를 한번에 버퍼링하지 않기 위해 쪼갬&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다시 본론으로 돌아가서 병합되는 예시와 반대의 경우를 살펴보겠음&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1. Coalesced(병합)

(원본: 쪼개서 발송)
[Frame 1: FIN=0, opcode=text, &amp;#34;Hello &amp;#34;]
[Frame 2: FIN=0, opcode=continuation, &amp;#34;World&amp;#34;] 
[Frame 3: FIN=1, opcode=continuation, &amp;#34;!&amp;#34;]

(중간자가 위의 세 프레임을 받아서 하나의 프레임으로 합쳐서 전달함)
[Frame: FIN=1, opcode=text, &amp;#34;Hello World!&amp;#34;]

2. Split(분할)

(원본)
[Frame: FIN=1, &amp;#34;Hello World!&amp;#34;]

(중간자가 원본 프레임을 쪼갬)
[Frame 1: FIN=0, &amp;#34;Hello &amp;#34;]
[Frame 2: FIN=1, &amp;#34;World!&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;다음 예시로 Websocket 으로 실시간 주식 데이터를 수신하는 Spring boot 서버 (client 역할)와 금융 서버(server 역할) 가 있다고 가정하고 코드를 살펴보겠음.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;금융 서버에서 아래와 같이 하나의 message 를 쪼개서 보냈다고 가정을 하겠음. (중간에 병합되지 않는다고 가정)

[WebSocket Frame 1]
FIN: 0 (아직 끝 아님)
Opcode: 0x1 (text)
Payload: {&amp;#34;stockCode&amp;#34;:&amp;#34;005930&amp;#34;,&amp;#34;price&amp;#34;:71500,&amp;#34;vol

[WebSocket Frame 2] 
FIN: 0 (아직 끝 아님)
Opcode: 0x0 (continuation)
Payload: ume&amp;#34;:50000,&amp;#34;time&amp;#34;:&amp;#34;09:00:01&amp;#34;,&amp;#34;seller&amp;#34;:&amp;#34;

[WebSocket Frame 3]
FIN: 1 (이게 마지막)
Opcode: 0x0 (continuation)
Payload: foreign&amp;#34;,&amp;#34;buyer&amp;#34;:&amp;#34;institution&amp;#34;,...}
&lt;/code&gt;&lt;/pre&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebSocketHandler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TextWebSocketHandler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handleTextMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebSocketSession&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TextMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 여기서 message.getPayload()는 이미 완전한 message임&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// {&amp;#34;stockCode&amp;#34;:&amp;#34;005930&amp;#34;,&amp;#34;price&amp;#34;:71500,&amp;#34;volume&amp;#34;:50000,...} 전체가 옴&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;payload 가 완전한 message 인 이유는 Spring 이 내부적으로 Frame 1 과 2 가 FIN 이 0 이기 때문에 버퍼에 저장하고 이어붙이다가 Frame 3 에서 FIN 이 1 을 확인하고 버퍼 내용을 합쳐서 handleTextMessage() 를 호출하기 때문임.&lt;/p&gt;
&lt;p&gt;이런 부분을 공부해 보면서 느낀점은 평소 개발시에 프레임워크에서 이미 잘 구현된 기능들을 사용하다보니 이렇게 네트워크 레벨에서 일어나는 일들을 잘 모르고 지나치게 되는 것 같다는 생각이 들었음.&lt;/p&gt;
&lt;p&gt;다시 Websocket protocol 의 큰 틀을 정리해보면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handshake 와 Data transfer 이렇게 두 파트로 나뉨.&lt;/li&gt;
&lt;li&gt;Handshake 가 성공하면 이제 양방향 통신을 할 수 있는 상태가 되고 data transfer 가 이루어짐. 이때 websocket spec 에서 사용하는 data 단위인 &lt;code&gt;message&lt;/code&gt; 를 주고 받음.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Message&lt;/code&gt; 는 특정 네트워크 프레임과 일치하지 않을 수 있음. 중간장치에 의해 message 가 분할되거나 병합될 수 있기 때문&lt;/li&gt;
&lt;li&gt;Websocket 에 정의된 프레임은 data frame 3개, controle frame 3개 그리고 각각 예약 프레임 5개 씩 총 16개로 구성 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음으로는 Opening Handshake 가 어떻게 일어나는지 살펴보겠음.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="opening-handshake"&gt;&lt;a href="#opening-handshake" class="header-anchor"&gt;&lt;/a&gt;Opening Handshake
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;Header&lt;/th&gt;
 &lt;th style="text-align: center"&gt;용도&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Upgrade&lt;/td&gt;
 &lt;td style="text-align: center"&gt;프로토콜 업그레이드 요청&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Connection&lt;/td&gt;
 &lt;td style="text-align: center"&gt;연결 업그레이드 요청&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Sec-Websocket-Key&lt;/td&gt;
 &lt;td style="text-align: center"&gt;보안 키(Base64 인코딩된 16 bytes 랜덤 값)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Sec-Websocket-Version&lt;/td&gt;
 &lt;td style="text-align: center"&gt;프로토콜 버전(현재 13)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Sec-Websocekt-Protocol&lt;/td&gt;
 &lt;td style="text-align: center"&gt;서브프로토콜(선택)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Origin&lt;/td&gt;
 &lt;td style="text-align: center"&gt;브라우저에서 보내는 출처 정보&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;초기 handshake 는 HTTP 기반 서버와 중개자와 호환되도록 설계되어 서버와 통신하는 HTTP 클라이언트와 웹소켓 클라이언트 모두 단일 포트를 사용할 수 있음.
따라서 웹소켓 클라이언트의 handshake 는 HTTP 업그레이드 요청으로 이루어짐.&lt;/p&gt;
&lt;p&gt;Handshake 의 header 필드는 클라이언트 측에서 임의의 순서로 전송할 수 있으므로 서로 다른 header 필드가 수신되는 순서는 중요하지 않음.
클라이언트는 handshake 의 |Host| header 필드에 호스트명을 포함시켜 클라이언트와 서버 모두 사용중인 호스트에 대해 합의했는지 확인 할 수 있음.&lt;/p&gt;
&lt;p&gt;추가 header 필드는 Websocket 프로토콜에서 옵션을 선택하는데 사용됨.
일반적으로 사용하는 옵션으로 서브프로토콜 선택기 (Sec-Websocket-Protocol), 클라이언트가 지원하는 확장 목록(Sec-Websocket-Extension), Origin 필드 등이 있음. |Sec-Websocket-Protocol| 요청 header 필드는 클라이언트가 허용하는 서브프로토콜(웹소켓 프로토콜 위에 계층화된 어플리케이션 레벨 프로토콜)을 표시하는 데 사용할 수 있음. 서버는 허용가능한 프로토콜 중 하나 또는 아무것도 선택지 않을 수 있고 선택한 프로토콜을 나타내기 위해 handshake 에서 그 값을 echo 함.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Client 의 handshake 요청

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat 
Sec-WebSocket-Version: 13


// Server 의 handshake 응답

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat &amp;lt;- server 가 선택한 프로토콜을 echo 함
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;서버는 클라이언트의 handshake 를 수신하면 두 가지 정보를 응답에 포함해야 하는 데 첫번째는 Sec-Websocket-Accept 임.&lt;/p&gt;
&lt;h3 id="sec-websocket-accept-계산-방법"&gt;&lt;a href="#sec-websocket-accept-%ea%b3%84%ec%82%b0-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;Sec-Websocket-Accept 계산 방법
&lt;/h3&gt;&lt;p&gt;|Sec-Websocket-Accept| 필드는 서버가 websocket 연결을 수락할 의사가 있는지 여부를 나타냄.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== 를 가져옴&lt;/li&gt;
&lt;li&gt;dGhlIHNhbXBsZSBub25jZQ== + &amp;ldquo;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&amp;rdquo;&lt;/li&gt;
&lt;li&gt;SHA-1 해시 계산 : 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6
0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea&lt;/li&gt;
&lt;li&gt;Base64 인코딩 : &amp;ldquo;s3pPLMBiTxaQ9kYGzzhZRbK+xOo=&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 를 응답에 포함&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 Sec-Websocket-Key 를 만들고 상태값 101 을 같이 응답에 포함하여 handshake 를 수락했음을 client 에게 알려줌.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// 서버측에서 client 의 handshake 연결을 수락하고 정상적으로 연결이되면 상태코드로 101 을 가지게 됨. 101 이외의 상태 코드는 웹소켓 handshake가 완료되지 않은것임.
HTTP/1.1 101 Switching Protocols
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;클라이언트는 이 응답을 기반으로 Sec-Websocket-Accept 값이 예상된 값이 아니거나, 헤더 필드가 누락되었거나, HTTP 상태 코드가 101 이 아닌 경우를 확인하고 웹소켓 프레임은 전송되지 않음.&lt;/p&gt;
&lt;p&gt;다시 정리를 해보자면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handshake 는 HTTP 기반 프로토콜 upgrade 요청으로 시작됨&lt;/li&gt;
&lt;li&gt;header 의 순서는 상관없음&lt;/li&gt;
&lt;li&gt;서버의 연결 응답에는 Sec-Websocket-Key 를 이용해 Sec-Websocket-Accept 의 값을 계산하고 이와 함께 101 상태값을 내려주어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음으로는 Websocket 종료는 어떻게 일어나는지 살펴보겠음.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="closing-handshake"&gt;&lt;a href="#closing-handshake" class="header-anchor"&gt;&lt;/a&gt;Closing HandShake
&lt;/h2&gt;&lt;p&gt;Websocket 은 연결 종료시에도 handshake 방식으로 진행을 함.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;한쪽에서 종료를 나타내는 control frame(제어 프레임) 을 전송&lt;/li&gt;
&lt;li&gt;반대쪽에서 종료 control frame 으로 응답&lt;/li&gt;
&lt;li&gt;양쪽 모두 close frame 을 보내고 받으면 TCP 연결 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 자체 handshake 를 통해 종료를 하는 이유는 TCP closing handshake(FIN/ACK) 을 보완하기 위한 것으로 중간에 가로채는 프록시나 기타 중개자가 존재 할 경우 TCP closing handshake 가 항상 종단 간 신뢰할 수 있는 것은 아니라는 것에 근거한다고 함.&lt;/p&gt;
&lt;p&gt;종단간 신뢰할 수 없다는 부분을 좀 더 살펴보면 TCP closing handshake(4-way handshake) 를 사용하면 데이터 손실이 발생할 수 있기 때문임.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// TCP-Closing handshake

Client Server
 | |
 |-------- FIN -----------&amp;gt;| &amp;#34;나 보낼 거 다 보냄&amp;#34;
 |&amp;lt;------- ACK ------------| &amp;#34;알겠어&amp;#34;
 |&amp;lt;------- FIN ------------| &amp;#34;나도 다 보냄&amp;#34;
 |-------- ACK -----------&amp;gt;| &amp;#34;알겠어&amp;#34;
 | |
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 문서에서 말하는 핵심 문제는 중개자(프록시, 로드밸런서)가 있으면 TCP 종료가 end-to-end 로 전달되지 않을 수 있다는 것임.&lt;/p&gt;
&lt;p&gt;만약 TCP closing handshake 로 종료를 한다고 했을 때 예시를 살펴보겠음.&lt;/p&gt;
&lt;p&gt;플래그를 모를 수 있기 때문에 표를 첨부함.&lt;/p&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;SYN&lt;/td&gt;
 &lt;td&gt;연결 시작&lt;/td&gt;
 &lt;td&gt;3-way handshake 시작&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ACK&lt;/td&gt;
 &lt;td&gt;수신 확인&lt;/td&gt;
 &lt;td&gt;거의 모든 패킷에 포함&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;FIN&lt;/td&gt;
 &lt;td&gt;정상 종료&lt;/td&gt;
 &lt;td&gt;보낼 데이터 없을 때&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RST&lt;/td&gt;
 &lt;td&gt;강제 종료&lt;/td&gt;
 &lt;td&gt;에러 상황, 비정상 종료&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 상황 재현: 증권 서버가 데이터를 보내고 있는 중에 Client가 소켓을 닫음&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 증권 Server 측&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TextMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stockData1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 전송됨&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TextMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stockData2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 전송됨, 아직 Client가 안 읽음&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Client 측 - 갑자기 소켓 닫음&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// stockData2가 receive queue에 있는 상태에서 닫힘&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이때 OS 레벨에서 일어나는 일:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Client 의 receive queue]
+------------------+
| stockData2 (미처리) | ← 아직 애플리케이션이 안 읽음
+------------------+

socket.close() 호출됨
 ↓
OS: &amp;#34;어? 읽지 않은 데이터가 있는데 닫으라고?&amp;#34;
 ↓
OS가 RST 패킷 전송 (정상적인 FIN 대신)
 ↓
증권 Server: RST 수신
 ↓
증권 Server의 recv() 호출 실패 (Connection reset by peer)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="rst-vs-fin-차이"&gt;&lt;a href="#rst-vs-fin-%ec%b0%a8%ec%9d%b4" class="header-anchor"&gt;&lt;/a&gt;RST vs FIN 차이
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;FIN: &amp;#34;나 할 말 다 했어, 깔끔하게 끝내자&amp;#34;
RST: &amp;#34;비상! 연결 강제 종료! 뭔가 잘못됐어!&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;RST를 받은 증권 Server는:&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;/ul&gt;
&lt;p&gt;이걸 보면 이런 생각이 들 수 도 있음. 아니 그럼 HTTP request, response 에서도 똑같이 이 문제가 생길 수 있는거 아닌가?&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;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HTTP 예시

Client Proxy Server
 │ │ │
 │── GET /stock ─────────&amp;gt;│── GET /stock ─────────&amp;gt;│
 │ │ │
 │&amp;lt;── 200 OK + data ──────│&amp;lt;── 200 OK + data ──────│
 │ │ │
 │── FIN ────────────────&amp;gt;│ │
 │ │ (전달할 수도, 안 할 수도)
 │ │ │
 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;HTTP 는 stateless 하고 요청/응답이 끝나면 연결의 역할이 끝나기 때문에 비정상 종료되어도 큰 문제가 없음.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Websocket 예시

Client Proxy Server
 │ │ │
 │══ WebSocket 연결 (지속) ══════════════════════════│
 │ │ │
 │&amp;lt;── stockData1 ─────────│&amp;lt;── stockData1 ─────────│
 │&amp;lt;── stockData2 ─────────│&amp;lt;── stockData2 ─────────│
 │&amp;lt;── stockData3 ─────────│&amp;lt;── stockData3 ─────────│
 │ ... │ ... │
 │ │ │
 │── FIN ────────────────&amp;gt;│ │
 │ │ (전달 안 됨) │
 │ │ │
 │ │&amp;lt;── stockData4 ─────────│ ← Server는 계속 보냄!
 │ │&amp;lt;── stockData5 ─────────│
 │ │&amp;lt;── stockData6 ─────────│
 │ │ │
 │ │ Proxy 버퍼에 쌓임 │
 │ │ 또는 어디선가 손실 │
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;하지만 Websocket 은 stateful 하고 연결이 계속 유지되면서 데이터가 흐르기 때문에 문제가 됨.&lt;/p&gt;
&lt;p&gt;이러한 문제 때문에 Websocket 은 자체 handshake 를 통해 종료를 하는거임.
Close frame 은 애플리케이션 레이어 메세지라 proxy 가 반드시 전달을 해야함. TCP FIN 처럼 proxy 가 임의로 처리가 불가능함.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Client Proxy Server
 │ │ │
 │── Close Frame ────────&amp;gt;│── Close Frame ────────&amp;gt;│ ← 애플리케이션 레벨
 │ │ │
 │ │ Server: &amp;#34;아 종료구나&amp;#34; │
 │ │ 구독 해제 │
 │ │ 데이터 전송 중단│
 │ │ │
 │&amp;lt;── Close Frame ────────│&amp;lt;── Close Frame ────────│
 │ │ │
 │── FIN ────────────────&amp;gt;│── FIN ────────────────&amp;gt;│ ← 이제 TCP 종료
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="websocket-설계-철학-최소한의-프레이밍"&gt;&lt;a href="#websocket-%ec%84%a4%ea%b3%84-%ec%b2%a0%ed%95%99-%ec%b5%9c%ec%86%8c%ed%95%9c%ec%9d%98-%ed%94%84%eb%a0%88%ec%9d%b4%eb%b0%8d" class="header-anchor"&gt;&lt;/a&gt;WebSocket 설계 철학: 최소한의 프레이밍
&lt;/h2&gt;&lt;h3 id="핵심-원칙"&gt;&lt;a href="#%ed%95%b5%ec%8b%ac-%ec%9b%90%ec%b9%99" class="header-anchor"&gt;&lt;/a&gt;핵심 원칙
&lt;/h3&gt;&lt;p&gt;RFC 6455에서 WebSocket의 설계 원칙을 이렇게 명시하고 있음:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;The WebSocket Protocol is designed on the principle that there should be minimal framing&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;WebSocket이 제공하는 프레이밍은 딱 두 가지 목적만을 위한 것임:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;스트림 → 메시지 단위 변환&lt;/strong&gt;: TCP는 연속적인 바이트 스트림인데, 애플리케이션은 &amp;ldquo;메시지&amp;rdquo; 단위로 생각함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;텍스트 vs 바이너리 구분&lt;/strong&gt;: UTF-8 텍스트인지 임의의 바이너리 데이터인지 구분&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그 외의 모든 메타데이터(메시지 타입, 라우팅, 인증 등)는 &lt;strong&gt;애플리케이션 레이어에서 알아서 처리하라&lt;/strong&gt;는 철학임.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="tcp의-문제-메시지-경계가-없음"&gt;&lt;a href="#tcp%ec%9d%98-%eb%ac%b8%ec%a0%9c-%eb%a9%94%ec%8b%9c%ec%a7%80-%ea%b2%bd%ea%b3%84%ea%b0%80-%ec%97%86%ec%9d%8c" class="header-anchor"&gt;&lt;/a&gt;TCP의 문제: 메시지 경계가 없음
&lt;/h3&gt;&lt;p&gt;TCP는 바이트 스트림 프로토콜임. 데이터가 파이프를 타고 흐르듯 연속적으로 전달될 뿐, &amp;ldquo;여기서 메시지 끝&amp;quot;이라는 구분이 없음.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;보내는 쪽:
 send(&amp;#34;Hello&amp;#34;)
 send(&amp;#34;World&amp;#34;)

TCP 파이프 안:
 [H][e][l][l][o][W][o][r][l][d] ← 전부 붙어있음

받는 쪽이 실제로 받을 수 있는 것:
 recv() → &amp;#34;Hel&amp;#34;
 recv() → &amp;#34;loWor&amp;#34;
 recv() → &amp;#34;ld&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;받는 쪽은 &amp;ldquo;Hello&amp;quot;가 어디서 끝나고 &amp;ldquo;World&amp;quot;가 어디서 시작하는지 알 수 없음.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="websocket의-해결-프레임으로-경계-복원"&gt;&lt;a href="#websocket%ec%9d%98-%ed%95%b4%ea%b2%b0-%ed%94%84%eb%a0%88%ec%9e%84%ec%9c%bc%eb%a1%9c-%ea%b2%bd%ea%b3%84-%eb%b3%b5%ec%9b%90" class="header-anchor"&gt;&lt;/a&gt;WebSocket의 해결: 프레임으로 경계 복원
&lt;/h3&gt;&lt;p&gt;WebSocket은 각 메시지를 프레임으로 감싸서 경계를 만들어줌:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;보내는 쪽:
 ws.send(&amp;#34;Hello&amp;#34;)
 ws.send(&amp;#34;World&amp;#34;)

WebSocket 프레이밍 후:
 [FIN=1, len=5, &amp;#34;Hello&amp;#34;][FIN=1, len=5, &amp;#34;World&amp;#34;]
 ├─── 프레임 1 ───────┤├─── 프레임 2 ────────┤

받는 쪽:
 onMessage(&amp;#34;Hello&amp;#34;) ← 정확히 원본 메시지 단위로 받음
 onMessage(&amp;#34;World&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h3 id="최소한의-의미"&gt;&lt;a href="#%ec%b5%9c%ec%86%8c%ed%95%9c%ec%9d%98-%ec%9d%98%eb%af%b8" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;최소한&amp;quot;의 의미
&lt;/h3&gt;&lt;p&gt;WebSocket 프레임 헤더에 들어있는 정보는 이게 전부임:&lt;/p&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;FIN&lt;/td&gt;
 &lt;td&gt;메시지의 마지막 프레임인지&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Opcode&lt;/td&gt;
 &lt;td&gt;텍스트(0x1) vs 바이너리(0x2) vs 컨트롤(0x8, 0x9, 0xA)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Length&lt;/td&gt;
 &lt;td&gt;페이로드 길이&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Mask&lt;/td&gt;
 &lt;td&gt;마스킹 여부 (보안용)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;HTTP 헤더와 비교해보면 차이가 확연함:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HTTP 헤더 (수백 바이트):
 Content-Type: application/json
 Content-Length: 42
 Authorization: Bearer xxx
 X-Request-ID: abc123
 Cache-Control: no-cache
 ... 등등

WebSocket 프레임 헤더 (2~14 바이트):
 [FIN + opcode][MASK + length]
 끝.
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h3 id="websocket이-해주지-않는-것들"&gt;&lt;a href="#websocket%ec%9d%b4-%ed%95%b4%ec%a3%bc%ec%a7%80-%ec%95%8a%eb%8a%94-%ea%b2%83%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;WebSocket이 해주지 않는 것들
&lt;/h3&gt;&lt;p&gt;&amp;ldquo;최소한의 프레이밍&amp;rdquo; 철학은 곧 &lt;strong&gt;나머지는 알아서 하라&lt;/strong&gt;는 뜻임:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;클라이언트가&lt;/span&gt; &lt;span class="err"&gt;보내는&lt;/span&gt; &lt;span class="err"&gt;실제&lt;/span&gt; &lt;span class="err"&gt;메시지:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SUBSCRIBE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;channel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stock.005930&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;userId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gun0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;abc123&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;WebSocket이 아는 것:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;이건 텍스트 프레임이고, 길이는 120바이트다&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WebSocket이 모르는 것:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;이 뭔지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;channel&lt;/code&gt;로 어디에 라우팅해야 하는지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;userId&lt;/code&gt;와 &lt;code&gt;token&lt;/code&gt;으로 인증을 어떻게 처리할지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 것들은 전부 애플리케이션 레이어가 직접 구현해야 함.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="그래서-stomp-같은-서브프로토콜을-사용하는-것"&gt;&lt;a href="#%ea%b7%b8%eb%9e%98%ec%84%9c-stomp-%ea%b0%99%ec%9d%80-%ec%84%9c%eb%b8%8c%ed%94%84%eb%a1%9c%ed%86%a0%ec%bd%9c%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%98%eb%8a%94-%ea%b2%83" class="header-anchor"&gt;&lt;/a&gt;그래서 STOMP 같은 서브프로토콜을 사용하는 것
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;WebSocket만 사용하면:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@OnMessage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// message = 그냥 문자열&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 이게 뭔지, 누구한테 보낼지 직접 파싱해야 함&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSONObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SUBSCRIBE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 구독 로직 직접 구현&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UNSUBSCRIBE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 구독 해제 로직 직접 구현&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SEND&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 메시지 전송 로직 직접 구현&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;STOMP를 얹으면:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@MessageMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/stock/{stockCode}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handleStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@DestinationVariable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stockCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StockRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 메시지 타입, 라우팅이 이미 처리되어 있음&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="tcp-위에-websocket이-추가하는-것들"&gt;&lt;a href="#tcp-%ec%9c%84%ec%97%90-websocket%ec%9d%b4-%ec%b6%94%ea%b0%80%ed%95%98%eb%8a%94-%ea%b2%83%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;TCP 위에 WebSocket이 추가하는 것들
&lt;/h3&gt;&lt;p&gt;RFC 6455에서 WebSocket의 역할을 명확히 정의하고 있음:&lt;/p&gt;
&lt;h4 id="1-웹-origin-기반-보안-모델"&gt;&lt;a href="#1-%ec%9b%b9-origin-%ea%b8%b0%eb%b0%98-%eb%b3%b4%ec%95%88-%eb%aa%a8%eb%8d%b8" class="header-anchor"&gt;&lt;/a&gt;1. 웹 Origin 기반 보안 모델
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Origin: http://example.com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;브라우저 환경에서 &amp;ldquo;이 스크립트가 어디서 왔는지&amp;quot;를 서버에 알려줌. 서버가 cross-origin 요청을 거부할 수 있는 근거를 제공함.&lt;/p&gt;
&lt;h4 id="2-주소-지정-및-프로토콜-네이밍"&gt;&lt;a href="#2-%ec%a3%bc%ec%86%8c-%ec%a7%80%ec%a0%95-%eb%b0%8f-%ed%94%84%eb%a1%9c%ed%86%a0%ec%bd%9c-%eb%84%a4%ec%9d%b4%eb%b0%8d" class="header-anchor"&gt;&lt;/a&gt;2. 주소 지정 및 프로토콜 네이밍
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/chat&lt;/span&gt; &lt;span class="kr"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;server.example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Sec-WebSocket-Protocol&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;stomp, mqtt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;하나의 IP + 하나의 포트에서 여러 서비스를 제공할 수 있음:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Path로 엔드포인트 구분 (&lt;code&gt;/chat&lt;/code&gt;, &lt;code&gt;/notifications&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Host 헤더로 가상 호스팅&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt;로 서브프로토콜 협상&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-프레이밍-메커니즘"&gt;&lt;a href="#3-%ed%94%84%eb%a0%88%ec%9d%b4%eb%b0%8d-%eb%a9%94%ec%bb%a4%eb%8b%88%ec%a6%98" class="header-anchor"&gt;&lt;/a&gt;3. 프레이밍 메커니즘
&lt;/h4&gt;&lt;p&gt;RFC에서 흥미로운 표현을 쓰고 있음:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;layers a framing mechanism on top of TCP to get back to the IP packet mechanism that TCP is built on, but without length limits&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&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;IP&lt;/td&gt;
 &lt;td&gt;패킷 기반, 경계 명확, 크기 제한 있음 (~1500 bytes)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TCP&lt;/td&gt;
 &lt;td&gt;스트림 기반, 경계 없음, 크기 제한 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;WebSocket&lt;/td&gt;
 &lt;td&gt;프레임 기반, 경계 명확, 크기 제한 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;TCP가 IP 패킷들을 이어붙여서 &amp;ldquo;연속적인 스트림&amp;quot;으로 만들어버렸는데, WebSocket이 다시 &amp;ldquo;메시지 단위&amp;quot;를 복원해주는 것임.&lt;/p&gt;
&lt;h4 id="4-프록시-친화적-closing-handshake"&gt;&lt;a href="#4-%ed%94%84%eb%a1%9d%ec%8b%9c-%ec%b9%9c%ed%99%94%ec%a0%81-closing-handshake" class="header-anchor"&gt;&lt;/a&gt;4. 프록시 친화적 Closing Handshake
&lt;/h4&gt;&lt;p&gt;TCP FIN/ACK만으로는 중간에 프록시가 있을 때 데이터 손실이 발생할 수 있음:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Client] ----data----&amp;gt; [Proxy] ----data----&amp;gt; [Server]
[Client] &amp;lt;---FIN------ [Proxy] [Server] ← 프록시가 임의로 끊을 수 있음
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;WebSocket Close 프레임은 애플리케이션 레이어에서 종료를 협상하기 때문에 더 안전함:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Client] ---Close Frame---&amp;gt; [Proxy] ---Close Frame---&amp;gt; [Server]
[Client] &amp;lt;--Close Frame---- [Proxy] &amp;lt;--Close Frame---- [Server]
[Client] -------TCP FIN-------&amp;gt; ... -------TCP FIN-------&amp;gt; [Server]
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h3 id="raw-tcp에-최대한-가깝게"&gt;&lt;a href="#raw-tcp%ec%97%90-%ec%b5%9c%eb%8c%80%ed%95%9c-%ea%b0%80%ea%b9%9d%ea%b2%8c" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;Raw TCP에 최대한 가깝게&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;RFC의 핵심 문장:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;Basically it is intended to be as close to just exposing raw TCP to script as possible given the constraints of the Web.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;브라우저에서 JavaScript로 raw TCP 소켓을 직접 열 수는 없음 (보안상 이유). WebSocket은 그 제약 내에서 &lt;strong&gt;최대한 TCP에 가까운 경험&lt;/strong&gt;을 제공하려는 것임.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebSocket이 추가하지 않는 것들:&lt;/strong&gt;&lt;/p&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;메시지 ID&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;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;재전송/순서 보장&lt;/td&gt;
 &lt;td&gt;TCP가 이미 제공&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;메시지 압축&lt;/td&gt;
 &lt;td&gt;기본은 raw (확장으로 가능)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;인증&lt;/td&gt;
 &lt;td&gt;HTTP 핸드셰이크에서 처리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;라우팅&lt;/td&gt;
 &lt;td&gt;서브프로토콜이 처리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id="http-인프라와의-공존"&gt;&lt;a href="#http-%ec%9d%b8%ed%94%84%eb%9d%bc%ec%99%80%ec%9d%98-%ea%b3%b5%ec%a1%b4" class="header-anchor"&gt;&lt;/a&gt;HTTP 인프라와의 공존
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;It&amp;rsquo;s also designed in such a way that its servers can share a port with HTTP servers&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;이건 실용적으로 매우 중요한 설계 결정임:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Port 80/443
 │
 ├── GET /api/users HTTP/1.1 → 일반 HTTP 처리
 ├── GET /index.html HTTP/1.1 → 일반 HTTP 처리
 └── GET /ws HTTP/1.1 → WebSocket 업그레이드
 Upgrade: websocket
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기존 로드밸런서, 프록시, 방화벽 통과 가능&lt;/li&gt;
&lt;li&gt;추가 포트 오픈 불필요&lt;/li&gt;
&lt;li&gt;SSL 인증서 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핸드셰이크가 HTTP Upgrade 형태인 이유도 이것 때문임. RFC에서 이렇게 언급하고 있음:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;the design does not limit WebSocket to HTTP, and future implementations could use a simpler handshake over a dedicated port without reinventing the entire protocol&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;HTTP 호환은 현재 웹 인프라를 위해 선택한 것이지, 프로토콜의 본질은 아니라는 뜻임.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="확장성"&gt;&lt;a href="#%ed%99%95%ec%9e%a5%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;확장성
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;The protocol is intended to be extensible; future versions will likely introduce additional concepts such as multiplexing.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;확장을 위해 예약해둔 것들:&lt;/p&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;RSV1, RSV2, RSV3 비트&lt;/td&gt;
 &lt;td&gt;프레임별 확장 플래그&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Opcode 0x3-0x7&lt;/td&gt;
 &lt;td&gt;추가 데이터 프레임 타입&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Opcode 0xB-0xF&lt;/td&gt;
 &lt;td&gt;추가 컨트롤 프레임 타입&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Sec-WebSocket-Extensions&lt;/code&gt; 헤더&lt;/td&gt;
 &lt;td&gt;확장 협상&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;실제 확장 예시 - permessage-deflate:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;메시지 압축 확장인데, RSV1 비트를 사용해서 &amp;ldquo;이 프레임은 압축됨&amp;quot;을 표시함.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="비유로-정리"&gt;&lt;a href="#%eb%b9%84%ec%9c%a0%eb%a1%9c-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;비유로 정리
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;TCP = 고속도로&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;차(바이트)들이 줄줄이 달림&lt;/li&gt;
&lt;li&gt;어디서 한 무리가 끝나고 다음 무리가 시작하는지 구분선 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;WebSocket = 컨테이너 트럭&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;화물(메시지)을 컨테이너(프레임)에 담아서 운송&lt;/li&gt;
&lt;li&gt;&amp;ldquo;컨테이너 안에 뭐가 들었는지&amp;quot;는 컨테이너 자체는 모름&lt;/li&gt;
&lt;li&gt;그냥 &amp;ldquo;컨테이너 크기가 얼마다&amp;rdquo; 정도만 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;STOMP = 물류 시스템&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 안에 송장을 붙여서 &amp;ldquo;이건 A에게, 저건 B에게&amp;rdquo;&lt;/li&gt;
&lt;li&gt;화물 종류별로 분류&lt;/li&gt;
&lt;li&gt;배송 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WebSocket은 컨테이너 트럭처럼 &lt;strong&gt;안전하게 덩어리째 전달&lt;/strong&gt;만 해주고, 그 안의 내용물 관리는 STOMP 같은 상위 프로토콜이 담당하는 구조임.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="정리"&gt;&lt;a href="#%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;정리
&lt;/h3&gt;&lt;p&gt;WebSocket의 설계 철학은 &lt;em&gt;&amp;ldquo;최소한만 하고, 나머지는 위임한다&amp;rdquo;&lt;/em&gt; 로 요약할 수 있음:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;WebSocket이 하는 것&lt;/th&gt;
 &lt;th&gt;WebSocket이 안 하는 것&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;/tr&gt;
 &lt;tr&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;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Ping/Pong heartbeat&lt;/td&gt;
 &lt;td&gt;비즈니스 로직&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Origin 기반 보안&lt;/td&gt;
 &lt;td&gt;애플리케이션 레벨 보안&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이런 철학 덕분에 WebSocket은 가볍고 범용적임. 그 위에 STOMP, Socket.IO, 또는 직접 만든 프로토콜을 얹어서 각자의 요구사항에 맞게 확장할 수 있음.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이렇게 RFC 6455 문서를 살펴보면서 Websocket 에 대해 알아 보았음. 다음 포스트 에서는 SpringBoot 에서 Websocket 이 어떻게 어떻게 구현되어져 있는지 살펴보는 시간을 가져볼까함.&lt;/p&gt;</description></item><item><title>Websocket 이전의 양방향 통신</title><link>https://0andwild.com/posts/260112_before_websocket/</link><pubDate>Mon, 12 Jan 2026 21:18:15 +0900</pubDate><guid>https://0andwild.com/posts/260112_before_websocket/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Websocket 이전의 양방향 통신" /&gt;&lt;h2 id="서론"&gt;&lt;a href="#%ec%84%9c%eb%a1%a0" class="header-anchor"&gt;&lt;/a&gt;서론
&lt;/h2&gt;&lt;p&gt;모의투자 프로젝트를 진행해보면서 주식의 가격변동 데이터를 실시간성으로 서빙해야하는 상황이 생겼고 Websocket 기술을 사용하게 됨.
양방향 실시간 통신 기술인 Websocket 을 이전부터 알고는 있었지만 상세하게 파고든적은 없어 이번 기회에 깊게 파고들어 가보고자함.&lt;/p&gt;
&lt;p&gt;Websocket 기술 문서인 RFC 6455 를 읽어보다 더 과거의 처리방식과 문제점을 설명하며 RFC 6202(Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP)를 인용한 부분이
있었음. 오늘은 RFC 6202 를 읽어보며 Websocket이 나오기 전에는 어떤 방식으로 처리를 하였고 그 당시의 생각과 Best practice 들에 관하여 알아가보고자 함.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="rfc-6202-known-issues-and-best-practices-for-the-use-of-long-polling-and-streaming-in-bidirectional-http"&gt;&lt;a href="#rfc-6202-known-issues-and-best-practices-for-the-use-of-long-polling-and-streaming-in-bidirectional-http" class="header-anchor"&gt;&lt;/a&gt;&lt;a class="link" href="%22https://datatracker.ietf.org/doc/html/rfc6202%22" &gt;RFC 6202&lt;/a&gt; (Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP)
&lt;/h2&gt;&lt;p&gt;2011년 4월 작성된 문서로 이 문서에서는 그 당시의 양방향 HTTP 통신의 잘 알려진 문제점과 best practice 로 HTTP long polling 과 HTTP streaming 에 대해 다룸. 또한 HTTP long polling과 HTTP streaming 모두 HTTP 를 확장한 것이며 HTTP 프로토콜이 양방향 통신을 위해 설계되지 않았음을 인정함.
문서의 저자들은 이 문서가 위 두가지 방식의 사용을 권장하지도 사용하지 말라는 것도 아니며 그저 좋은 사용사례와 문제점들을 얘기하는 것에 중점을 둔다고 표기함.&lt;/p&gt;
&lt;p&gt;기본적으로 HTTP(Hypertext Transfer Protocol:RFC2616)은 request/response 프로토콜임.
HTTP 는 clients, proxies, servers 이 세 가지 엔티티를 정의 하고 있음.
client 는 HTTP request 를 서버에 보내기 위해 연결을 생성하고, 서버는 응답을 반환하여 HTTP request 를 처리하기 위해 연결을 수락함. Proxies 는 클라이언트와 서버 사이에서 요청과 응답을 전달하는데 개입 할 수 있는 객체임.&lt;/p&gt;
&lt;p&gt;기본적으로 표준 HTTP 모델은 서버가 먼저 클라이언트에게 연결을 시작할 수 없고 요청하지 않은 HTTP 응답을 보낼 수 없기 때문에 서버가 클라이언트에게 비동기 이벤트를 보낼 수 없음.&lt;/p&gt;
&lt;p&gt;그래서 비동기 이벤트를 최대한 빠르게 받기 위해 클라이언트는 주기적으로 서버를 폴링을 해야하는데 이런 지속적인 폴링은 데이터가 없을때도 요청/응답을 강제로 발생시켜 네트워크 리소스를 잡아먹고, 데이터가 다음 폴링 요청을 서버가 수신할 때 까지 큐에 쌓이기 때문에 애플리케이션의 응답 효율을 떨어뜨림.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="http-long-polling--http-streaming"&gt;&lt;a href="#http-long-polling--http-streaming" class="header-anchor"&gt;&lt;/a&gt;HTTP long polling &amp;amp; HTTP streaming
&lt;/h2&gt;&lt;h2 id="1-http-long-polling"&gt;&lt;a href="#1-http-long-polling" class="header-anchor"&gt;&lt;/a&gt;1. HTTP long polling
&lt;/h2&gt;&lt;p&gt;전통적인 short polling 기술은 클라이언트에서 서버측으로 주기적으로 요청을 보내어 데이터를 업데이트 하는 방식이지만 새로운 이벤트가 없어도 빈 응답을 받거나 다음 polling 까지 대기를 해야함. 이 기술은 클라이언트가 설정한 지연시간에 따라 요청 주기가 결정되고 이 주기가 짧을 경우(폴링빈도=높음) 서버,네트워크 양쪽 모두에 감당하기 어려운 부담을 초래할 수 있음.&lt;/p&gt;
&lt;p&gt;이와 반대로 long polling은 특정 이벤트, 상태 또는 네트워크 타임아웃이 발생 하였을때만 요청에 대해 응답을 하여 메시지 전달 지연과 네트워크 자원 사용을 최소화 하려고 시도함.&lt;/p&gt;
&lt;h3 id="http-long-polling-life-cycle"&gt;&lt;a href="#http-long-polling-life-cycle" class="header-anchor"&gt;&lt;/a&gt;HTTP long polling life cycle
&lt;/h3&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;클라이언트는 응답을 받은 직후 새로운 long poll request 를 바로 생성하거나 허용가능한 일정 지연시간 동안 정지 후 생성함.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="http-long-polling-issue"&gt;&lt;a href="#http-long-polling-issue" class="header-anchor"&gt;&lt;/a&gt;HTTP long polling issue
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Header overhead : 매 요청/응답이 HTTP 메세지 이므로 데이터가 작더라도 HTTP header 가 항상 따라붙음.
작고 데이터의 경우 헤더가 데이터 전송의 상당부분을 차지하게 된다. 만약 네트워크 MTU(Maximum Transmission Unit)가 헤더를 포함한 모든 정보를 단일 IP 패킷에 수용 가능하다면 네트워크 부담은 크게 없음. 하지만 작은 메시지가 자주 오갈 때, 실제 데이터 대비 전송량이 커지는 문제가 발생함.
예시로 편지지 한 장(20g)을 보내는데 택배 박스(300g) 에 넣어서 보내는 것 과 같은 맥락임.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maximal latency : long poll 응답을 보낸 직후 서버가 바로 새 메시지를 보내고 싶어도 클라이언트의 다음 요청이 올때까지 기다려야 함. 평균 지연은 1 network trasit 에 가깝지만 최악의 경우 3 network transit(response-request-response) 까지 늘어날 수 있고 TCP 패킷 손실이 되었을 경우 재전송까지 고려하면 그 이상이 발생할 수 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connection Establishment : short polling, long polling 모두 TCP/IP 연결을 열고 닫는 것을 자주 한다는 비판이 있음. 하지만 두 polling 메커니즘은 재사용될 수 있는 presistent HTTP connection 과 잘 작동함.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allocated Resources : 운영체제는 TCP/IP 연결 및 연결 보류 중인 HTTP 요청들에게 자원을 할당함. HTTP long polling 은 각 클라이언트에 대해 TCP/IP 연결과 HTTP 요청이 모두 열린 상태로 유지되도록 요구함. 따라서 HTTP 롱 폴링 애플리케이션의 규모를 결정할 때 이 두가지와 관련된 리소스를 고려하는 것이 중요함.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Graceful Degradation : 서버나 클라이언트가 과부하 상태일 때 메시지가 큐에 쌓이다가 한 응답에 여러 메시지를 묶어서 보낼 수 있음. 지연은 늘어나지만 메시지당 오버헤드가 줄어서 부하가 자연스럽게 분산됨.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Timeouts : long poll 요청은 서버가 보낼 데이터가 생길 때까지 계속 대기(hanging) 상태를 유지해야 하기 때문에 타임아웃 문제가 발생 할 수 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Caching : 중간 프록시나 CDN 이 응답을 캐싱하면 최신 데이터가 아닌 오래된 데이터를 받는 문제가 생길 수 있음. 클라이언트나 호스트가 HTTP 중개자에게 롱 폴링이 사용중임을 알릴 방법이 없으나 양방향 흐름을 방해 할 수 있는(=캐싱)은 표준 헤더나 쿠키로 제어가 가능함. 최선의 관행으로 롱 폴링 요청이나 응답에서는 항상 의도적으로 캐싱을 억제함. &amp;ldquo;Cache-Control&amp;rdquo; 헤더를 &amp;ldquo;no-cache&amp;rdquo; 로 설정함.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="2-http-streaming"&gt;&lt;a href="#2-http-streaming" class="header-anchor"&gt;&lt;/a&gt;2. HTTP Streaming
&lt;/h2&gt;&lt;p&gt;HTTP streaming 의 메커니즘은 절대 request 를 종료 하거나 서버가 클라이언트에게 데이터를 보낸 후 에도 연결을 끊지 않는것임.
이러한 메커니즘은 클라이언트와 서버가 계속해서 연결을 시작하고 끊는 것을 하지 않아도 되기 때문에 네트워크 지연을 상당히 낮추어줌.&lt;/p&gt;
&lt;h3 id="http-streaming-life-cycle"&gt;&lt;a href="#http-streaming-life-cycle" class="header-anchor"&gt;&lt;/a&gt;HTTP Streaming life cycle
&lt;/h3&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;데이터를 보내고 서버는 요청을 종료 하거나 연결을 끊지 않고 3번을 계속해서 진행함&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="http-streaming-issue"&gt;&lt;a href="#http-streaming-issue" class="header-anchor"&gt;&lt;/a&gt;HTTP Streaming issue
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Network Intermediaries : HTTP 프로토콜은 서버에서 클라이언트로 응답을 전송하는 과정에서 중개자(프록시, 투명 프록시, 게이트웨이 등)가 개입할 수 있도록 허용함. HTTP Streaming 은 이러한 중개자와 함께 작동하지 않음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maximal Latency : 이론상 1 network transit 이지만 실제로는 Javascript / DOM 요소와 관련된 메모리 사용량의 무제한 증가 방지를 위해 주기적으로 연결을 끊고 다시 맺어야 함. 결국 long polling 처럼 최대 지연은 3 network transit 이 발생함.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Client Buffering : HTTP 스펙상 부분 응답을 즉시 처리할 의무가 없음. 대부분의 브라우저는 응답의 JS 를 실행하긴 하지만 일부는 버퍼 오버플로우가 발생해야 실행함. 공백 문자를 보내 버퍼를 채우는 방법을 사용할 수 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Framing Techniques : HTTP Streaming 을 사용하면 단일 HTTP 응답에 여러 애플리케이션 메시지를 전송할 수 있음. 하지만 중간 객체인 프록시에서 청크단위를 다시 재청크 하는 상황이 발생할 수 있기 때문에 청크 단위로 메시지를 구분 할 수 없음. 따라서 애플리케이션 레벨에서 별도로 구분자를 정의 해야함. Long polling 은 응답하나에 메시지가 하나이기 때문에 이와 같은 문제가 발생하지 않음.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="그-외의-server-push-메커니즘-소개"&gt;&lt;a href="#%ea%b7%b8-%ec%99%b8%ec%9d%98-server-push-%eb%a9%94%ec%bb%a4%eb%8b%88%ec%a6%98-%ec%86%8c%ea%b0%9c" class="header-anchor"&gt;&lt;/a&gt;그 외의 server-push 메커니즘 소개
&lt;/h2&gt;&lt;p&gt;여기서는 위 두 가지의 메커니즘 외에 Bayeux(4.1), BOSH(4.2), Server-Sent Events(4.3) 등을 소개함.
SSE 메커니즘을 사용할 때 권고사항을 다루고 있는데 아래와 같음.&lt;/p&gt;
&lt;p&gt;스펙상 HTTP chunking을 비활성화 하라고 권장한다. 그 이유는 위에서 설명한 HTTP streaming issue 와 같음.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;중간 프록시가 청크를 re-chunking 할 수 있음&lt;/li&gt;
&lt;li&gt;일부 프록시가 전체 응답을 버퍼링 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="best-practice"&gt;&lt;a href="#best-practice" class="header-anchor"&gt;&lt;/a&gt;Best Practice
&lt;/h2&gt;&lt;h3 id="요약"&gt;&lt;a href="#%ec%9a%94%ec%95%bd" class="header-anchor"&gt;&lt;/a&gt;요약
&lt;/h3&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;브라우저당 6~8개 제한&lt;/td&gt;
 &lt;td&gt;Long poll은 하나로, 쿠키로 중복 감지&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;파이프라이닝&lt;/td&gt;
 &lt;td&gt;일반 요청이 long poll 뒤에 막힐 수 있음&lt;/td&gt;
 &lt;td&gt;지원 여부 확인 후 사용, 폴백 준비&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;프록시&lt;/td&gt;
 &lt;td&gt;연결 공유 시 starvation 발생&lt;/td&gt;
 &lt;td&gt;비동기 프록시 사용, 연결 공유 피하기&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;타임아웃&lt;/td&gt;
 &lt;td&gt;너무 높으면 408/504, 너무 낮으면 트래픽 낭비&lt;/td&gt;
 &lt;td&gt;30초 권장&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;code&gt;Cache-Control: no-cache&lt;/code&gt; 필수&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;보안&lt;/td&gt;
 &lt;td&gt;Injection, DoS 취약&lt;/td&gt;
 &lt;td&gt;입력 검증, 연결 수 제한&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="1-limits-to-the-maximum-number-of-connections"&gt;&lt;a href="#1-limits-to-the-maximum-number-of-connections" class="header-anchor"&gt;&lt;/a&gt;1. Limits to the Maximum Number of Connections
&lt;/h2&gt;&lt;h3 id="배경"&gt;&lt;a href="#%eb%b0%b0%ea%b2%bd" class="header-anchor"&gt;&lt;/a&gt;배경
&lt;/h3&gt;&lt;p&gt;HTTP 스펙(RFC 2616)에서는 원래 &lt;strong&gt;클라이언트 하나가 서버에 최대 2개 연결&lt;/strong&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;최근 브라우저들은 이 제한을 6~8개로 늘렸지만, 여전히 제한이 존재함. 문제는 사용자가 여러 탭이나 프레임을 열면 이 연결들을 금방 소진한다는 것임.&lt;/p&gt;
&lt;h3 id="왜-문제가-되는가"&gt;&lt;a href="#%ec%99%9c-%eb%ac%b8%ec%a0%9c%ea%b0%80-%eb%90%98%eb%8a%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;왜 문제가 되는가?
&lt;/h3&gt;&lt;p&gt;Long polling은 연결을 오래 점유함. 만약 탭 3개에서 각각 long poll을 2개씩 열면:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;탭1: long poll 연결 2개
탭2: long poll 연결 2개 
탭3: long poll 연결 2개
─────────────────────
총 6개 연결 → 브라우저 한계 도달
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 상태에서 일반 HTTP 요청(이미지, API 호출 등)을 보내려면 기존 연결이 끝날 때까지 &lt;strong&gt;대기해야 함&lt;/strong&gt;. 이걸 **connection starvation(연결 고갈)**이라고 부름.&lt;/p&gt;
&lt;h3 id="권장사항"&gt;&lt;a href="#%ea%b6%8c%ec%9e%a5%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;권장사항
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;클라이언트 측:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Long poll 요청을 &lt;strong&gt;하나로 제한&lt;/strong&gt;하고, 여러 탭/프레임이 이걸 공유하는 게 이상적임&lt;/li&gt;
&lt;li&gt;하지만 브라우저 보안 모델 때문에 탭 간 리소스 공유가 어려움 (Same-Origin Policy 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;서버 측:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;쿠키를 사용해서 같은 브라우저에서 오는 중복 long poll 요청을 감지&lt;/strong&gt;해야 함&lt;/li&gt;
&lt;li&gt;중복 요청이 감지되면 둘 다 대기시키지 말고, 하나는 즉시 응답해서 해제해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[잘못된 처리]
요청1: 대기 중...
요청2: 대기 중... ← 둘 다 대기하면 connection starvation 발생

[올바른 처리]
요청1: 대기 중...
요청2: 들어옴 → 요청1에 즉시 빈 응답 → 요청2만 대기
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2-pipelined-connections"&gt;&lt;a href="#2-pipelined-connections" class="header-anchor"&gt;&lt;/a&gt;2. Pipelined Connections
&lt;/h2&gt;&lt;h3 id="파이프라이닝이란"&gt;&lt;a href="#%ed%8c%8c%ec%9d%b4%ed%94%84%eb%9d%bc%ec%9d%b4%eb%8b%9d%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;파이프라이닝이란?
&lt;/h3&gt;&lt;p&gt;HTTP/1.1에서 지원하는 기능으로, 응답을 기다리지 않고 &lt;strong&gt;여러 요청을 연속으로 보내는 것&lt;/strong&gt;임.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[파이프라이닝 없음]
요청1 → 응답1 → 요청2 → 응답2 → 요청3 → 응답3

[파이프라이닝 있음]
요청1 → 요청2 → 요청3 → 응답1 → 응답2 → 응답3
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="long-polling에서의-장점"&gt;&lt;a href="#long-polling%ec%97%90%ec%84%9c%ec%9d%98-%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;Long Polling에서의 장점
&lt;/h3&gt;&lt;p&gt;서버가 짧은 시간에 여러 메시지를 보내야 할 때 유용함. 파이프라이닝이 있으면 서버가 응답 후 클라이언트의 새 요청을 기다리지 않아도 됨. 이미 큐에 요청이 쌓여있으니까.&lt;/p&gt;
&lt;h3 id="문제점-일반-요청이-막힘"&gt;&lt;a href="#%eb%ac%b8%ec%a0%9c%ec%a0%90-%ec%9d%bc%eb%b0%98-%ec%9a%94%ec%b2%ad%ec%9d%b4-%eb%a7%89%ed%9e%98" class="header-anchor"&gt;&lt;/a&gt;문제점: 일반 요청이 막힘
&lt;/h3&gt;&lt;p&gt;파이프라이닝의 치명적인 문제가 있음. &lt;strong&gt;일반 요청이 long poll 뒤에 큐잉되면, long poll이 끝날 때까지 기다려야 함&lt;/strong&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[파이프라인 큐]
1. Long poll 요청 (30초 대기 중...)
2. 이미지 요청 ← long poll 끝날 때까지 대기
3. API 요청 ← long poll 끝날 때까지 대기
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이러면 페이지 로딩이 30초씩 지연될 수 있음.&lt;/p&gt;
&lt;h3 id="주의사항"&gt;&lt;a href="#%ec%a3%bc%ec%9d%98%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;주의사항
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;HTTP POST 파이프라이닝은 RFC 2616에서 &lt;strong&gt;권장하지 않음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;BOSH나 Bayeux 같은 프로토콜은 POST를 파이프라이닝하면서 요청 ID로 순서를 보장하는 방식을 씀&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;hr&gt;
&lt;h2 id="3-proxies"&gt;&lt;a href="#3-proxies" class="header-anchor"&gt;&lt;/a&gt;3. Proxies
&lt;/h2&gt;&lt;h3 id="일반-프록시와의-호환성"&gt;&lt;a href="#%ec%9d%bc%eb%b0%98-%ed%94%84%eb%a1%9d%ec%8b%9c%ec%99%80%ec%9d%98-%ed%98%b8%ed%99%98%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;일반 프록시와의 호환성
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Long Polling:&lt;/strong&gt; 대부분의 프록시와 잘 동작함. 왜냐하면 결국 완전한 HTTP 응답을 보내기 때문임 (이벤트 발생 시 또는 타임아웃 시).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP Streaming:&lt;/strong&gt; 문제가 있음. 두 가지 가정에 의존하는데:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프록시가 각 청크를 즉시 전달할 것 → &lt;strong&gt;보장 안 됨&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;브라우저가 도착한 JS 청크를 즉시 실행할 것 → &lt;strong&gt;보장 안 됨&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="리버스-프록시-문제"&gt;&lt;a href="#%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9c-%eb%ac%b8%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;리버스 프록시 문제
&lt;/h3&gt;&lt;p&gt;리버스 프록시는 클라이언트 입장에서는 실제 서버처럼 보이지만, 뒤에 있는 진짜 서버로 요청을 전달하는 역할을 함.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;클라이언트 → [리버스 프록시] → 실제 서버
 (Nginx, Apache 등)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Long polling과 streaming 모두 동작하긴 하지만, &lt;strong&gt;성능 문제&lt;/strong&gt;가 있음. 대부분의 프록시는 많은 연결을 오래 유지하도록 설계되지 않았음.&lt;/p&gt;
&lt;h3 id="연결-공유-문제-connection-sharing"&gt;&lt;a href="#%ec%97%b0%ea%b2%b0-%ea%b3%b5%ec%9c%a0-%eb%ac%b8%ec%a0%9c-connection-sharing" class="header-anchor"&gt;&lt;/a&gt;연결 공유 문제 (Connection Sharing)
&lt;/h3&gt;&lt;p&gt;이게 가장 심각한 문제임. Apache mod_jk 같은 프록시는 &lt;strong&gt;여러 클라이언트가 소수의 연결을 공유&lt;/strong&gt;하도록 설계됨.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Apache mod_jk 연결 풀: 8개]

클라이언트A의 long poll → 연결1 점유 (30초...)
클라이언트B의 long poll → 연결2 점유 (30초...)
클라이언트C의 long poll → 연결3 점유 (30초...)
...
클라이언트H의 long poll → 연결8 점유 (30초...)

클라이언트I의 일반 요청 → 연결 없음! 대기...
클라이언트J의 일반 요청 → 연결 없음! 대기...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;8개 연결이 모두 long poll에 점유되면, 다른 모든 요청(long poll이든 일반 요청이든)이 막혀버림. 이걸 &lt;strong&gt;connection starvation&lt;/strong&gt;이라고 함.&lt;/p&gt;
&lt;h3 id="근본-원인-동기식-vs-비동기식"&gt;&lt;a href="#%ea%b7%bc%eb%b3%b8-%ec%9b%90%ec%9d%b8-%eb%8f%99%ea%b8%b0%ec%8b%9d-vs-%eb%b9%84%eb%8f%99%ea%b8%b0%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;근본 원인: 동기식 vs 비동기식
&lt;/h3&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;Long Poll 영향&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;동기식&lt;/strong&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;strong&gt;비동기식&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;이벤트 기반, 연결당 최소 리소스&lt;/td&gt;
 &lt;td&gt;영향 적음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;동기식 예시: Apache mod_jk, Java Servlet 2.5
비동기식 예시: Nginx, Node.js, Java Servlet 3.0+&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;결론:&lt;/strong&gt; Long polling/streaming을 쓸 때는 &lt;strong&gt;연결 공유를 피해야 함&lt;/strong&gt;. HTTP의 기본 가정은 &amp;ldquo;각 요청이 최대한 빨리 완료된다&amp;quot;인데, long poll은 이 가정을 깨기 때문임.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="4-http-responses"&gt;&lt;a href="#4-http-responses" class="header-anchor"&gt;&lt;/a&gt;4. HTTP Responses
&lt;/h2&gt;&lt;p&gt;이건 단순함. 표준 HTTP 그대로 따르면 됨.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버가 요청을 성공적으로 받으면 &lt;strong&gt;200 OK&lt;/strong&gt;로 응답&lt;/li&gt;
&lt;li&gt;응답 시점: 이벤트 발생, 상태 변화, 또는 타임아웃&lt;/li&gt;
&lt;li&gt;응답 body에 실제 이벤트/상태/타임아웃 정보 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;특별한 건 없고, 그냥 HTTP 스펙 준수하면 됨.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="5-timeouts"&gt;&lt;a href="#5-timeouts" class="header-anchor"&gt;&lt;/a&gt;5. Timeouts
&lt;/h2&gt;&lt;h3 id="딜레마"&gt;&lt;a href="#%eb%94%9c%eb%a0%88%eb%a7%88" class="header-anchor"&gt;&lt;/a&gt;딜레마
&lt;/h3&gt;&lt;p&gt;Long poll 타임아웃 값을 정하는 건 까다로움:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;너무 높게 설정하면:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버에서 &lt;strong&gt;408 Request Timeout&lt;/strong&gt; 받을 수 있음&lt;/li&gt;
&lt;li&gt;프록시에서 &lt;strong&gt;504 Gateway Timeout&lt;/strong&gt; 받을 수 있음&lt;/li&gt;
&lt;li&gt;네트워크 연결이 끊어진 걸 늦게 감지함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;너무 낮게 설정하면:&lt;/strong&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;/ul&gt;
&lt;h3 id="실험-결과와-권장값"&gt;&lt;a href="#%ec%8b%a4%ed%97%98-%ea%b2%b0%ea%b3%bc%ec%99%80-%ea%b6%8c%ec%9e%a5%ea%b0%92" class="header-anchor"&gt;&lt;/a&gt;실험 결과와 권장값
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;브라우저 기본 타임아웃: 300초 (5분)
실험에서 성공한 값: 최대 120초
안전한 권장값: 30초
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;대부분의 네트워크 인프라(프록시, 로드밸런서 등)는 브라우저만큼 긴 타임아웃을 갖고 있지 않음. 중간에 있는 장비가 먼저 연결을 끊어버릴 수 있음.&lt;/p&gt;
&lt;h3 id="네트워크-장비-벤더를-위한-권장사항"&gt;&lt;a href="#%eb%84%a4%ed%8a%b8%ec%9b%8c%ed%81%ac-%ec%9e%a5%eb%b9%84-%eb%b2%a4%eb%8d%94%eb%a5%bc-%ec%9c%84%ed%95%9c-%ea%b6%8c%ec%9e%a5%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;네트워크 장비 벤더를 위한 권장사항
&lt;/h3&gt;&lt;p&gt;Long polling 호환성을 원하면, 타임아웃을 &lt;strong&gt;30초보다 충분히 길게&lt;/strong&gt; 설정해야 함. 여기서 &amp;ldquo;충분히&amp;quot;란 평균 네트워크 왕복 시간의 몇 배 이상을 의미함.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="6-impact-on-intermediary-entities"&gt;&lt;a href="#6-impact-on-intermediary-entities" class="header-anchor"&gt;&lt;/a&gt;6. Impact on Intermediary Entities
&lt;/h2&gt;&lt;h3 id="투명성-문제"&gt;&lt;a href="#%ed%88%ac%eb%aa%85%ec%84%b1-%eb%ac%b8%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;투명성 문제
&lt;/h3&gt;&lt;p&gt;Long poll 요청은 중간 장비(프록시, 게이트웨이 등) 입장에서 &lt;strong&gt;일반 HTTP 요청과 구분이 안 됨&lt;/strong&gt;. &amp;ldquo;이건 long poll이니까 특별하게 처리해줘&amp;quot;라고 알려줄 방법이 없음.&lt;/p&gt;
&lt;p&gt;이로 인해 중간 장비가 불필요한 작업을 할 수 있음:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;캐싱 시도 (실시간 데이터인데 캐시하면 안 됨)&lt;/li&gt;
&lt;li&gt;타임아웃 적용 (long poll은 원래 오래 걸림)&lt;/li&gt;
&lt;li&gt;연결 재사용 시도 (long poll은 점유 시간이 김)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="캐싱-방지"&gt;&lt;a href="#%ec%ba%90%ec%8b%b1-%eb%b0%a9%ec%a7%80" class="header-anchor"&gt;&lt;/a&gt;캐싱 방지
&lt;/h3&gt;&lt;p&gt;가장 중요한 건 &lt;strong&gt;캐싱 방지&lt;/strong&gt;임. 실시간 데이터가 캐시되면 클라이언트가 과거 데이터를 받게 됨.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;반드시 설정해야 하는 헤더:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Cache-Control: no-cache
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;요청과 응답 모두에 이 헤더를 포함시켜야 함. 이건 표준 HTTP 헤더라서 대부분의 중간 장비가 이해하고 존중함.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="7-security-considerations"&gt;&lt;a href="#7-security-considerations" class="header-anchor"&gt;&lt;/a&gt;7. Security Considerations
&lt;/h2&gt;&lt;p&gt;RFC 6202는 HTTP의 새로운 기능을 제안하는 게 아니라, 기존 사용 방식을 설명하는 문서임. 따라서 새로운 보안 취약점을 만들지는 않음. 하지만 이미 배포된 솔루션들에 존재하는 보안 이슈들이 있음.&lt;/p&gt;
&lt;h3 id="1-injection-공격-cross-domain-long-polling"&gt;&lt;a href="#1-injection-%ea%b3%b5%ea%b2%a9-cross-domain-long-polling" class="header-anchor"&gt;&lt;/a&gt;1. Injection 공격 (Cross-Domain Long Polling)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;문제 상황:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;크로스 도메인 long polling에서 JSONP 방식을 쓸 때, 서버가 반환한 JavaScript를 브라우저가 실행함.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 서버 응답 (JSONP)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;52300&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;만약 서버가 injection 공격에 취약하면, 공격자가 악성 코드를 삽입할 수 있음:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 공격자가 조작한 응답
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;52300&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="nx"&gt;stealCookies&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;브라우저는 이걸 그대로 실행해버림.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;대응책:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 측 입력 검증 철저히&lt;/li&gt;
&lt;li&gt;CORS를 사용하고 JSONP 피하기&lt;/li&gt;
&lt;li&gt;Content-Type 헤더 정확히 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-dos-denial-of-service-공격"&gt;&lt;a href="#2-dos-denial-of-service-%ea%b3%b5%ea%b2%a9" class="header-anchor"&gt;&lt;/a&gt;2. DoS (Denial of Service) 공격
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;문제 상황:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Long polling과 HTTP streaming은 &lt;strong&gt;많은 연결을 오래 유지&lt;/strong&gt;해야 함. 공격자가 대량의 long poll 연결을 열어두면:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;공격자 → 연결 1,000개 열어둠 (각각 30초 대기)
 ↓
서버 리소스 고갈 → 정상 사용자 서비스 불가
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;일반 HTTP 요청은 금방 끝나니까 연결당 리소스 점유 시간이 짧음. 하지만 long poll은 의도적으로 오래 유지하니까 DoS에 취약함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;대응책:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IP당 연결 수 제한&lt;/li&gt;
&lt;li&gt;인증된 사용자만 long poll 허용&lt;/li&gt;
&lt;li&gt;Rate limiting 적용&lt;/li&gt;
&lt;li&gt;비동기 서버 사용 (연결당 리소스 최소화)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="정리"&gt;&lt;a href="#%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;정리
&lt;/h2&gt;&lt;p&gt;RFC 6202 를 읽어보면서 과거의 서버 푸쉬 이벤트를 어떤식으로 만들었는지 알아보았음. 기존에 알고 있던 polling, streaming, SSE 메커니즘에 대한 지식과 문제점들을 좀 더 상세하게 알아볼 수 있어서 좋았음.&lt;/p&gt;
&lt;p&gt;이 문서를 읽어보며 정리된 생각은 새로운 프로토콜이 아닌 HTTP 프로토콜을 확장해서 쓰는 개념이고 HTTP 의 설계상 양방향 비동기 통신을 위한 프로토콜이 아니기 때문에 남용을 해서 server event push 의 목적을 달성한 느낌이었음.&lt;/p&gt;
&lt;p&gt;다음 포스팅 에서는 RFC 6455 Websocket 문서를 정리해보면서 RFC 6202 에서의 문제점과 그 발전과정을 연결지어 작성해보려 함.&lt;/p&gt;</description></item><item><title>25년 간단한 회고와 26년 앞으로의 계획</title><link>https://0andwild.com/posts/260101_plan/</link><pubDate>Thu, 01 Jan 2026 23:03:28 +0900</pubDate><guid>https://0andwild.com/posts/260101_plan/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post 25년 간단한 회고와 26년 앞으로의 계획" /&gt;&lt;h1 id="25년을-되돌아보며"&gt;&lt;a href="#25%eb%85%84%ec%9d%84-%eb%90%98%eb%8f%8c%ec%95%84%eb%b3%b4%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;25년을 되돌아보며
&lt;/h1&gt;&lt;p&gt;24년 7월과 11월 궤양성 대장염이 악화되어 더 이상 치료할 수 있는 약이 없어 대장을 모두 절제하는 수술을 두 차례 받게 되었다. 회복할 시간이 필요했고, 25년 1월을 끝으로 첫 회사를 퇴사했다. 2년 2개월이라는 짧지 않은 시간 동안 많은 것을 배웠고 아쉬움도 많이 남았지만, 이렇게 마무리되었다.&lt;/p&gt;
&lt;p&gt;학생 때부터 알바와 일을 계속 해왔던 터라, 집에서 아무것도 하지 않고 휴식하는 것이 처음에는 어색하기도 하고 불안하기도 했다. 하지만 점차 적응해 나갔다. 수술의 여파가 커서 활동적인 것들은 할 수 없었지만 집에서 하고 싶었던 게임도 마음껏 하고 심적으로 힘들었던 부분도 많이 회복되었다.&lt;/p&gt;
&lt;p&gt;돌이켜보면 휴식 기간 동안에도 정보처리기사와 SQLD를 취득하고 사이드 프로젝트를 꾸준히 진행하며 프로그래밍 감각을 유지하려 노력했던 것 같다. (완전히 아무것도 하지 않고 쉬는 것은 안되는 것 같다&amp;hellip;)&lt;/p&gt;
&lt;p&gt;25년은 &amp;lsquo;퇴사&amp;rsquo;와 &amp;lsquo;회복&amp;rsquo;, 이 두 단어로 요약할 수 있는 한 해였다. 건강이 좋지 않아 힘든 시간이 많았지만, 잘 버텨낸 나 자신에게 칭찬을 해주고 싶다.&lt;/p&gt;
&lt;h1 id="26년-앞으로의-계획"&gt;&lt;a href="#26%eb%85%84-%ec%95%9e%ec%9c%bc%eb%a1%9c%ec%9d%98-%ea%b3%84%ed%9a%8d" class="header-anchor"&gt;&lt;/a&gt;26년 앞으로의 계획
&lt;/h1&gt;&lt;p&gt;새해를 맞이하며 몸도 많이 회복되었고, 다시 취업을 준비하고 있다. 관심 있는 분야는 금융권 또는 이커머스 도메인이며, 이쪽 분야에 맞춰 준비 중이다.&lt;/p&gt;
&lt;p&gt;앞으로의 일은 한 치 앞도 알 수 없지만, 꾸준히 준비하다 보면 좋은 기회가 올 것이라 믿는다.&lt;/p&gt;</description></item><item><title>코딩게임 추천 : 농부는 대체되었다</title><link>https://0andwild.com/posts/251215_game/</link><pubDate>Mon, 15 Dec 2025 16:26:41 +0900</pubDate><guid>https://0andwild.com/posts/251215_game/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post 코딩게임 추천 : 농부는 대체되었다" /&gt;&lt;p&gt;스팀 게임에서 재미난 게임을 발견해서 소개를 해보고자 한다. 😂&lt;/p&gt;
&lt;p&gt;&lt;img alt="게임 화면" class="gallery-image" data-flex-basis="403px" data-flex-grow="168" data-title-escaped="게임 화면" height="1221" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/251215_game/img1.png" srcset="https://0andwild.com/posts/251215_game/img1_hu_dbaea8ae4567c5eb.png 800w, https://0andwild.com/posts/251215_game/img1_hu_838718a872d3dce7.png 1600w, https://0andwild.com/posts/251215_game/img1.png 2055w" title="게임 화면" width="2055"&gt;
&lt;img alt="파이썬 for 문 설명" class="gallery-image" data-flex-basis="298px" data-flex-grow="124" data-title-escaped="파이썬 for 문 설명" height="611" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/251215_game/img3.png" title="파이썬 for 문 설명" width="759"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;농부는 대체되었다&lt;/strong&gt; 라는 게임인데 기본적으로 드론이 있고 이 드론을 파이썬 코드를 작성해서 제어하고 농작물 재배를 자동화 하는 게임이다. ㅋㅋㅋㅋㅋ 😂&lt;/p&gt;
&lt;p&gt;생각보다 파이썬 문법에 대한 설명도 잘 되어 있고 파이썬을 처음 써보시는 분들도 재미있게 공부하면서 연습하기에 나쁘지 않을 수 도 있을 것 같다 ?&lt;/p&gt;
&lt;p&gt;아직 한글 모드를 적용안해서 영어로 나오지만 한글 모드도 지원하는 것 같다..!&lt;/p&gt;
&lt;p&gt;&lt;img alt="스킬트리 화면" class="gallery-image" data-flex-basis="405px" data-flex-grow="169" data-title-escaped="스킬트리 화면" height="1213" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/251215_game/img2.png" srcset="https://0andwild.com/posts/251215_game/img2_hu_768712cf7a1c97b4.png 800w, https://0andwild.com/posts/251215_game/img2_hu_29d195d1de8a99d8.png 1600w, https://0andwild.com/posts/251215_game/img2.png 2050w" title="스킬트리 화면" width="2050"&gt;
&lt;img alt="인게임 Docs" class="gallery-image" data-flex-basis="417px" data-flex-grow="174" data-title-escaped="인게임 Docs" height="592" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/251215_game/img4.png" srcset="https://0andwild.com/posts/251215_game/img4_hu_aeecbe396f321bf0.png 800w, https://0andwild.com/posts/251215_game/img4.png 1031w" title="인게임 Docs" width="1031"&gt;&lt;/p&gt;
&lt;p&gt;게임을 해보면서 처음에 변수도 선언이 안되어서 뭐지 싶었는데 함수, 변수 등 모두 작물을 재배한 재화로 스킬을 찍어야 사용할 수 있는 그런 게임이다&amp;hellip;&lt;/p&gt;
&lt;p&gt;나름 중독성 있고 최종적으로 재배지를 늘리고 농작물을 심고 자라나는 속도에 맞추어 수확하는 최적화 움직임을 알고리즘으로 구현 하는 컨텐츠 인 것 같다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;오늘 기준으로 스팀에서 할인도 20% 하고 있으니 관심 있는 분들(개발자 ?)은 한번쯤 해보시는 걸 추천&amp;hellip;.? 😄&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;스팀 링크&lt;/strong&gt;: &lt;a class="link" href="https://store.steampowered.com/app/2060160/_/" target="_blank" rel="noopener"
 &gt;농부는 대체되었다&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Giscus로 Hugo 블로그에 댓글 기능 추가하기</title><link>https://0andwild.com/posts/251017_comments_giscus/</link><pubDate>Fri, 17 Oct 2025 12:00:00 +0900</pubDate><guid>https://0andwild.com/posts/251017_comments_giscus/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Giscus로 Hugo 블로그에 댓글 기능 추가하기" /&gt;&lt;h2 id="giscus란"&gt;&lt;a href="#giscus%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;Giscus란?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Giscus&lt;/strong&gt;는 GitHub Discussions를 백엔드로 사용하는 오픈소스 댓글 시스템입니다.&lt;/p&gt;
&lt;h3 id="주요-특징"&gt;&lt;a href="#%ec%a3%bc%ec%9a%94-%ed%8a%b9%ec%a7%95" class="header-anchor"&gt;&lt;/a&gt;주요 특징
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;완전 무료&lt;/strong&gt; (GitHub 기능 활용)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;서버 불필요&lt;/strong&gt; (GitHub이 모든 것을 처리)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Markdown 완벽 지원&lt;/strong&gt; (코드 블록, 이미지, 표 등)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;반응(Reactions)&lt;/strong&gt; (👍, ❤️, 😄 등)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;GitHub 알림&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="utterances와의-차이점"&gt;&lt;a href="#utterances%ec%99%80%ec%9d%98-%ec%b0%a8%ec%9d%b4%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;Utterances와의 차이점
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;기능&lt;/th&gt;
 &lt;th&gt;Giscus&lt;/th&gt;
 &lt;th&gt;Utterances&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;백엔드&lt;/td&gt;
 &lt;td&gt;GitHub Discussions&lt;/td&gt;
 &lt;td&gt;GitHub Issues&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;⚠️ (flat)&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;이슈 트래킹&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: Giscus가 Utterances의 상위 호환입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="사전-준비"&gt;&lt;a href="#%ec%82%ac%ec%a0%84-%ec%a4%80%eb%b9%84" class="header-anchor"&gt;&lt;/a&gt;사전 준비
&lt;/h2&gt;&lt;h3 id="필요한-것"&gt;&lt;a href="#%ed%95%84%ec%9a%94%ed%95%9c-%ea%b2%83" class="header-anchor"&gt;&lt;/a&gt;필요한 것
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;GitHub 계정&lt;/li&gt;
&lt;li&gt;Public GitHub 저장소 (블로그 저장소)&lt;/li&gt;
&lt;li&gt;Hugo + Blowfish 테마&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="제약사항"&gt;&lt;a href="#%ec%a0%9c%ec%95%bd%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;제약사항
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Public 저장소만 가능&lt;/strong&gt; (Private 저장소는 Discussions 기능 제한)&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;GitHub 계정 필요&lt;/strong&gt; (익명 댓글 불가)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="1단계-github-discussions-활성화"&gt;&lt;a href="#1%eb%8b%a8%ea%b3%84-github-discussions-%ed%99%9c%ec%84%b1%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;1단계: GitHub Discussions 활성화
&lt;/h2&gt;&lt;h3 id="11-저장소-설정-페이지-이동"&gt;&lt;a href="#11-%ec%a0%80%ec%9e%a5%ec%86%8c-%ec%84%a4%ec%a0%95-%ed%8e%98%ec%9d%b4%ec%a7%80-%ec%9d%b4%eb%8f%99" class="header-anchor"&gt;&lt;/a&gt;1.1 저장소 설정 페이지 이동
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;GitHub에서 블로그 저장소 접속&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;예: https://github.com/0AndWild/0AndWild.github.io
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Settings&lt;/strong&gt; 탭 클릭&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="12-discussions-활성화"&gt;&lt;a href="#12-discussions-%ed%99%9c%ec%84%b1%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;1.2 Discussions 활성화
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;페이지를 아래로 스크롤하여 &lt;strong&gt;Features&lt;/strong&gt; 섹션 찾기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discussions&lt;/strong&gt; 체크박스를 ✅ 체크&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;자동으로 저장됨&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="13-확인"&gt;&lt;a href="#13-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;1.3 확인
&lt;/h3&gt;&lt;p&gt;저장소 상단에 &lt;strong&gt;Discussions&lt;/strong&gt; 탭이 생성되었는지 확인&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Code | Issues | Pull requests | Discussions | ← 새로 생김!
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2단계-giscus-app-설치"&gt;&lt;a href="#2%eb%8b%a8%ea%b3%84-giscus-app-%ec%84%a4%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;2단계: Giscus App 설치
&lt;/h2&gt;&lt;h3 id="21-giscus-github-app-설치"&gt;&lt;a href="#21-giscus-github-app-%ec%84%a4%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;2.1 Giscus GitHub App 설치
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://github.com/apps/giscus" target="_blank" rel="noopener"
 &gt;https://github.com/apps/giscus&lt;/a&gt; 접속&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install&lt;/strong&gt; 버튼 클릭&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;권한 선택:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All repositories&lt;/strong&gt; (모든 저장소)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Only select repositories&lt;/strong&gt; (특정 저장소만 - 권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;블로그 저장소 선택:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0AndWild/0AndWild.github.io
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install&lt;/strong&gt; 클릭&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="22-권한-확인"&gt;&lt;a href="#22-%ea%b6%8c%ed%95%9c-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;2.2 권한 확인
&lt;/h3&gt;&lt;p&gt;Giscus가 요청하는 권한:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Read access to discussions&lt;/strong&gt; (토론 읽기)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Write access to discussions&lt;/strong&gt; (토론 쓰기)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Read access to metadata&lt;/strong&gt; (메타데이터 읽기)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3단계-giscus-설정-생성"&gt;&lt;a href="#3%eb%8b%a8%ea%b3%84-giscus-%ec%84%a4%ec%a0%95-%ec%83%9d%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;3단계: Giscus 설정 생성
&lt;/h2&gt;&lt;h3 id="31-giscus-웹사이트-접속"&gt;&lt;a href="#31-giscus-%ec%9b%b9%ec%82%ac%ec%9d%b4%ed%8a%b8-%ec%a0%91%ec%86%8d" class="header-anchor"&gt;&lt;/a&gt;3.1 Giscus 웹사이트 접속
&lt;/h3&gt;&lt;p&gt;&lt;a class="link" href="https://giscus.app/ko" target="_blank" rel="noopener"
 &gt;https://giscus.app/ko&lt;/a&gt; 방문&lt;/p&gt;
&lt;h3 id="32-저장소-연결"&gt;&lt;a href="#32-%ec%a0%80%ec%9e%a5%ec%86%8c-%ec%97%b0%ea%b2%b0" class="header-anchor"&gt;&lt;/a&gt;3.2 저장소 연결
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;저장소&lt;/strong&gt; 섹션에 입력:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0AndWild/0AndWild.github.io
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;아래에 성공 메시지가 표시되어야 함:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;✅ 성공! 이 저장소는 모든 조건을 만족합니다.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;만약 오류가 뜨면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Discussions 활성화 확인&lt;/li&gt;
&lt;li&gt;Giscus App 설치 확인&lt;/li&gt;
&lt;li&gt;저장소가 Public인지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="33-페이지--discussion-연결-방식"&gt;&lt;a href="#33-%ed%8e%98%ec%9d%b4%ec%a7%80--discussion-%ec%97%b0%ea%b2%b0-%eb%b0%a9%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;3.3 페이지 ↔️ Discussion 연결 방식
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Discussion 카테고리&lt;/strong&gt; 섹션에서 선택:&lt;/p&gt;
&lt;h4 id="권장-pathname-경로명"&gt;&lt;a href="#%ea%b6%8c%ec%9e%a5-pathname-%ea%b2%bd%eb%a1%9c%eb%aa%85" class="header-anchor"&gt;&lt;/a&gt;권장: &lt;code&gt;pathname&lt;/code&gt; (경로명)
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;매핑: pathname 선택
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;각 블로그 포스트의 경로가 Discussion 제목이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;포스트: &lt;code&gt;/posts/giscus-guide/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Discussion 제목: &lt;code&gt;posts/giscus-guide&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="대안들"&gt;&lt;a href="#%eb%8c%80%ec%95%88%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;대안들:
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;URL&lt;/code&gt;: 전체 URL 사용 (도메인 변경 시 문제)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;: 포스트 제목 사용 (제목 변경 시 문제)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;og:title&lt;/code&gt;: OpenGraph 제목&lt;/li&gt;
&lt;li&gt;&lt;code&gt;specific term&lt;/code&gt;: 직접 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;추천&lt;/strong&gt;: &lt;code&gt;pathname&lt;/code&gt; 사용&lt;/p&gt;
&lt;h3 id="34-discussion-카테고리-선택"&gt;&lt;a href="#34-discussion-%ec%b9%b4%ed%85%8c%ea%b3%a0%eb%a6%ac-%ec%84%a0%ed%83%9d" class="header-anchor"&gt;&lt;/a&gt;3.4 Discussion 카테고리 선택
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Discussion 카테고리&lt;/strong&gt; 드롭다운에서 선택:&lt;/p&gt;
&lt;h4 id="권장-announcements"&gt;&lt;a href="#%ea%b6%8c%ec%9e%a5-announcements" class="header-anchor"&gt;&lt;/a&gt;권장: &lt;code&gt;Announcements&lt;/code&gt;
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;카테고리: Announcements 선택
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;관리자만 새 Discussion 생성 가능&lt;/li&gt;
&lt;li&gt;댓글은 누구나 가능&lt;/li&gt;
&lt;li&gt;블로그 포스트용으로 최적&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="대안-general"&gt;&lt;a href="#%eb%8c%80%ec%95%88-general" class="header-anchor"&gt;&lt;/a&gt;대안: &lt;code&gt;General&lt;/code&gt;
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;누구나 Discussion 생성 가능&lt;/li&gt;
&lt;li&gt;더 개방적&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;추천&lt;/strong&gt;: &lt;code&gt;Announcements&lt;/code&gt; (블로그에 적합)&lt;/p&gt;
&lt;h3 id="35-기능-선택"&gt;&lt;a href="#35-%ea%b8%b0%eb%8a%a5-%ec%84%a0%ed%83%9d" class="header-anchor"&gt;&lt;/a&gt;3.5 기능 선택
&lt;/h3&gt;&lt;h4 id="반응-활성화"&gt;&lt;a href="#%eb%b0%98%ec%9d%91-%ed%99%9c%ec%84%b1%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;반응 활성화
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;✅ 반응 활성화
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;사용자가 👍, ❤️, 😄 등으로 반응 가능&lt;/p&gt;
&lt;h4 id="메타데이터-보내기"&gt;&lt;a href="#%eb%a9%94%ed%83%80%eb%8d%b0%ec%9d%b4%ed%84%b0-%eb%b3%b4%eb%82%b4%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;메타데이터 보내기
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;□ 메타데이터 보내기 (체크 해제 권장)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;불필요한 기능, 꺼두는 것이 좋음&lt;/p&gt;
&lt;h4 id="댓글-입력란-위치"&gt;&lt;a href="#%eb%8c%93%ea%b8%80-%ec%9e%85%eb%a0%a5%eb%9e%80-%ec%9c%84%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;댓글 입력란 위치
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&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;기존 댓글을 먼저 읽고 작성하도록 유도&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="느긋한-로딩"&gt;&lt;a href="#%eb%8a%90%ea%b8%8b%ed%95%9c-%eb%a1%9c%eb%94%a9" class="header-anchor"&gt;&lt;/a&gt;느긋한 로딩
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;✅ 느긋한 로딩
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;페이지 로딩 속도 향상 (권장)&lt;/p&gt;
&lt;h3 id="36-테마-선택"&gt;&lt;a href="#36-%ed%85%8c%eb%a7%88-%ec%84%a0%ed%83%9d" class="header-anchor"&gt;&lt;/a&gt;3.6 테마 선택
&lt;/h3&gt;&lt;h4 id="권장-preferred_color_scheme"&gt;&lt;a href="#%ea%b6%8c%ec%9e%a5-preferred_color_scheme" class="header-anchor"&gt;&lt;/a&gt;권장: &lt;code&gt;preferred_color_scheme&lt;/code&gt;
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;테마: preferred_color_scheme
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;동작&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자의 시스템 설정에 따라 자동 전환&lt;/li&gt;
&lt;li&gt;다크모드 ↔️ 라이트모드 자동&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="대안"&gt;&lt;a href="#%eb%8c%80%ec%95%88" class="header-anchor"&gt;&lt;/a&gt;대안:
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;light&lt;/code&gt;: 항상 밝은 테마&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dark&lt;/code&gt;: 항상 어두운 테마&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transparent_dark&lt;/code&gt;: 투명 다크&lt;/li&gt;
&lt;li&gt;기타 GitHub 테마들&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;추천&lt;/strong&gt;: &lt;code&gt;preferred_color_scheme&lt;/code&gt; (자동 전환)&lt;/p&gt;
&lt;h3 id="37-언어-설정"&gt;&lt;a href="#37-%ec%96%b8%ec%96%b4-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;3.7 언어 설정
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;언어: ko (한국어)
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="4단계-생성된-코드-복사"&gt;&lt;a href="#4%eb%8b%a8%ea%b3%84-%ec%83%9d%ec%84%b1%eb%90%9c-%ec%bd%94%eb%93%9c-%eb%b3%b5%ec%82%ac" class="header-anchor"&gt;&lt;/a&gt;4단계: 생성된 코드 복사
&lt;/h2&gt;&lt;h3 id="41-스크립트-복사"&gt;&lt;a href="#41-%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-%eb%b3%b5%ec%82%ac" class="header-anchor"&gt;&lt;/a&gt;4.1 스크립트 복사
&lt;/h3&gt;&lt;p&gt;페이지 하단에 &lt;strong&gt;Enable giscus&lt;/strong&gt; 섹션에서 생성된 코드 복사:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://giscus.app/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0AndWild/0AndWild.github.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;R_kgDOxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Announcements&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DIC_kwDOxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pathname&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-reactions-enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-emit-metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-input-position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bottom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;preferred_color_scheme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ko&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-loading&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;lazy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="42-중요한-값들"&gt;&lt;a href="#42-%ec%a4%91%ec%9a%94%ed%95%9c-%ea%b0%92%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;4.2 중요한 값들
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data-repo-id&lt;/code&gt;: 저장소 고유 ID (자동 생성)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-category-id&lt;/code&gt;: 카테고리 고유 ID (자동 생성)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 값들은 본인의 저장소마다 다르므로, 반드시 Giscus 웹사이트에서 생성된 코드를 사용해야 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="5단계-blowfish-테마에-통합"&gt;&lt;a href="#5%eb%8b%a8%ea%b3%84-blowfish-%ed%85%8c%eb%a7%88%ec%97%90-%ed%86%b5%ed%95%a9" class="header-anchor"&gt;&lt;/a&gt;5단계: Blowfish 테마에 통합
&lt;/h2&gt;&lt;h3 id="51-디렉토리-생성"&gt;&lt;a href="#51-%eb%94%94%eb%a0%89%ed%86%a0%eb%a6%ac-%ec%83%9d%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;5.1 디렉토리 생성
&lt;/h3&gt;&lt;p&gt;터미널에서 블로그 루트 디렉토리로 이동 후:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p layouts/partials
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="52-commentshtml-파일-생성"&gt;&lt;a href="#52-commentshtml-%ed%8c%8c%ec%9d%bc-%ec%83%9d%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;5.2 comments.html 파일 생성
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;touch layouts/partials/comments.html
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;또는 IDE/에디터에서 직접 생성:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;layouts/
 └── partials/
 └── comments.html ← 새로 생성
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="53-giscus-코드-삽입"&gt;&lt;a href="#53-giscus-%ec%bd%94%eb%93%9c-%ec%82%bd%ec%9e%85" class="header-anchor"&gt;&lt;/a&gt;5.3 Giscus 코드 삽입
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;layouts/partials/comments.html&lt;/code&gt; 파일에 다음 내용 추가:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- Giscus 댓글 시스템 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://giscus.app/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0AndWild/0AndWild.github.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;R_kgDOxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Announcements&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DIC_kwDOxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pathname&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-reactions-enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-emit-metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-input-position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bottom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;preferred_color_scheme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ko&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-loading&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;lazy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;⚠️ &lt;strong&gt;주의&lt;/strong&gt;: 위의 &lt;code&gt;data-repo-id&lt;/code&gt;와 &lt;code&gt;data-category-id&lt;/code&gt; 값을 &lt;strong&gt;본인의 값으로 교체&lt;/strong&gt;해야 합니다!&lt;/p&gt;
&lt;h3 id="54-paramstoml-설정"&gt;&lt;a href="#54-paramstoml-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;5.4 params.toml 설정
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;config/_default/params.toml&lt;/code&gt; 파일을 열고 &lt;code&gt;[article]&lt;/code&gt; 섹션에 추가:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;showComments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c"&gt;# 이 줄 추가 또는 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;# ... 기타 설정들&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이미 &lt;code&gt;showComments&lt;/code&gt; 항목이 있다면 &lt;code&gt;true&lt;/code&gt;로 설정되어 있는지 확인하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="6단계-로컬-테스트"&gt;&lt;a href="#6%eb%8b%a8%ea%b3%84-%eb%a1%9c%ec%bb%ac-%ed%85%8c%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;6단계: 로컬 테스트
&lt;/h2&gt;&lt;h3 id="61-hugo-서버-실행"&gt;&lt;a href="#61-hugo-%ec%84%9c%eb%b2%84-%ec%8b%a4%ed%96%89" class="header-anchor"&gt;&lt;/a&gt;6.1 Hugo 서버 실행
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server -D
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="62-브라우저에서-확인"&gt;&lt;a href="#62-%eb%b8%8c%eb%9d%bc%ec%9a%b0%ec%a0%80%ec%97%90%ec%84%9c-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;6.2 브라우저에서 확인
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;http://localhost:1313
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;포스트 페이지 하단에 Giscus 댓글 위젯이 표시되어야 합니다.&lt;/p&gt;
&lt;h3 id="63-테스트-댓글-작성"&gt;&lt;a href="#63-%ed%85%8c%ec%8a%a4%ed%8a%b8-%eb%8c%93%ea%b8%80-%ec%9e%91%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;6.3 테스트 댓글 작성
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitHub으로 로그인&lt;/strong&gt; 버튼 클릭&lt;/li&gt;
&lt;li&gt;GitHub OAuth 인증&lt;/li&gt;
&lt;li&gt;테스트 댓글 작성&lt;/li&gt;
&lt;li&gt;댓글이 표시되는지 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="64-github-discussions-확인"&gt;&lt;a href="#64-github-discussions-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;6.4 GitHub Discussions 확인
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;GitHub 저장소 → &lt;strong&gt;Discussions&lt;/strong&gt; 탭&lt;/li&gt;
&lt;li&gt;Announcements 카테고리에 새 Discussion 생성되었는지 확인&lt;/li&gt;
&lt;li&gt;Discussion 제목이 포스트 경로인지 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="7단계-배포"&gt;&lt;a href="#7%eb%8b%a8%ea%b3%84-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;7단계: 배포
&lt;/h2&gt;&lt;h3 id="71-git에-커밋"&gt;&lt;a href="#71-git%ec%97%90-%ec%bb%a4%eb%b0%8b" class="header-anchor"&gt;&lt;/a&gt;7.1 Git에 커밋
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add layouts/partials/comments.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add config/_default/params.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Add Giscus comments system&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="72-github에-푸시"&gt;&lt;a href="#72-github%ec%97%90-%ed%91%b8%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;7.2 GitHub에 푸시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="73-github-actions-확인"&gt;&lt;a href="#73-github-actions-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;7.3 GitHub Actions 확인
&lt;/h3&gt;&lt;p&gt;GitHub Actions가 자동으로 빌드 및 배포를 진행합니다.&lt;/p&gt;
&lt;p&gt;배포 상태 확인:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GitHub 저장소 → Actions 탭
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="74-배포된-사이트-확인"&gt;&lt;a href="#74-%eb%b0%b0%ed%8f%ac%eb%90%9c-%ec%82%ac%ec%9d%b4%ed%8a%b8-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;7.4 배포된 사이트 확인
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;https://0andwild.github.io
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;포스트 페이지에 댓글 위젯이 정상적으로 표시되는지 확인하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="고급-설정"&gt;&lt;a href="#%ea%b3%a0%ea%b8%89-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;고급 설정
&lt;/h2&gt;&lt;h3 id="다크모드-및-언어-동적-설정-권장"&gt;&lt;a href="#%eb%8b%a4%ed%81%ac%eb%aa%a8%eb%93%9c-%eb%b0%8f-%ec%96%b8%ec%96%b4-%eb%8f%99%ec%a0%81-%ec%84%a4%ec%a0%95-%ea%b6%8c%ec%9e%a5" class="header-anchor"&gt;&lt;/a&gt;다크모드 및 언어 동적 설정 (권장)
&lt;/h3&gt;&lt;p&gt;Blowfish 테마의 다크모드 토글과 언어 전환에 따라 Giscus가 자동으로 변경되도록 설정하는 완전한 방법입니다.&lt;/p&gt;
&lt;h4 id="완전한-동적-설정"&gt;&lt;a href="#%ec%99%84%ec%a0%84%ed%95%9c-%eb%8f%99%ec%a0%81-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;완전한 동적 설정
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;layouts/partials/comments.html&lt;/code&gt; 전체 코드:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- Giscus Comments with Dynamic Theme and Language --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ $lang := .Site.Language.Lang }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ $translationKey := .File.TranslationBaseName }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Get current theme (dark/light)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark_tritanopia&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light_tritanopia&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Get language from Hugo template
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentLang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{{ $lang }}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Use file directory path for unified comments across languages
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Example: &amp;#34;posts/subscription_alert&amp;#34; for both index.ko.md and index.en.md
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discussionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{{ .File.Dir | replaceRE &amp;#34;^content/&amp;#34; &amp;#34;&amp;#34; | replaceRE &amp;#34;/$&amp;#34; &amp;#34;&amp;#34; }}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Wait for DOM to be ready
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;loading&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DOMContentLoaded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initGiscus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;initGiscus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;initGiscus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Create and insert Giscus script with dynamic settings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;script&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://giscus.app/client.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-repo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0AndWild/0AndWild.github.io&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-repo-id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R_kgDOQAqZFA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;General&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-category-id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DIC_kwDOQAqZFM4CwwRg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-mapping&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;specific&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-term&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discussionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-strict&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-reactions-enabled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-emit-metadata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-input-position&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bottom&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-theme&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-lang&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentLang&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-loading&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lazy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;crossorigin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;anonymous&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Find giscus container or create one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.giscus-container&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentScript&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Monitor theme changes and update Giscus
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;updateGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;iframe.giscus-frame&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;giscus&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;https://giscus.app&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Giscus theme update delayed, will retry...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Watch for theme changes using MutationObserver
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributeName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Delay update to ensure iframe is ready
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Start observing after a short delay
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;attributeFilter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Update theme when Giscus iframe loads
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://giscus.app&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;giscus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Giscus is ready, update theme
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;/* Ensure Giscus iframe has proper height and displays all content */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;giscus-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;giscus-container&lt;/span&gt; &lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;giscus-frame&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;/* Make sure comment actions are visible */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;giscus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;visible&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;giscus-container&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="동작-방식-설명"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-%ec%84%a4%eb%aa%85" class="header-anchor"&gt;&lt;/a&gt;동작 방식 설명
&lt;/h4&gt;&lt;h5 id="1-언어-동적-설정"&gt;&lt;a href="#1-%ec%96%b8%ec%96%b4-%eb%8f%99%ec%a0%81-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;1. &lt;strong&gt;언어 동적 설정&lt;/strong&gt;
&lt;/h5&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentLang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Hugo 템플릿에서 현재 페이지 언어 가져오기&lt;/li&gt;
&lt;li&gt;한국어 페이지: &lt;code&gt;ko&lt;/code&gt;, 영어 페이지: &lt;code&gt;en&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Giscus에 해당 언어로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한국어 페이지 → Giscus UI가 한국어로 표시&lt;/li&gt;
&lt;li&gt;영어 페이지 → Giscus UI가 영어로 표시&lt;/li&gt;
&lt;li&gt;언어 전환 시 페이지 리로드되면서 자동으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="2-다크모드-동적-설정"&gt;&lt;a href="#2-%eb%8b%a4%ed%81%ac%eb%aa%a8%eb%93%9c-%eb%8f%99%ec%a0%81-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;2. &lt;strong&gt;다크모드 동적 설정&lt;/strong&gt;
&lt;/h5&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark_tritanopia&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light_tritanopia&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Blowfish 테마는 다크모드 시 &lt;code&gt;&amp;lt;html class=&amp;quot;dark&amp;quot;&amp;gt;&lt;/code&gt; 추가&lt;/li&gt;
&lt;li&gt;이를 감지하여 테마 결정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dark_tritanopia&lt;/code&gt; / &lt;code&gt;light_tritanopia&lt;/code&gt; 테마 사용 (색맹 친화적)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;페이지 로드 시: 현재 테마 상태로 Giscus 로드&lt;/li&gt;
&lt;li&gt;다크모드 토글 클릭 시: 실시간으로 Giscus 테마 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="3-언어별-댓글-통합"&gt;&lt;a href="#3-%ec%96%b8%ec%96%b4%eb%b3%84-%eb%8c%93%ea%b8%80-%ed%86%b5%ed%95%a9" class="header-anchor"&gt;&lt;/a&gt;3. &lt;strong&gt;언어별 댓글 통합&lt;/strong&gt;
&lt;/h5&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;discussionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;replaceRE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;^content/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;replaceRE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/$&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;파일 디렉토리 경로를 Discussion ID로 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content/posts/subscription_alert/index.ko.md&lt;/code&gt; → &lt;code&gt;posts/subscription_alert&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content/posts/subscription_alert/index.en.md&lt;/code&gt; → &lt;code&gt;posts/subscription_alert&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;같은 ID이므로 한국어/영어 버전이 같은 댓글 공유&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한국어 포스트에서 작성한 댓글&lt;/li&gt;
&lt;li&gt;영어 포스트에서도 동일하게 표시&lt;/li&gt;
&lt;li&gt;포스트별로는 별도 Discussion 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="4-실시간-테마-변경-감지"&gt;&lt;a href="#4-%ec%8b%a4%ec%8b%9c%ea%b0%84-%ed%85%8c%eb%a7%88-%eb%b3%80%ea%b2%bd-%ea%b0%90%ec%a7%80" class="header-anchor"&gt;&lt;/a&gt;4. &lt;strong&gt;실시간 테마 변경 감지&lt;/strong&gt;
&lt;/h5&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributeName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MutationObserver&lt;/code&gt;로 HTML 클래스 변경 감지&lt;/li&gt;
&lt;li&gt;다크모드 토글 클릭 시 즉시 감지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;postMessage&lt;/code&gt;로 Giscus iframe에 테마 변경 명령 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="테스트-방법"&gt;&lt;a href="#%ed%85%8c%ec%8a%a4%ed%8a%b8-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;테스트 방법
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. 로컬 서버 실행&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server -D
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. 브라우저에서 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http://localhost:1313/posts/subscription_alert/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;테스트 항목&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;✅ 페이지 로드 시 현재 테마(라이트/다크)로 Giscus 표시&lt;/li&gt;
&lt;li&gt;✅ 다크모드 토글 클릭 시 Giscus 테마 즉시 변경&lt;/li&gt;
&lt;li&gt;✅ 언어 전환 (ko → en) 시 Giscus 언어 변경&lt;/li&gt;
&lt;li&gt;✅ 한국어/영어 페이지에서 같은 댓글 표시&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="테마-옵션-변경"&gt;&lt;a href="#%ed%85%8c%eb%a7%88-%ec%98%b5%ec%85%98-%eb%b3%80%ea%b2%bd" class="header-anchor"&gt;&lt;/a&gt;테마 옵션 변경
&lt;/h4&gt;&lt;p&gt;다른 테마를 사용하려면 &lt;code&gt;getGiscusTheme()&lt;/code&gt; 함수 수정:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 기본 테마
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 고대비 테마
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark_high_contrast&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light_high_contrast&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// GitHub 스타일 테마
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getGiscusTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark_dimmed&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;사용 가능한 테마&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;light&lt;/code&gt; / &lt;code&gt;dark&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;light_high_contrast&lt;/code&gt; / &lt;code&gt;dark_high_contrast&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;light_tritanopia&lt;/code&gt; / &lt;code&gt;dark_tritanopia&lt;/code&gt; (색맹 친화적)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dark_dimmed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transparent_dark&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;preferred_color_scheme&lt;/code&gt; (시스템 설정 따름)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="정적-테마-설정-간단한-방법"&gt;&lt;a href="#%ec%a0%95%ec%a0%81-%ed%85%8c%eb%a7%88-%ec%84%a4%ec%a0%95-%ea%b0%84%eb%8b%a8%ed%95%9c-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;정적 테마 설정 (간단한 방법)
&lt;/h4&gt;&lt;p&gt;동적 변경이 필요 없다면 정적으로 설정 가능:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://giscus.app/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0AndWild/0AndWild.github.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;R_kgDOxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;General&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DIC_kwDOxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pathname&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;preferred_color_scheme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ko&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;: 간단함
&lt;strong&gt;단점&lt;/strong&gt;: 실시간 테마 변경 불가, 언어별 댓글 분리됨&lt;/p&gt;
&lt;h3 id="포스트별-댓글-숨기기"&gt;&lt;a href="#%ed%8f%ac%ec%8a%a4%ed%8a%b8%eb%b3%84-%eb%8c%93%ea%b8%80-%ec%88%a8%ea%b8%b0%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;포스트별 댓글 숨기기
&lt;/h3&gt;&lt;p&gt;특정 포스트에서만 댓글을 숨기려면, 해당 포스트의 front matter에:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;댓글 없는 포스트&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;showComments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 이 포스트만 댓글 숨김&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="카테고리별-댓글-분리"&gt;&lt;a href="#%ec%b9%b4%ed%85%8c%ea%b3%a0%eb%a6%ac%eb%b3%84-%eb%8c%93%ea%b8%80-%eb%b6%84%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;카테고리별 댓글 분리
&lt;/h3&gt;&lt;p&gt;다른 카테고리의 포스트에 다른 Discussion 카테고리를 사용하려면:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 조건부 카테고리 설정 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Tutorial&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;DIC_kwDOxxxxTutorial&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;DIC_kwDOxxxxGeneral&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="p"&gt;}};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://giscus.app/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ category }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="문제-해결"&gt;&lt;a href="#%eb%ac%b8%ec%a0%9c-%ed%95%b4%ea%b2%b0" class="header-anchor"&gt;&lt;/a&gt;문제 해결
&lt;/h2&gt;&lt;h3 id="댓글-위젯이-표시되지-않음"&gt;&lt;a href="#%eb%8c%93%ea%b8%80-%ec%9c%84%ec%a0%af%ec%9d%b4-%ed%91%9c%ec%8b%9c%eb%90%98%ec%a7%80-%ec%95%8a%ec%9d%8c" class="header-anchor"&gt;&lt;/a&gt;댓글 위젯이 표시되지 않음
&lt;/h3&gt;&lt;h4 id="원인-1-discussions-미활성화"&gt;&lt;a href="#%ec%9b%90%ec%9d%b8-1-discussions-%eb%af%b8%ed%99%9c%ec%84%b1%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;원인 1: Discussions 미활성화
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;해결: GitHub 저장소 → Settings → Discussions 체크
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="원인-2-giscus-app-미설치"&gt;&lt;a href="#%ec%9b%90%ec%9d%b8-2-giscus-app-%eb%af%b8%ec%84%a4%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;원인 2: Giscus App 미설치
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;해결: https://github.com/apps/giscus 에서 Install
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="원인-3-저장소-id-오류"&gt;&lt;a href="#%ec%9b%90%ec%9d%b8-3-%ec%a0%80%ec%9e%a5%ec%86%8c-id-%ec%98%a4%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;원인 3: 저장소 ID 오류
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;해결: giscus.app에서 코드 재생성
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="원인-4-showcomments-설정-누락"&gt;&lt;a href="#%ec%9b%90%ec%9d%b8-4-showcomments-%ec%84%a4%ec%a0%95-%eb%88%84%eb%9d%bd" class="header-anchor"&gt;&lt;/a&gt;원인 4: showComments 설정 누락
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# config/_default/params.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;showComments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c"&gt;# 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="로그인-버튼만-보이고-댓글-못-씀"&gt;&lt;a href="#%eb%a1%9c%ea%b7%b8%ec%9d%b8-%eb%b2%84%ed%8a%bc%eb%a7%8c-%eb%b3%b4%ec%9d%b4%ea%b3%a0-%eb%8c%93%ea%b8%80-%eb%aa%bb-%ec%94%80" class="header-anchor"&gt;&lt;/a&gt;로그인 버튼만 보이고 댓글 못 씀
&lt;/h3&gt;&lt;h4 id="원인-github-oauth-승인-필요"&gt;&lt;a href="#%ec%9b%90%ec%9d%b8-github-oauth-%ec%8a%b9%ec%9d%b8-%ed%95%84%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;원인: GitHub OAuth 승인 필요
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. &lt;span class="s2"&gt;&amp;#34;GitHub으로 로그인&amp;#34;&lt;/span&gt; 클릭
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. OAuth 권한 승인
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 저장소로 리다이렉트
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 댓글 작성 가능
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="댓글이-저장되지-않음"&gt;&lt;a href="#%eb%8c%93%ea%b8%80%ec%9d%b4-%ec%a0%80%ec%9e%a5%eb%90%98%ec%a7%80-%ec%95%8a%ec%9d%8c" class="header-anchor"&gt;&lt;/a&gt;댓글이 저장되지 않음
&lt;/h3&gt;&lt;h4 id="원인-저장소-권한-문제"&gt;&lt;a href="#%ec%9b%90%ec%9d%b8-%ec%a0%80%ec%9e%a5%ec%86%8c-%ea%b6%8c%ed%95%9c-%eb%ac%b8%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;원인: 저장소 권한 문제
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;확인 사항:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 저장소가 Public인지
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. Giscus App 권한에 저장소 포함되어 있는지
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Discussion 카테고리가 존재하는지
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="다크모드가-동기화-안-됨"&gt;&lt;a href="#%eb%8b%a4%ed%81%ac%eb%aa%a8%eb%93%9c%ea%b0%80-%eb%8f%99%ea%b8%b0%ed%99%94-%ec%95%88-%eb%90%a8" class="header-anchor"&gt;&lt;/a&gt;다크모드가 동기화 안 됨
&lt;/h3&gt;&lt;h4 id="해결-javascript-동기화-코드-추가"&gt;&lt;a href="#%ed%95%b4%ea%b2%b0-javascript-%eb%8f%99%ea%b8%b0%ed%99%94-%ec%bd%94%eb%93%9c-%ec%b6%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;해결: JavaScript 동기화 코드 추가
&lt;/h4&gt;&lt;p&gt;위의 &amp;ldquo;고급 설정 &amp;gt; 다크모드 자동 전환&amp;rdquo; 참고&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="giscus-관리"&gt;&lt;a href="#giscus-%ea%b4%80%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;Giscus 관리
&lt;/h2&gt;&lt;h3 id="댓글-관리"&gt;&lt;a href="#%eb%8c%93%ea%b8%80-%ea%b4%80%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;댓글 관리
&lt;/h3&gt;&lt;h4 id="github-discussions에서-관리"&gt;&lt;a href="#github-discussions%ec%97%90%ec%84%9c-%ea%b4%80%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;GitHub Discussions에서 관리
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. GitHub 저장소 → Discussions 탭
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 해당 Discussion 클릭
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 관리 작업:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 댓글 수정 &lt;span class="o"&gt;(&lt;/span&gt;본인 댓글만&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 댓글 삭제 &lt;span class="o"&gt;(&lt;/span&gt;관리자&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 사용자 차단 &lt;span class="o"&gt;(&lt;/span&gt;관리자&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - Discussion 잠금 &lt;span class="o"&gt;(&lt;/span&gt;관리자&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="스팸-댓글-처리"&gt;&lt;a href="#%ec%8a%a4%ed%8c%b8-%eb%8c%93%ea%b8%80-%ec%b2%98%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;스팸 댓글 처리
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. GitHub Discussions에서 스팸 댓글 찾기
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 댓글 옆 ... 메뉴 → &lt;span class="s2"&gt;&amp;#34;Delete&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 사용자 차단: 프로필 → Block user
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="알림-설정"&gt;&lt;a href="#%ec%95%8c%eb%a6%bc-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;알림 설정
&lt;/h3&gt;&lt;h4 id="github-알림으로-댓글-알림-받기"&gt;&lt;a href="#github-%ec%95%8c%eb%a6%bc%ec%9c%bc%eb%a1%9c-%eb%8c%93%ea%b8%80-%ec%95%8c%eb%a6%bc-%eb%b0%9b%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;GitHub 알림으로 댓글 알림 받기
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. GitHub → Settings → Notifications
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. Watching에 저장소 추가
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 이메일로 알림 받기 설정
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="특정-discussion만-알림-받기"&gt;&lt;a href="#%ed%8a%b9%ec%a0%95-discussion%eb%a7%8c-%ec%95%8c%eb%a6%bc-%eb%b0%9b%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;특정 Discussion만 알림 받기
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Discussions 탭 → 해당 Discussion
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 오른쪽 &lt;span class="s2"&gt;&amp;#34;Subscribe&amp;#34;&lt;/span&gt; 버튼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. &lt;span class="s2"&gt;&amp;#34;Notify me&amp;#34;&lt;/span&gt; 선택
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="통계-및-분석"&gt;&lt;a href="#%ed%86%b5%ea%b3%84-%eb%b0%8f-%eb%b6%84%ec%84%9d" class="header-anchor"&gt;&lt;/a&gt;통계 및 분석
&lt;/h2&gt;&lt;h3 id="댓글-통계-보기"&gt;&lt;a href="#%eb%8c%93%ea%b8%80-%ed%86%b5%ea%b3%84-%eb%b3%b4%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;댓글 통계 보기
&lt;/h3&gt;&lt;p&gt;GitHub Discussions에서:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Discussions 탭
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 카테고리별 Discussion 수 확인
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 각 Discussion의 댓글 수 확인
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="github-insights-활용"&gt;&lt;a href="#github-insights-%ed%99%9c%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;GitHub Insights 활용
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GitHub 저장소 → Insights → Community
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ Discussions 활동 확인
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="비용-및-제한사항"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9-%eb%b0%8f-%ec%a0%9c%ed%95%9c%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;비용 및 제한사항
&lt;/h2&gt;&lt;h3 id="비용"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;비용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;완전 무료&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub 계정만 있으면 사용 가능&lt;/li&gt;
&lt;li&gt;저장소 크기 제한 내에서 무제한 댓글&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="제한사항"&gt;&lt;a href="#%ec%a0%9c%ed%95%9c%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;제한사항
&lt;/h3&gt;&lt;h4 id="github-api-rate-limit"&gt;&lt;a href="#github-api-rate-limit" class="header-anchor"&gt;&lt;/a&gt;GitHub API Rate Limit
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;시간당 60회 (미인증)&lt;/li&gt;
&lt;li&gt;시간당 5,000회 (인증)&lt;/li&gt;
&lt;li&gt;Giscus는 캐싱으로 최적화되어 있어 문제 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="저장소-크기"&gt;&lt;a href="#%ec%a0%80%ec%9e%a5%ec%86%8c-%ed%81%ac%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;저장소 크기
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;GitHub Free: 저장소당 1GB&lt;/li&gt;
&lt;li&gt;텍스트 댓글만으로는 제한 도달 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="discussions-제한"&gt;&lt;a href="#discussions-%ec%a0%9c%ed%95%9c" class="header-anchor"&gt;&lt;/a&gt;Discussions 제한
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;없음 (무제한)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="대안-비교"&gt;&lt;a href="#%eb%8c%80%ec%95%88-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;대안 비교
&lt;/h2&gt;&lt;h3 id="giscus-vs-utterances"&gt;&lt;a href="#giscus-vs-utterances" class="header-anchor"&gt;&lt;/a&gt;Giscus vs Utterances
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;항목&lt;/th&gt;
 &lt;th&gt;Giscus&lt;/th&gt;
 &lt;th&gt;Utterances&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;백엔드&lt;/td&gt;
 &lt;td&gt;Discussions&lt;/td&gt;
 &lt;td&gt;Issues&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;Flat&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: Giscus 사용 권장&lt;/p&gt;
&lt;h3 id="giscus-vs-disqus"&gt;&lt;a href="#giscus-vs-disqus" class="header-anchor"&gt;&lt;/a&gt;Giscus vs Disqus
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;항목&lt;/th&gt;
 &lt;th&gt;Giscus&lt;/th&gt;
 &lt;th&gt;Disqus&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;무료 (광고)&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;✅ (Guest)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Markdown&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;❌&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;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="마이그레이션-가이드"&gt;&lt;a href="#%eb%a7%88%ec%9d%b4%ea%b7%b8%eb%a0%88%ec%9d%b4%ec%85%98-%ea%b0%80%ec%9d%b4%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;마이그레이션 가이드
&lt;/h2&gt;&lt;h3 id="utterances--giscus"&gt;&lt;a href="#utterances--giscus" class="header-anchor"&gt;&lt;/a&gt;Utterances → Giscus
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. GitHub Issues를 Discussions로 변환
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 수동 작업 필요 &lt;span class="o"&gt;(&lt;/span&gt;자동화 없음&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 또는 Issues 그대로 두고 Giscus 새로 시작
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. comments.html 파일 교체
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - Utterances 코드 삭제
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - Giscus 코드 추가
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 배포
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="disqus--giscus"&gt;&lt;a href="#disqus--giscus" class="header-anchor"&gt;&lt;/a&gt;Disqus → Giscus
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Disqus 데이터 Export &lt;span class="o"&gt;(&lt;/span&gt;XML&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. GitHub Discussions로 수동 이전
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 자동화 도구 없음
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 스크립트 직접 작성 필요
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 또는 새로 시작 권장
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="추가-리소스"&gt;&lt;a href="#%ec%b6%94%ea%b0%80-%eb%a6%ac%ec%86%8c%ec%8a%a4" class="header-anchor"&gt;&lt;/a&gt;추가 리소스
&lt;/h2&gt;&lt;h3 id="공식-문서"&gt;&lt;a href="#%ea%b3%b5%ec%8b%9d-%eb%ac%b8%ec%84%9c" class="header-anchor"&gt;&lt;/a&gt;공식 문서
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://giscus.app/ko" target="_blank" rel="noopener"
 &gt;Giscus 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/giscus/giscus" target="_blank" rel="noopener"
 &gt;Giscus GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="커뮤니티"&gt;&lt;a href="#%ec%bb%a4%eb%ae%a4%eb%8b%88%ed%8b%b0" class="header-anchor"&gt;&lt;/a&gt;커뮤니티
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/giscus/giscus/discussions" target="_blank" rel="noopener"
 &gt;Giscus Discussions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blowfish.page/docs/" target="_blank" rel="noopener"
 &gt;Blowfish 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="체크리스트"&gt;&lt;a href="#%ec%b2%b4%ed%81%ac%eb%a6%ac%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;체크리스트
&lt;/h2&gt;&lt;p&gt;설치 완료 확인:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; GitHub Discussions 활성화&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Giscus App 설치&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;code&gt;layouts/partials/comments.html&lt;/code&gt; 생성&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Giscus 코드 삽입 (본인의 ID로)&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;code&gt;params.toml&lt;/code&gt;에 &lt;code&gt;showComments = true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 로컬 테스트 완료&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; GitHub에 푸시&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 배포된 사이트에서 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 테스트 댓글 작성&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; GitHub Discussions에 생성 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="결론"&gt;&lt;a href="#%ea%b2%b0%eb%a1%a0" class="header-anchor"&gt;&lt;/a&gt;결론
&lt;/h2&gt;&lt;p&gt;Giscus는 Hugo/GitHub Pages 블로그에 가장 적합한 댓글 시스템입니다:&lt;/p&gt;
&lt;h3 id="장점-정리"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;장점 정리
&lt;/h3&gt;&lt;p&gt;✅ 완전 무료
✅ 설정 간단 (10분)
✅ 서버 불필요
✅ Markdown 완벽 지원
✅ GitHub 통합
✅ 데이터 소유&lt;/p&gt;
&lt;h3 id="단점"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;p&gt;❌ GitHub 계정 필수 (익명 불가)
❌ 기술 블로그에 적합 (일반 사용자는 허들 있음)&lt;/p&gt;
&lt;h3 id="추천-대상"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&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;/ul&gt;</description></item><item><title>Hugo &amp; GithubPages 블로그 댓글 시스템 구현 가이드</title><link>https://0andwild.com/posts/251017_comments_guide/</link><pubDate>Fri, 17 Oct 2025 11:00:00 +0900</pubDate><guid>https://0andwild.com/posts/251017_comments_guide/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Hugo &amp; GithubPages 블로그 댓글 시스템 구현 가이드" /&gt;&lt;h2 id="개요"&gt;&lt;a href="#%ea%b0%9c%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;개요
&lt;/h2&gt;&lt;p&gt;정적 사이트 생성기(Hugo)로 만든 블로그에 댓글 기능을 추가하는 모든 방법을 비교 분석합니다. &lt;strong&gt;익명 댓글&lt;/strong&gt;, &lt;strong&gt;GitHub 로그인&lt;/strong&gt;, &lt;strong&gt;소셜 로그인&lt;/strong&gt; 등 다양한 요구사항에 맞는 솔루션을 제시합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="댓글-시스템-분류"&gt;&lt;a href="#%eb%8c%93%ea%b8%80-%ec%8b%9c%ec%8a%a4%ed%85%9c-%eb%b6%84%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;댓글 시스템 분류
&lt;/h2&gt;&lt;h3 id="인증-방식에-따른-분류"&gt;&lt;a href="#%ec%9d%b8%ec%a6%9d-%eb%b0%a9%ec%8b%9d%ec%97%90-%eb%94%b0%eb%a5%b8-%eb%b6%84%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;인증 방식에 따른 분류
&lt;/h3&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;GitHub 전용&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Giscus, Utterances&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;익명 가능&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Remark42, Commento, Comentario, HashOver&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;익명 + 소셜 로그인&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Remark42, Commento, Disqus&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;소셜 로그인만&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Disqus, Hyvor Talk&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="호스팅-방식에-따른-분류"&gt;&lt;a href="#%ed%98%b8%ec%8a%a4%ed%8c%85-%eb%b0%a9%ec%8b%9d%ec%97%90-%eb%94%b0%eb%a5%b8-%eb%b6%84%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;호스팅 방식에 따른 분류
&lt;/h3&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;SaaS (관리 불필요)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Giscus, Utterances, Disqus, Hyvor Talk&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;셀프 호스팅&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Remark42, Commento, Comentario, HashOver&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;하이브리드&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Cusdis (Vercel 무료 배포)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="1-giscus-최고-추천---github-사용자용"&gt;&lt;a href="#1-giscus-%ec%b5%9c%ea%b3%a0-%ec%b6%94%ec%b2%9c---github-%ec%82%ac%ec%9a%a9%ec%9e%90%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;1. Giscus (최고 추천 - GitHub 사용자용)
&lt;/h2&gt;&lt;h3 id="개념"&gt;&lt;a href="#%ea%b0%9c%eb%85%90" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;GitHub Discussions를 백엔드로 사용하는 댓글 시스템&lt;/p&gt;
&lt;h3 id="동작-방식"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. 사용자가 블로그 방문
 ↓
2. Giscus 위젯 로드
 ↓
3. GitHub OAuth로 로그인
 ↓
4. 댓글 작성
 ↓
5. GitHub Discussions에 자동 저장
 ↓
6. 블로그에 실시간 표시
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;완전 무료&lt;/strong&gt; (GitHub 기능 활용)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;서버 불필요&lt;/strong&gt; (GitHub이 백엔드)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;데이터 소유&lt;/strong&gt; (본인 저장소에 저장)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Markdown 지원&lt;/strong&gt; (코드 블록, 이미지 등)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;반응(Reactions) 지원&lt;/strong&gt; (👍, ❤️ 등)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;알림&lt;/strong&gt; (GitHub 알림으로 댓글 알림)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;다크 모드&lt;/strong&gt; (블로그 테마와 동기화)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;스팸 방지&lt;/strong&gt; (GitHub 계정 필요)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;관리 간편&lt;/strong&gt; (GitHub Discussions에서 관리)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;검색 가능&lt;/strong&gt; (GitHub 검색으로 댓글 검색)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;익명 댓글 불가&lt;/strong&gt; (GitHub 계정 필수)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;기술 블로그에 적합&lt;/strong&gt; (일반 사용자는 GitHub 계정 없을 수 있음)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;GitHub 의존성&lt;/strong&gt; (GitHub 장애 시 댓글 불가)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐ (2/5)&lt;/p&gt;
&lt;h3 id="설정-방법"&gt;&lt;a href="#%ec%84%a4%ec%a0%95-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;설정 방법
&lt;/h3&gt;&lt;h4 id="1단계-github-discussions-활성화"&gt;&lt;a href="#1%eb%8b%a8%ea%b3%84-github-discussions-%ed%99%9c%ec%84%b1%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;1단계: GitHub Discussions 활성화
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. GitHub 저장소 → Settings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. Features 섹션 → Discussions 체크
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="2단계-giscus-설정"&gt;&lt;a href="#2%eb%8b%a8%ea%b3%84-giscus-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;2단계: Giscus 설정
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;&lt;a class="link" href="https://giscus.app/ko" target="_blank" rel="noopener"
 &gt;giscus.app&lt;/a&gt; 방문&lt;/li&gt;
&lt;li&gt;저장소 입력: &lt;code&gt;username/repository&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;설정 선택:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;페이지 ↔️ Discussion 연결&lt;/strong&gt;: &lt;code&gt;pathname&lt;/code&gt; (권장)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discussion 카테고리&lt;/strong&gt;: &lt;code&gt;Announcements&lt;/code&gt; 또는 &lt;code&gt;General&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;/li&gt;
&lt;/ol&gt;
&lt;h4 id="3단계-blowfish에-추가"&gt;&lt;a href="#3%eb%8b%a8%ea%b3%84-blowfish%ec%97%90-%ec%b6%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;3단계: Blowfish에 추가
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/partials/comments.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://giscus.app/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0AndWild/0AndWild.github.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;YOUR_REPO_ID&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Announcements&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;YOUR_CATEGORY_ID&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pathname&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-reactions-enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-emit-metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-input-position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bottom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;preferred_color_scheme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ko&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="4단계-paramstoml-설정"&gt;&lt;a href="#4%eb%8b%a8%ea%b3%84-paramstoml-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;4단계: params.toml 설정
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;showComments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="테마-동기화-다크모드"&gt;&lt;a href="#%ed%85%8c%eb%a7%88-%eb%8f%99%ea%b8%b0%ed%99%94-%eb%8b%a4%ed%81%ac%eb%aa%a8%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;테마 동기화 (다크모드)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 블로그 테마 변경 시 Giscus 테마도 변경
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;giscusTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;iframe.giscus-frame&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;giscusTheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data-theme&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;giscusTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;giscus&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dark&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://giscus.app&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/script&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="비용"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;비용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;완전 무료&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="추천-대상"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&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;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-utterances"&gt;&lt;a href="#2-utterances" class="header-anchor"&gt;&lt;/a&gt;2. Utterances
&lt;/h2&gt;&lt;h3 id="개념-1"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-1" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;GitHub Issues를 백엔드로 사용하는 댓글 시스템 (Giscus의 전신)&lt;/p&gt;
&lt;h3 id="동작-방식-1"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-1" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. GitHub OAuth 로그인
 ↓
2. 댓글 작성
 ↓
3. GitHub Issues에 저장 (각 포스트 = 1개 Issue)
 ↓
4. 블로그에 표시
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-1"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ 완전 무료&lt;/li&gt;
&lt;li&gt;✅ 가벼움 (TypeScript)&lt;/li&gt;
&lt;li&gt;✅ 간단한 설정&lt;/li&gt;
&lt;li&gt;✅ Markdown 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-1"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Issues 사용&lt;/strong&gt; (Discussions보다 덜 적합)&lt;/li&gt;
&lt;li&gt;❌ Giscus보다 기능 적음&lt;/li&gt;
&lt;li&gt;❌ 익명 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="giscus-vs-utterances"&gt;&lt;a href="#giscus-vs-utterances" class="header-anchor"&gt;&lt;/a&gt;Giscus vs Utterances
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;기능&lt;/th&gt;
 &lt;th&gt;Giscus&lt;/th&gt;
 &lt;th&gt;Utterances&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;백엔드&lt;/td&gt;
 &lt;td&gt;Discussions&lt;/td&gt;
 &lt;td&gt;Issues&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;✅ (nested)&lt;/td&gt;
 &lt;td&gt;⚠️ (flat)&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: Giscus가 Utterances의 상위 호환&lt;/p&gt;
&lt;h3 id="구현-난이도-1"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-1" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐ (2/5)&lt;/p&gt;
&lt;h3 id="설정-방법-1"&gt;&lt;a href="#%ec%84%a4%ec%a0%95-%eb%b0%a9%eb%b2%95-1" class="header-anchor"&gt;&lt;/a&gt;설정 방법
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/partials/comments.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://utteranc.es/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;username/repository&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;issue-term&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pathname&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github-light&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="추천-대상-1"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-1" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;특별한 이유가 없다면 &lt;strong&gt;Giscus 사용 권장&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3-remark42-최고-추천---익명--소셜-로그인"&gt;&lt;a href="#3-remark42-%ec%b5%9c%ea%b3%a0-%ec%b6%94%ec%b2%9c---%ec%9d%b5%eb%aa%85--%ec%86%8c%ec%85%9c-%eb%a1%9c%ea%b7%b8%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;3. Remark42 (최고 추천 - 익명 + 소셜 로그인)
&lt;/h2&gt;&lt;h3 id="개념-2"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-2" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;오픈소스 셀프 호스팅 댓글 시스템으로, 익명 및 다양한 소셜 로그인 지원&lt;/p&gt;
&lt;h3 id="동작-방식-2"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-2" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. Remark42 서버 배포 (Docker)
 ↓
2. 블로그에 Remark42 스크립트 삽입
 ↓
3. 사용자 선택:
 - 익명 댓글 작성
 - GitHub/Google/Twitter 로그인 후 작성
 ↓
4. Remark42 DB에 저장
 ↓
5. 블로그에 표시
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-2"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-2" class="header-anchor"&gt;&lt;/a&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; (GitHub, Google, Facebook, Twitter, Email)&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;li&gt;✅ &lt;strong&gt;Markdown 지원&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;li&gt;✅ &lt;strong&gt;알림&lt;/strong&gt; (이메일/Telegram)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Import/Export&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="단점-2"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-2" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;셀프 호스팅 필요&lt;/strong&gt; (Docker 서버)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;유지보수 책임&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;호스팅 비용&lt;/strong&gt; (월 $5~, 무료 티어 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-2"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-2" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐⭐⭐ (4/5)&lt;/p&gt;
&lt;h3 id="호스팅-옵션"&gt;&lt;a href="#%ed%98%b8%ec%8a%a4%ed%8c%85-%ec%98%b5%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;호스팅 옵션
&lt;/h3&gt;&lt;h4 id="옵션-1-railway-추천"&gt;&lt;a href="#%ec%98%b5%ec%85%98-1-railway-%ec%b6%94%ec%b2%9c" class="header-anchor"&gt;&lt;/a&gt;옵션 1: Railway (추천)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Railway.app 회원가입
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. &lt;span class="s2"&gt;&amp;#34;New Project&amp;#34;&lt;/span&gt; → &lt;span class="s2"&gt;&amp;#34;Deploy from GitHub&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Remark42 Docker 이미지 선택
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 환경변수 설정:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="nv"&gt;REMARK_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-remark42.railway.app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="nv"&gt;SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-random-secret
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="nv"&gt;AUTH_ANON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# 익명 댓글 허용&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="nv"&gt;AUTH_GITHUB_CID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_client_id
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="nv"&gt;AUTH_GITHUB_CSEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_client_secret
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Railway 무료 티어&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;월 $5 크레딧&lt;/li&gt;
&lt;li&gt;소규모 블로그 충분&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="옵션-2-flyio"&gt;&lt;a href="#%ec%98%b5%ec%85%98-2-flyio" class="header-anchor"&gt;&lt;/a&gt;옵션 2: Fly.io
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# fly.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-remark42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;build&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;umputun/remark42:latest&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;env&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;REMARK_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://my-remark42.fly.dev&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;AUTH_ANON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;AUTH_GITHUB_CID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;AUTH_GITHUB_CSEC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fly launch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fly deploy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Fly.io 무료 티어&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3개 앱&lt;/li&gt;
&lt;li&gt;소규모 블로그 충분&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="옵션-3-docker-compose-vps"&gt;&lt;a href="#%ec%98%b5%ec%85%98-3-docker-compose-vps" class="header-anchor"&gt;&lt;/a&gt;옵션 3: Docker Compose (VPS)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# docker-compose.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.8&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remark42&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;umputun/remark42:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;REMARK_URL=https://remark.your-blog.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SECRET=your-secret-key-change-this&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTH_ANON=true &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 익명 허용&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTH_GITHUB_CID=xxx &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# GitHub 로그인&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTH_GITHUB_CSEC=xxx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTH_GOOGLE_CID=xxx &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Google 로그인&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTH_GOOGLE_CSEC=xxx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ADMIN_SHARED_ID=github_username &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 관리자&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./data:/srv/var&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker-compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="블로그-삽입-코드"&gt;&lt;a href="#%eb%b8%94%eb%a1%9c%ea%b7%b8-%ec%82%bd%ec%9e%85-%ec%bd%94%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;블로그 삽입 코드
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/partials/comments.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;remark42&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;remark_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://your-remark42.railway.app&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;site_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0andwild-blog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;embed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ko&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;max_shown_comments&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;simple_view&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;no_footer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;script&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;remark_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/web/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nx"&gt;remark_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;embed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="익명--github-동시-사용-설정"&gt;&lt;a href="#%ec%9d%b5%eb%aa%85--github-%eb%8f%99%ec%8b%9c-%ec%82%ac%ec%9a%a9-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;익명 + GitHub 동시 사용 설정
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 환경변수&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_ANON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# 익명 허용&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_GITHUB_CID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxx &lt;span class="c1"&gt;# GitHub OAuth App ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_GITHUB_CSEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxx &lt;span class="c1"&gt;# GitHub OAuth App Secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ANON_VOTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# 익명 사용자 투표 불가 (스팸 방지)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;사용자는 선택 가능:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;익명으로 댓글 달기&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;GitHub으로 로그인&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="관리자-기능"&gt;&lt;a href="#%ea%b4%80%eb%a6%ac%ec%9e%90-%ea%b8%b0%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;관리자 기능
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 관리자 지정&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ADMIN_SHARED_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;github_yourusername
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 또는 이메일&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ADMIN_SHARED_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;you@example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/li&gt;
&lt;/ul&gt;
&lt;h3 id="비용-1"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9-1" class="header-anchor"&gt;&lt;/a&gt;비용
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Railway&lt;/strong&gt;: 무료 또는 월 $5&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fly.io&lt;/strong&gt;: 무료 티어 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VPS (DigitalOcean 등)&lt;/strong&gt;: 월 $5~&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="추천-대상-2"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-2" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;익명 + 소셜 로그인 모두 원하는 경우&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ 기술적으로 Docker 다룰 수 있는 사용자&lt;/li&gt;
&lt;li&gt;✅ 데이터 완전 통제 원하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="4-commento--comentario"&gt;&lt;a href="#4-commento--comentario" class="header-anchor"&gt;&lt;/a&gt;4. Commento / Comentario
&lt;/h2&gt;&lt;h3 id="개념-3"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-3" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;프라이버시 중심의 경량 댓글 시스템&lt;/p&gt;
&lt;h3 id="commento-vs-comentario"&gt;&lt;a href="#commento-vs-comentario" class="header-anchor"&gt;&lt;/a&gt;Commento vs Comentario
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;항목&lt;/th&gt;
 &lt;th&gt;Commento&lt;/th&gt;
 &lt;th&gt;Comentario&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;활발히 개발 중 (Commento 포크)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;라이선스&lt;/td&gt;
 &lt;td&gt;MIT&lt;/td&gt;
 &lt;td&gt;MIT&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;언어&lt;/td&gt;
 &lt;td&gt;Go&lt;/td&gt;
 &lt;td&gt;Go&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: Comentario 사용 권장&lt;/p&gt;
&lt;h3 id="comentario-장점"&gt;&lt;a href="#comentario-%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;Comentario 장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ 익명 댓글 가능&lt;/li&gt;
&lt;li&gt;✅ 소셜 로그인 (GitHub, Google, GitLab, SSO)&lt;/li&gt;
&lt;li&gt;✅ 가벼움 (Go 기반)&lt;/li&gt;
&lt;li&gt;✅ 프라이버시 중심&lt;/li&gt;
&lt;li&gt;✅ Markdown 지원&lt;/li&gt;
&lt;li&gt;✅ 투표 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-3"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-3" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ 셀프 호스팅 필요&lt;/li&gt;
&lt;li&gt;❌ Remark42보다 기능 적음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-3"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-3" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐⭐⭐ (4/5)&lt;/p&gt;
&lt;h3 id="docker-배포"&gt;&lt;a href="#docker-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;Docker 배포
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.8&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;comentario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;registry.gitlab.com/comentario/comentario&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;COMENTARIO_ORIGIN=https://comments.your-blog.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;COMENTARIO_BIND=0.0.0.0:8080&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;COMENTARIO_POSTGRES=postgres://user:pass@db/comentario&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres:15&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_DB=comentario&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_USER=comentario&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_PASSWORD=change-this&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="블로그-삽입"&gt;&lt;a href="#%eb%b8%94%eb%a1%9c%ea%b7%b8-%ec%82%bd%ec%9e%85" class="header-anchor"&gt;&lt;/a&gt;블로그 삽입
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://comments.your-blog.com/js/commento.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;commento&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="추천-대상-3"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-3" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Remark42 대안&lt;/li&gt;
&lt;li&gt;더 간단한 시스템 원하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="5-disqus-전통적-saas"&gt;&lt;a href="#5-disqus-%ec%a0%84%ed%86%b5%ec%a0%81-saas" class="header-anchor"&gt;&lt;/a&gt;5. Disqus (전통적 SaaS)
&lt;/h2&gt;&lt;h3 id="개념-4"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-4" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;가장 오래되고 널리 사용되는 클라우드 댓글 시스템&lt;/p&gt;
&lt;h3 id="동작-방식-3"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-3" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. Disqus 계정 생성 및 사이트 등록
 ↓
2. 블로그에 Disqus 스크립트 삽입
 ↓
3. 사용자 선택:
 - Guest (익명 - 이메일 필요)
 - Disqus 계정
 - Facebook/Twitter/Google 로그인
 ↓
4. Disqus 서버에 저장
 ↓
5. 블로그에 표시
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-3"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-3" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;설정 초간단&lt;/strong&gt; (5분)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;서버 불필요&lt;/strong&gt; (SaaS)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Guest 모드&lt;/strong&gt; (이메일만으로 댓글)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;소셜 로그인&lt;/strong&gt; (Facebook, Twitter, Google)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;강력한 관리자 도구&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;스팸 필터&lt;/strong&gt; (Akismet 통합)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;모바일 앱&lt;/strong&gt; (iOS/Android)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;분석/통계&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-4"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-4" class="header-anchor"&gt;&lt;/a&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;li&gt;❌ &lt;strong&gt;프라이버시 우려&lt;/strong&gt; (데이터 추적)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;데이터 소유권 없음&lt;/strong&gt; (Disqus 서버)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;GitHub 로그인 없음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;광고 제거 비용&lt;/strong&gt; (월 $11.99~)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-4"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-4" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐ (1/5) - 가장 쉬움&lt;/p&gt;
&lt;h3 id="설정-방법-2"&gt;&lt;a href="#%ec%84%a4%ec%a0%95-%eb%b0%a9%eb%b2%95-2" class="header-anchor"&gt;&lt;/a&gt;설정 방법
&lt;/h3&gt;&lt;h4 id="1단계-disqus-사이트-등록"&gt;&lt;a href="#1%eb%8b%a8%ea%b3%84-disqus-%ec%82%ac%ec%9d%b4%ed%8a%b8-%eb%93%b1%eb%a1%9d" class="header-anchor"&gt;&lt;/a&gt;1단계: Disqus 사이트 등록
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. disqus.com 가입
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. &lt;span class="s2"&gt;&amp;#34;I want to install Disqus on my site&amp;#34;&lt;/span&gt; 선택
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Website Name 입력 &lt;span class="o"&gt;(&lt;/span&gt;예: andwild-blog&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. Category 선택
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. Plan 선택 &lt;span class="o"&gt;(&lt;/span&gt;Basic - Free&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="2단계-blowfish-설정"&gt;&lt;a href="#2%eb%8b%a8%ea%b3%84-blowfish-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;2단계: Blowfish 설정
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# config/_default/config.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disqus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;shortname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;andwild-blog&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 1단계에서 생성한 이름&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# config/_default/params.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;showComments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hugo는 Disqus를 기본 지원하므로 자동으로 댓글 표시됨!&lt;/p&gt;
&lt;h4 id="3단계-guest-댓글-허용"&gt;&lt;a href="#3%eb%8b%a8%ea%b3%84-guest-%eb%8c%93%ea%b8%80-%ed%97%88%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;3단계: Guest 댓글 허용
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disqus Dashboard → Settings → Community
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ Guest Commenting: Allow guests to comment &lt;span class="o"&gt;(&lt;/span&gt;체크&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="광고-제거-방법"&gt;&lt;a href="#%ea%b4%91%ea%b3%a0-%ec%a0%9c%ea%b1%b0-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;광고 제거 방법
&lt;/h3&gt;&lt;h4 id="방법-1-유료-플랜-1199월"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-1-%ec%9c%a0%eb%a3%8c-%ed%94%8c%eb%9e%9c-1199%ec%9b%94" class="header-anchor"&gt;&lt;/a&gt;방법 1: 유료 플랜 ($11.99/월~)
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Plus Plan: 광고 없음&lt;/li&gt;
&lt;li&gt;Pro Plan: 광고 없음 + 고급 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="방법-2-css로-숨기기-비추천---약관-위반-가능"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-2-css%eb%a1%9c-%ec%88%a8%ea%b8%b0%ea%b8%b0-%eb%b9%84%ec%b6%94%ec%b2%9c---%ec%95%bd%ea%b4%80-%ec%9c%84%eb%b0%98-%ea%b0%80%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;방법 2: CSS로 숨기기 (비추천 - 약관 위반 가능)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;/* 비추천: Disqus 약관 위반 가능 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;disqus_thread&lt;/span&gt; &lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ads&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="비용-2"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9-2" class="header-anchor"&gt;&lt;/a&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;Plus&lt;/strong&gt;: $11.99/월 (광고 없음)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pro&lt;/strong&gt;: $89/월 (고급 기능)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="추천-대상-4"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-4" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&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;hr&gt;
&lt;h2 id="6-cusdis-vercel-무료-배포"&gt;&lt;a href="#6-cusdis-vercel-%eb%ac%b4%eb%a3%8c-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;6. Cusdis (Vercel 무료 배포)
&lt;/h2&gt;&lt;h3 id="개념-5"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-5" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;경량 오픈소스 댓글 시스템, Vercel에 무료 배포 가능&lt;/p&gt;
&lt;h3 id="동작-방식-4"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-4" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. Cusdis를 Vercel에 배포 (1-Click)
 ↓
2. PostgreSQL 연결 (Vercel 무료)
 ↓
3. 대시보드에서 사이트 추가
 ↓
4. 블로그에 스크립트 삽입
 ↓
5. 사용자가 이메일 + 이름으로 댓글
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-4"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-4" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;완전 무료&lt;/strong&gt; (Vercel 무료 티어)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;익명 댓글&lt;/strong&gt; (이메일 + 이름만)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;가벼움&lt;/strong&gt; (50KB)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;설정 간단&lt;/strong&gt; (Vercel 1-Click 배포)&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="단점-5"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-5" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ Markdown 미지원&lt;/li&gt;
&lt;li&gt;❌ 소셜 로그인 없음&lt;/li&gt;
&lt;li&gt;❌ 기능 단순&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-5"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-5" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐⭐ (3/5)&lt;/p&gt;
&lt;h3 id="설정-방법-3"&gt;&lt;a href="#%ec%84%a4%ec%a0%95-%eb%b0%a9%eb%b2%95-3" class="header-anchor"&gt;&lt;/a&gt;설정 방법
&lt;/h3&gt;&lt;h4 id="1단계-vercel-배포"&gt;&lt;a href="#1%eb%8b%a8%ea%b3%84-vercel-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;1단계: Vercel 배포
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. https://cusdis.com/ 방문
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. &lt;span class="s2"&gt;&amp;#34;Deploy with Vercel&amp;#34;&lt;/span&gt; 클릭
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. GitHub 연결
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. PostgreSQL 추가 &lt;span class="o"&gt;(&lt;/span&gt;Vercel Storage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. 배포 완료
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="2단계-사이트-추가"&gt;&lt;a href="#2%eb%8b%a8%ea%b3%84-%ec%82%ac%ec%9d%b4%ed%8a%b8-%ec%b6%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;2단계: 사이트 추가
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 배포된 Cusdis 대시보드 접속
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. &lt;span class="s2"&gt;&amp;#34;Add Website&amp;#34;&lt;/span&gt; 클릭
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Domain 입력: 0andwild.github.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. App ID 복사
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="3단계-블로그-삽입"&gt;&lt;a href="#3%eb%8b%a8%ea%b3%84-%eb%b8%94%eb%a1%9c%ea%b7%b8-%ec%82%bd%ec%9e%85" class="header-anchor"&gt;&lt;/a&gt;3단계: 블로그 삽입
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/partials/comments.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;cusdis_thread&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://your-cusdis.vercel.app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-app-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;YOUR_APP_ID&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-page-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ .File.UniqueID }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-page-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ .Permalink }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-page-title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ .Title }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://your-cusdis.vercel.app/js/cusdis.es.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="비용-3"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9-3" class="header-anchor"&gt;&lt;/a&gt;비용
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;완전 무료&lt;/strong&gt; (Vercel 무료 티어)&lt;/p&gt;
&lt;h3 id="추천-대상-5"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-5" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ 간단한 익명 댓글만 필요한 경우&lt;/li&gt;
&lt;li&gt;✅ 완전 무료 원하는 경우&lt;/li&gt;
&lt;li&gt;✅ Vercel 사용 경험 있는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="7-hashover"&gt;&lt;a href="#7-hashover" class="header-anchor"&gt;&lt;/a&gt;7. HashOver
&lt;/h2&gt;&lt;h3 id="개념-6"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-6" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;PHP 기반의 완전 익명 댓글 시스템&lt;/p&gt;
&lt;h3 id="장점-5"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-5" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ 완전 익명 (아무 정보도 필요 없음)&lt;/li&gt;
&lt;li&gt;✅ PHP + flat file (DB 불필요)&lt;/li&gt;
&lt;li&gt;✅ 오픈소스&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-6"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-6" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ PHP 필요 (정적 사이트에 부적합)&lt;/li&gt;
&lt;li&gt;❌ GitHub 로그인 없음&lt;/li&gt;
&lt;li&gt;❌ 오래된 프로젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-6"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-6" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐⭐⭐ (4/5)&lt;/p&gt;
&lt;h3 id="추천-대상-6"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-6" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;정적 블로그에는 비추천&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;PHP 서버 있을 때만 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="8-hyvor-talk-프리미엄-saas"&gt;&lt;a href="#8-hyvor-talk-%ed%94%84%eb%a6%ac%eb%af%b8%ec%97%84-saas" class="header-anchor"&gt;&lt;/a&gt;8. Hyvor Talk (프리미엄 SaaS)
&lt;/h2&gt;&lt;h3 id="개념-7"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-7" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;광고 없는 프리미엄 댓글 시스템&lt;/p&gt;
&lt;h3 id="장점-6"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-6" class="header-anchor"&gt;&lt;/a&gt;장점
&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;h3 id="단점-7"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-7" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;유료&lt;/strong&gt; (월 $5~)&lt;/li&gt;
&lt;li&gt;❌ GitHub 로그인 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="비용-4"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9-4" class="header-anchor"&gt;&lt;/a&gt;비용
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Starter&lt;/strong&gt;: $5/월 (1 사이트)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pro&lt;/strong&gt;: $15/월 (3 사이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="추천-대상-7"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%8c%80%ec%83%81-7" class="header-anchor"&gt;&lt;/a&gt;추천 대상
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Disqus 유료 대안&lt;/li&gt;
&lt;li&gt;광고 없는 SaaS 원하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="비교표"&gt;&lt;a href="#%eb%b9%84%ea%b5%90%ed%91%9c" class="header-anchor"&gt;&lt;/a&gt;비교표
&lt;/h2&gt;&lt;h3 id="인증-방식별"&gt;&lt;a href="#%ec%9d%b8%ec%a6%9d-%eb%b0%a9%ec%8b%9d%eb%b3%84" class="header-anchor"&gt;&lt;/a&gt;인증 방식별
&lt;/h3&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;GitHub&lt;/th&gt;
 &lt;th&gt;Google&lt;/th&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;strong&gt;Giscus&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&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;Utterances&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&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;strong&gt;Remark42&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
 &lt;td&gt;$5/월&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Comentario&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
 &lt;td&gt;$5/월&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Disqus&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;⚠️&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&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;Cusdis&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&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;Hyvor Talk&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⭐&lt;/td&gt;
 &lt;td&gt;$5/월&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="기능별"&gt;&lt;a href="#%ea%b8%b0%eb%8a%a5%eb%b3%84" class="header-anchor"&gt;&lt;/a&gt;기능별
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;시스템&lt;/th&gt;
 &lt;th&gt;Markdown&lt;/th&gt;
 &lt;th&gt;반응&lt;/th&gt;
 &lt;th&gt;투표&lt;/th&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;Giscus&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&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;Remark42&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&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;Disqus&lt;/td&gt;
 &lt;td&gt;⚠️&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&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;Cusdis&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;⚠️&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⚠️&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="호스팅별"&gt;&lt;a href="#%ed%98%b8%ec%8a%a4%ed%8c%85%eb%b3%84" class="header-anchor"&gt;&lt;/a&gt;호스팅별
&lt;/h3&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;th&gt;의존성&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Giscus&lt;/td&gt;
 &lt;td&gt;GitHub&lt;/td&gt;
 &lt;td&gt;GitHub Discussions&lt;/td&gt;
 &lt;td&gt;GitHub&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Remark42&lt;/td&gt;
 &lt;td&gt;셀프&lt;/td&gt;
 &lt;td&gt;본인 서버&lt;/td&gt;
 &lt;td&gt;Docker&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Disqus&lt;/td&gt;
 &lt;td&gt;Disqus&lt;/td&gt;
 &lt;td&gt;Disqus 서버&lt;/td&gt;
 &lt;td&gt;Disqus&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cusdis&lt;/td&gt;
 &lt;td&gt;Vercel&lt;/td&gt;
 &lt;td&gt;Vercel DB&lt;/td&gt;
 &lt;td&gt;Vercel&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="선택-가이드"&gt;&lt;a href="#%ec%84%a0%ed%83%9d-%ea%b0%80%ec%9d%b4%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;선택 가이드
&lt;/h2&gt;&lt;h3 id="시나리오별-추천"&gt;&lt;a href="#%ec%8b%9c%eb%82%98%eb%a6%ac%ec%98%a4%eb%b3%84-%ec%b6%94%ec%b2%9c" class="header-anchor"&gt;&lt;/a&gt;시나리오별 추천
&lt;/h3&gt;&lt;h4 id="1-개발자-블로그-github-사용자-대상"&gt;&lt;a href="#1-%ea%b0%9c%eb%b0%9c%ec%9e%90-%eb%b8%94%eb%a1%9c%ea%b7%b8-github-%ec%82%ac%ec%9a%a9%ec%9e%90-%eb%8c%80%ec%83%81" class="header-anchor"&gt;&lt;/a&gt;1. &amp;ldquo;개발자 블로그, GitHub 사용자 대상&amp;rdquo;
&lt;/h4&gt;&lt;p&gt;→ &lt;strong&gt;Giscus&lt;/strong&gt; ⭐⭐⭐⭐⭐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;무료, 간단, Markdown 지원&lt;/li&gt;
&lt;li&gt;GitHub 통합으로 알림도 편함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-일반-블로그-익명-댓글-필수"&gt;&lt;a href="#2-%ec%9d%bc%eb%b0%98-%eb%b8%94%eb%a1%9c%ea%b7%b8-%ec%9d%b5%eb%aa%85-%eb%8c%93%ea%b8%80-%ed%95%84%ec%88%98" class="header-anchor"&gt;&lt;/a&gt;2. &amp;ldquo;일반 블로그, 익명 댓글 필수&amp;rdquo;
&lt;/h4&gt;&lt;p&gt;→ &lt;strong&gt;Cusdis&lt;/strong&gt; (간단) 또는 &lt;strong&gt;Remark42&lt;/strong&gt; (고급)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cusdis: 5분 설정, 완전 무료&lt;/li&gt;
&lt;li&gt;Remark42: 더 많은 기능, 소셜 로그인 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-익명--github-로그인-둘-다"&gt;&lt;a href="#3-%ec%9d%b5%eb%aa%85--github-%eb%a1%9c%ea%b7%b8%ec%9d%b8-%eb%91%98-%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;3. &amp;ldquo;익명 + GitHub 로그인 둘 다&amp;rdquo;
&lt;/h4&gt;&lt;p&gt;→ &lt;strong&gt;Remark42&lt;/strong&gt; ⭐⭐⭐⭐⭐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;유일하게 둘 다 지원&lt;/li&gt;
&lt;li&gt;관리자 기능 강력&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4-기술-없음-빠르게-설정"&gt;&lt;a href="#4-%ea%b8%b0%ec%88%a0-%ec%97%86%ec%9d%8c-%eb%b9%a0%eb%a5%b4%ea%b2%8c-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;4. &amp;ldquo;기술 없음, 빠르게 설정&amp;rdquo;
&lt;/h4&gt;&lt;p&gt;→ &lt;strong&gt;Disqus&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5분 설정&lt;/li&gt;
&lt;li&gt;광고는 감수&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="5-완전-무료--서버-관리-싫음"&gt;&lt;a href="#5-%ec%99%84%ec%a0%84-%eb%ac%b4%eb%a3%8c--%ec%84%9c%eb%b2%84-%ea%b4%80%eb%a6%ac-%ec%8b%ab%ec%9d%8c" class="header-anchor"&gt;&lt;/a&gt;5. &amp;ldquo;완전 무료 + 서버 관리 싫음&amp;rdquo;
&lt;/h4&gt;&lt;p&gt;→ &lt;strong&gt;Giscus&lt;/strong&gt; (GitHub) 또는 &lt;strong&gt;Cusdis&lt;/strong&gt; (익명)&lt;/p&gt;
&lt;h4 id="6-프라이버시-최우선"&gt;&lt;a href="#6-%ed%94%84%eb%9d%bc%ec%9d%b4%eb%b2%84%ec%8b%9c-%ec%b5%9c%ec%9a%b0%ec%84%a0" class="header-anchor"&gt;&lt;/a&gt;6. &amp;ldquo;프라이버시 최우선&amp;rdquo;
&lt;/h4&gt;&lt;p&gt;→ &lt;strong&gt;Remark42&lt;/strong&gt; 또는 &lt;strong&gt;Comentario&lt;/strong&gt; (셀프 호스팅)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터 완전 통제&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="실전-구현-blowfish--giscus"&gt;&lt;a href="#%ec%8b%a4%ec%a0%84-%ea%b5%ac%ed%98%84-blowfish--giscus" class="header-anchor"&gt;&lt;/a&gt;실전 구현: Blowfish + Giscus
&lt;/h2&gt;&lt;h3 id="전체-설정-과정"&gt;&lt;a href="#%ec%a0%84%ec%b2%b4-%ec%84%a4%ec%a0%95-%ea%b3%bc%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;전체 설정 과정
&lt;/h3&gt;&lt;h4 id="1-github-discussions-활성화"&gt;&lt;a href="#1-github-discussions-%ed%99%9c%ec%84%b1%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;1. GitHub Discussions 활성화
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GitHub 저장소 → Settings → Features → Discussions 체크
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="2-giscus-app-설치"&gt;&lt;a href="#2-giscus-app-%ec%84%a4%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;2. Giscus App 설치
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;https://github.com/apps/giscus 방문 → Install
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ 저장소 선택
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="3-giscus-설정-생성"&gt;&lt;a href="#3-giscus-%ec%84%a4%ec%a0%95-%ec%83%9d%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;3. Giscus 설정 생성
&lt;/h4&gt;&lt;p&gt;&lt;a class="link" href="https://giscus.app/ko" target="_blank" rel="noopener"
 &gt;giscus.app/ko&lt;/a&gt;에서:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;저장소: 0AndWild/0AndWild.github.io
매핑: pathname
카테고리: Announcements
테마: preferred_color_scheme
언어: ko
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;생성된 코드 복사&lt;/p&gt;
&lt;h4 id="4-파일-생성"&gt;&lt;a href="#4-%ed%8c%8c%ec%9d%bc-%ec%83%9d%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;4. 파일 생성
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 디렉토리 생성 (없으면)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p layouts/partials
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 파일 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;touch layouts/partials/comments.html
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="5-코드-삽입"&gt;&lt;a href="#5-%ec%bd%94%eb%93%9c-%ec%82%bd%ec%9e%85" class="header-anchor"&gt;&lt;/a&gt;5. 코드 삽입
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/partials/comments.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://giscus.app/client.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0AndWild/0AndWild.github.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-repo-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;R_xxxxxxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Announcements&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-category-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DIC_xxxxxxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pathname&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-reactions-enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-emit-metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-input-position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bottom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;preferred_color_scheme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ko&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="6-paramstoml-수정"&gt;&lt;a href="#6-paramstoml-%ec%88%98%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;6. params.toml 수정
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;showComments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="7-로컬-테스트"&gt;&lt;a href="#7-%eb%a1%9c%ec%bb%ac-%ed%85%8c%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;7. 로컬 테스트
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server -D
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# http://localhost:1313 에서 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="8-배포"&gt;&lt;a href="#8-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;8. 배포
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Add Giscus comments&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="실전-구현-blowfish--remark42-railway"&gt;&lt;a href="#%ec%8b%a4%ec%a0%84-%ea%b5%ac%ed%98%84-blowfish--remark42-railway" class="header-anchor"&gt;&lt;/a&gt;실전 구현: Blowfish + Remark42 (Railway)
&lt;/h2&gt;&lt;h3 id="전체-설정-과정-1"&gt;&lt;a href="#%ec%a0%84%ec%b2%b4-%ec%84%a4%ec%a0%95-%ea%b3%bc%ec%a0%95-1" class="header-anchor"&gt;&lt;/a&gt;전체 설정 과정
&lt;/h3&gt;&lt;h4 id="1-github-oauth-app-생성"&gt;&lt;a href="#1-github-oauth-app-%ec%83%9d%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;1. GitHub OAuth App 생성
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Application name: AndWild Blog Comments
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Homepage URL: https://0andwild.github.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Authorization callback URL: https://your-remark42.railway.app/auth/github/callback
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;생성 후:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client ID 복사
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client Secret 생성 및 복사
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="2-railway-배포"&gt;&lt;a href="#2-railway-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;2. Railway 배포
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. railway.app 가입
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. &lt;span class="s2"&gt;&amp;#34;New Project&amp;#34;&lt;/span&gt; → &lt;span class="s2"&gt;&amp;#34;Deploy Docker Image&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Image: umputun/remark42:latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 환경변수 추가:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;REMARK_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-project.railway.app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;randomly-generated-secret-key-change-this
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SITE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0andwild-blog
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_ANON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_GITHUB_CID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_github_client_id
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_GITHUB_CSEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_github_client_secret
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ADMIN_SHARED_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;github_yourusername
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="3-배포-확인"&gt;&lt;a href="#3-%eb%b0%b0%ed%8f%ac-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;3. 배포 확인
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Railway가 자동으로 URL 생성:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;https://your-project.railway.app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;브라우저에서 접속하여 Remark42 UI 확인
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="4-blowfish-설정"&gt;&lt;a href="#4-blowfish-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;4. Blowfish 설정
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p layouts/partials
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;touch layouts/partials/comments.html
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/partials/comments.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;remark42&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;remark_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://your-project.railway.app&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;site_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0andwild-blog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;embed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;light&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ko&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;script&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;remark_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/web/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nx"&gt;remark_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;embed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="5-paramstoml"&gt;&lt;a href="#5-paramstoml" class="header-anchor"&gt;&lt;/a&gt;5. params.toml
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;showComments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="6-테스트-및-배포"&gt;&lt;a href="#6-%ed%85%8c%ec%8a%a4%ed%8a%b8-%eb%b0%8f-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;6. 테스트 및 배포
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server -D
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 확인 후&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Add Remark42 comments&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="마이그레이션-가이드"&gt;&lt;a href="#%eb%a7%88%ec%9d%b4%ea%b7%b8%eb%a0%88%ec%9d%b4%ec%85%98-%ea%b0%80%ec%9d%b4%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;마이그레이션 가이드
&lt;/h2&gt;&lt;h3 id="disqus--giscus"&gt;&lt;a href="#disqus--giscus" class="header-anchor"&gt;&lt;/a&gt;Disqus → Giscus
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Disqus에서 데이터 Export &lt;span class="o"&gt;(&lt;/span&gt;XML&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. GitHub Discussions로 수동 이전
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;자동화 스크립트 없음, 수동 작업 필요&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="disqus--remark42"&gt;&lt;a href="#disqus--remark42" class="header-anchor"&gt;&lt;/a&gt;Disqus → Remark42
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Disqus XML Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. Remark42 Admin → Import → Disqus 선택
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. XML 파일 업로드
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="결론"&gt;&lt;a href="#%ea%b2%b0%eb%a1%a0" class="header-anchor"&gt;&lt;/a&gt;결론
&lt;/h2&gt;&lt;h3 id="최종-추천"&gt;&lt;a href="#%ec%b5%9c%ec%a2%85-%ec%b6%94%ec%b2%9c" class="header-anchor"&gt;&lt;/a&gt;최종 추천
&lt;/h3&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;strong&gt;개발자 블로그&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Giscus&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;무료, GitHub 통합, Markdown&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;일반 블로그 (익명 필요)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Cusdis&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;무료, 간단, 익명&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;익명 + 소셜 둘 다&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Remark42&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;유연함, 모든 기능&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;빠른 설정&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Disqus&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;5분 완료 (광고 감수)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;완전 통제&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Remark42&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;셀프 호스팅, 커스터마이징&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="개인-추천-0andwild-블로그"&gt;&lt;a href="#%ea%b0%9c%ec%9d%b8-%ec%b6%94%ec%b2%9c-0andwild-%eb%b8%94%eb%a1%9c%ea%b7%b8" class="header-anchor"&gt;&lt;/a&gt;개인 추천 (0AndWild 블로그)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Giscus&lt;/strong&gt; 사용 권장&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Pages 블로그에 완벽히 어울림&lt;/li&gt;
&lt;li&gt;기술 블로그는 GitHub 사용자가 주 독자&lt;/li&gt;
&lt;li&gt;무료, 간단, 유지보수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;대안&lt;/strong&gt;: Remark42 (익명 댓글 원할 때)&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="빠른시작"&gt;&lt;a href="#%eb%b9%a0%eb%a5%b8%ec%8b%9c%ec%9e%91" class="header-anchor"&gt;&lt;/a&gt;빠른시작
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Giscus로 시작&lt;/strong&gt; (10분)&lt;/li&gt;
&lt;li&gt;사용자 피드백 수집&lt;/li&gt;
&lt;li&gt;익명 댓글 요청 많으면 &lt;strong&gt;Remark42로 전환&lt;/strong&gt; 고려&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;댓글 시스템은 나중에도 바꿀 수 있으니, 일단 Giscus로 시작하는 것을 강력히 권장합니다!&lt;/p&gt;</description></item><item><title>Hugo &amp; GithubPages 블로그 구독 및 이메일 알림 시스템 구현 가이드</title><link>https://0andwild.com/posts/251017_subscription_alert/</link><pubDate>Fri, 17 Oct 2025 10:00:00 +0900</pubDate><guid>https://0andwild.com/posts/251017_subscription_alert/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Hugo &amp; GithubPages 블로그 구독 및 이메일 알림 시스템 구현 가이드" /&gt;&lt;h2 id="개요"&gt;&lt;a href="#%ea%b0%9c%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;개요
&lt;/h2&gt;&lt;p&gt;정적 사이트 생성기(Hugo)로 만든 블로그에 구독 및 이메일 알림 기능을 추가하는 방법을 분석합니다. 특히 &lt;strong&gt;키워드 기반 선택적 알림&lt;/strong&gt; 기능 구현까지 다룹니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-rss-feed--이메일-서비스"&gt;&lt;a href="#1-rss-feed--%ec%9d%b4%eb%a9%94%ec%9d%bc-%ec%84%9c%eb%b9%84%ec%8a%a4" class="header-anchor"&gt;&lt;/a&gt;1. RSS Feed + 이메일 서비스
&lt;/h2&gt;&lt;h3 id="개념"&gt;&lt;a href="#%ea%b0%9c%eb%85%90" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;Hugo의 기본 RSS Feed를 이메일로 변환하는 서비스를 활용하는 방식입니다.&lt;/p&gt;
&lt;h3 id="방법-a-blogtrottr"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-a-blogtrottr" class="header-anchor"&gt;&lt;/a&gt;방법 A: Blogtrottr
&lt;/h3&gt;&lt;h4 id="동작-방식"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. Hugo가 자동 생성한 RSS Feed (index.xml)
 ↓
2. 사용자가 Blogtrottr에 RSS URL 등록
 ↓
3. Blogtrottr가 주기적으로 RSS 확인
 ↓
4. 새 글 감지 시 이메일 발송
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="장점"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&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;✅ 서버 없이 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="단점"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&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;❌ 키워드 필터링 불가&lt;/li&gt;
&lt;li&gt;❌ 사용자가 직접 외부 사이트에서 등록해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="구현-난이도"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h4&gt;&lt;p&gt;⭐ (1/5) - 가장 쉬움&lt;/p&gt;
&lt;h4 id="사용-예시"&gt;&lt;a href="#%ec%82%ac%ec%9a%a9-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;사용 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;블로그에 링크 추가:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[&lt;span class="nt"&gt;이메일로 구독하기&lt;/span&gt;](&lt;span class="na"&gt;https://blogtrottr.com&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(사이트에서 https://0andwild.github.io/index.xml 입력)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="방법-b-feedburner-google"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-b-feedburner-google" class="header-anchor"&gt;&lt;/a&gt;방법 B: FeedBurner (Google)
&lt;/h3&gt;&lt;h4 id="동작-방식-1"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-1" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. FeedBurner에 RSS Feed 등록
 ↓
2. FeedBurner가 RSS를 프록시/관리
 ↓
3. 구독 폼을 블로그에 삽입
 ↓
4. 사용자가 블로그에서 직접 구독
 ↓
5. 새 글 발행 시 자동 이메일 발송
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="장점-1"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&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;✅ RSS 관리 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="단점-1"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;❌ Google의 지원 중단 가능성 (업데이트 중단됨)&lt;/li&gt;
&lt;li&gt;❌ 키워드 필터링 불가&lt;/li&gt;
&lt;li&gt;❌ 커스텀 제한적&lt;/li&gt;
&lt;li&gt;❌ 오래된 UI&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="구현-난이도-1"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-1" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h4&gt;&lt;p&gt;⭐⭐ (2/5)&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2-mailchimp--rss-campaign-추천"&gt;&lt;a href="#2-mailchimp--rss-campaign-%ec%b6%94%ec%b2%9c" class="header-anchor"&gt;&lt;/a&gt;2. Mailchimp + RSS Campaign (추천)
&lt;/h2&gt;&lt;h3 id="개념-1"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-1" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;전문 이메일 마케팅 플랫폼을 활용하여 RSS Feed를 자동으로 이메일로 변환&lt;/p&gt;
&lt;h3 id="동작-방식-2"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-2" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. Mailchimp에 RSS Campaign 생성
 ↓
2. RSS URL 등록 및 체크 주기 설정 (일/주/월)
 ↓
3. 블로그에 Mailchimp 구독 폼 삽입
 ↓
4. 사용자가 이메일 입력하여 구독
 ↓
5. 새 글 감지 시 자동으로 이메일 템플릿 생성
 ↓
6. 전체 구독자에게 발송
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-2"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-2" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;무료 티어&lt;/strong&gt;: 2,000명 구독자까지&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;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;li&gt;✅ &lt;strong&gt;스팸 필터 회피&lt;/strong&gt; (전문 발송 서버)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-2"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-2" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ 키워드 필터링 기본 미지원 (Pro 플랜에서 태그별 세그먼트 가능)&lt;/li&gt;
&lt;li&gt;❌ 무료 티어에서 Mailchimp 로고 표시&lt;/li&gt;
&lt;li&gt;❌ 2,000명 초과 시 유료 ($13/월~)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-2"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-2" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐ (2/5)&lt;/p&gt;
&lt;h3 id="설정-단계"&gt;&lt;a href="#%ec%84%a4%ec%a0%95-%eb%8b%a8%ea%b3%84" class="header-anchor"&gt;&lt;/a&gt;설정 단계
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. Mailchimp 계정 생성
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. Audience 생성
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Campaign → Create → Email → RSS Campaign
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. RSS URL 입력: https://your-blog.com/index.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. 발송 주기 설정 &lt;span class="o"&gt;(&lt;/span&gt;Daily/Weekly&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6. 이메일 템플릿 디자인
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;7. 구독 폼 코드 복사
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;8. Hugo에 삽입 &lt;span class="o"&gt;(&lt;/span&gt;layouts/partials/subscribe.html&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="블로그-삽입-코드-예시"&gt;&lt;a href="#%eb%b8%94%eb%a1%9c%ea%b7%b8-%ec%82%bd%ec%9e%85-%ec%bd%94%eb%93%9c-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;블로그 삽입 코드 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- Mailchimp 구독 폼 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mc_embed_signup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://your-mailchimp-url.com/subscribe&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;EMAIL&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;이메일 주소&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;구독하기&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="3-buttondown-개발자-친화적-추천"&gt;&lt;a href="#3-buttondown-%ea%b0%9c%eb%b0%9c%ec%9e%90-%ec%b9%9c%ed%99%94%ec%a0%81-%ec%b6%94%ec%b2%9c" class="header-anchor"&gt;&lt;/a&gt;3. Buttondown (개발자 친화적, 추천)
&lt;/h2&gt;&lt;h3 id="개념-2"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-2" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;Markdown 기반의 뉴스레터 플랫폼으로, API를 통한 커스터마이징이 가능&lt;/p&gt;
&lt;h3 id="동작-방식-3"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-3" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. Buttondown에 RSS Feed 연동
 ↓
2. 자동으로 RSS 항목을 Markdown 이메일로 변환
 ↓
3. 구독자가 태그/키워드 선택 가능
 ↓
4. API를 통해 특정 태그 구독자만 필터링 가능
 ↓
5. 매칭되는 구독자에게만 발송
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-3"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-3" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;무료 티어&lt;/strong&gt;: 1,000명까지&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Markdown 기반&lt;/strong&gt; (개발자 친화적)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;강력한 API&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;li&gt;✅ &lt;strong&gt;깔끔한 UI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;RSS import 자동화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;프라이버시 중심&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-3"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-3" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ 이메일 디자인이 단순 (Markdown만)&lt;/li&gt;
&lt;li&gt;❌ 통계 기능이 Mailchimp보다 약함&lt;/li&gt;
&lt;li&gt;❌ 한국어 지원 부족&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-3"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-3" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐⭐ (3/5) - API 사용 시 난이도 증가&lt;/p&gt;
&lt;h3 id="키워드-알림-구현-예시"&gt;&lt;a href="#%ed%82%a4%ec%9b%8c%eb%93%9c-%ec%95%8c%eb%a6%bc-%ea%b5%ac%ed%98%84-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;키워드 알림 구현 예시
&lt;/h3&gt;&lt;h4 id="1단계-구독-폼에-태그-선택-추가"&gt;&lt;a href="#1%eb%8b%a8%ea%b3%84-%ea%b5%ac%eb%8f%85-%ed%8f%bc%ec%97%90-%ed%83%9c%ea%b7%b8-%ec%84%a0%ed%83%9d-%ec%b6%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;1단계: 구독 폼에 태그 선택 추가
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://buttondown.email/api/emails/embed-subscribe/YOUR_ID&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;이메일&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;관심 주제 선택:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kubernetes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;docker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Docker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;golang&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;구독하기&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="2단계-github-actions로-선택적-발송"&gt;&lt;a href="#2%eb%8b%a8%ea%b3%84-github-actions%eb%a1%9c-%ec%84%a0%ed%83%9d%ec%a0%81-%eb%b0%9c%ec%86%a1" class="header-anchor"&gt;&lt;/a&gt;2단계: GitHub Actions로 선택적 발송
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Send Newsletter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;content/posts/**&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;send&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Extract tags from post&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; TAGS=$(grep &amp;#34;^tags = &amp;#34; content/posts/*/index.md | cut -d&amp;#39;&amp;#34;&amp;#39; -f2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;POST_TAGS=$TAGS&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Send to matching subscribers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -X POST https://api.buttondown.email/v1/emails \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;Authorization: Token ${{ secrets.BUTTONDOWN_API_KEY }}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#34;subject=New Post&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#34;body=...&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#34;tag=$POST_TAGS&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="4-sendgrid--github-actions-완전-커스텀"&gt;&lt;a href="#4-sendgrid--github-actions-%ec%99%84%ec%a0%84-%ec%bb%a4%ec%8a%a4%ed%85%80" class="header-anchor"&gt;&lt;/a&gt;4. SendGrid + GitHub Actions (완전 커스텀)
&lt;/h2&gt;&lt;h3 id="개념-3"&gt;&lt;a href="#%ea%b0%9c%eb%85%90-3" class="header-anchor"&gt;&lt;/a&gt;개념
&lt;/h3&gt;&lt;p&gt;이메일 발송 API와 CI/CD를 결합하여 완전히 커스터마이징된 알림 시스템 구축&lt;/p&gt;
&lt;h3 id="동작-방식-4"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d-4" class="header-anchor"&gt;&lt;/a&gt;동작 방식
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;1. 새 글 작성 후 Git Push
 ↓
2. GitHub Actions 트리거
 ↓
3. Action에서 Front Matter 파싱
 - 글 제목, 요약, 태그 추출
 ↓
4. 구독자 DB 조회 (Supabase/JSON 파일)
 - 각 구독자의 관심 키워드와 매칭
 ↓
5. 매칭되는 구독자만 필터링
 ↓
6. SendGrid API로 개별 이메일 발송
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="장점-4"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-4" class="header-anchor"&gt;&lt;/a&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;li&gt;✅ &lt;strong&gt;무료 티어&lt;/strong&gt;: SendGrid 월 100통&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;자동화&lt;/strong&gt; (Git push만 하면 됨)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;확장 가능&lt;/strong&gt; (DB, 로직 자유롭게)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;구독자 데이터 소유&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점-4"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-4" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;❌ 개발 작업 필요&lt;/li&gt;
&lt;li&gt;❌ 유지보수 부담&lt;/li&gt;
&lt;li&gt;❌ SendGrid 무료 티어 제한적 (월 100통)&lt;/li&gt;
&lt;li&gt;❌ 구독 폼, DB 직접 구현 필요&lt;/li&gt;
&lt;li&gt;❌ 스팸 필터 회피 설정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="구현-난이도-4"&gt;&lt;a href="#%ea%b5%ac%ed%98%84-%eb%82%9c%ec%9d%b4%eb%8f%84-4" class="header-anchor"&gt;&lt;/a&gt;구현 난이도
&lt;/h3&gt;&lt;p&gt;⭐⭐⭐⭐⭐ (5/5) - 가장 복잡&lt;/p&gt;
&lt;h3 id="아키텍처"&gt;&lt;a href="#%ec%95%84%ed%82%a4%ed%85%8d%ec%b2%98" class="header-anchor"&gt;&lt;/a&gt;아키텍처
&lt;/h3&gt;&lt;h4 id="구독자-데이터베이스-옵션"&gt;&lt;a href="#%ea%b5%ac%eb%8f%85%ec%9e%90-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4-%ec%98%b5%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;구독자 데이터베이스 옵션
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;옵션 A: JSON 파일 (간단)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// subscribers.json (GitHub 저장소에 암호화하여 저장)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;kubernetes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;docker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;active&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dev@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;golang&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rust&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;active&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;옵션 B: Supabase (권장)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- subscribers 테이블
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 배열 형태
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="github-actions-워크플로우"&gt;&lt;a href="#github-actions-%ec%9b%8c%ed%81%ac%ed%94%8c%eb%a1%9c%ec%9a%b0" class="header-anchor"&gt;&lt;/a&gt;GitHub Actions 워크플로우
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Email Notification&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;content/posts/**&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Node.js&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;node-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;18&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Extract Post Metadata&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # 가장 최근 수정된 포스트 찾기
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; POST_FILE=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep &amp;#39;content/posts&amp;#39; | head -1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Front Matter 파싱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; TITLE=$(grep &amp;#34;^title = &amp;#34; $POST_FILE | cut -d&amp;#39;&amp;#34;&amp;#39; -f2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; TAGS=$(grep &amp;#34;^tags = &amp;#34; $POST_FILE | sed &amp;#39;s/tags = \[//;s/\]//;s/&amp;#34;//g&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; SUMMARY=$(grep &amp;#34;^summary = &amp;#34; $POST_FILE | cut -d&amp;#39;&amp;#34;&amp;#39; -f2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; URL=&amp;#34;https://0andwild.github.io/$(dirname $POST_FILE | sed &amp;#39;s/content\///&amp;#39;)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;title=$TITLE&amp;#34; &amp;gt;&amp;gt; $GITHUB_OUTPUT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;tags=$TAGS&amp;#34; &amp;gt;&amp;gt; $GITHUB_OUTPUT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;summary=$SUMMARY&amp;#34; &amp;gt;&amp;gt; $GITHUB_OUTPUT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;url=$URL&amp;#34; &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Query Matching Subscribers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;subscribers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Supabase에서 매칭되는 구독자 조회
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -X POST https://YOUR_PROJECT.supabase.co/rest/v1/rpc/get_matching_subscribers \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;apikey: ${{ secrets.SUPABASE_KEY }}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;Content-Type: application/json&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#34;{\&amp;#34;post_tags\&amp;#34;: \&amp;#34;${{ steps.metadata.outputs.tags }}\&amp;#34;}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;gt; subscribers.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Send Emails via SendGrid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Node.js 스크립트 실행
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; cat &amp;gt; send-emails.js &amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const sgMail = require(&amp;#39;@sendgrid/mail&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const fs = require(&amp;#39;fs&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sgMail.setApiKey(process.env.SENDGRID_API_KEY);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const subscribers = JSON.parse(fs.readFileSync(&amp;#39;subscribers.json&amp;#39;));
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const title = process.env.POST_TITLE;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const summary = process.env.POST_SUMMARY;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const url = process.env.POST_URL;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; subscribers.forEach(async (subscriber) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; const msg = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; to: subscriber.email,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; from: &amp;#39;noreply@0andwild.github.io&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; subject: `새 글: ${title}`,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; html: `
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;lt;h2&amp;gt;${title}&amp;lt;/h2&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;lt;p&amp;gt;${summary}&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;lt;p&amp;gt;관심 키워드와 일치: ${subscriber.matched_keywords.join(&amp;#39;, &amp;#39;)}&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;lt;a href=&amp;#34;${url}&amp;#34;&amp;gt;글 읽기&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;lt;hr&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;lt;small&amp;gt;&amp;lt;a href=&amp;#34;https://0andwild.github.io/unsubscribe?token=${subscriber.token}&amp;#34;&amp;gt;구독 취소&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; `
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; await sgMail.send(msg);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; console.log(`Email sent to ${subscriber.email}`);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; npm install @sendgrid/mail
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; node send-emails.js&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SENDGRID_API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.SENDGRID_API_KEY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;POST_TITLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ steps.metadata.outputs.title }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;POST_SUMMARY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ steps.metadata.outputs.summary }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;POST_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ steps.metadata.outputs.url }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="구독-폼-구현-hugo-shortcode"&gt;&lt;a href="#%ea%b5%ac%eb%8f%85-%ed%8f%bc-%ea%b5%ac%ed%98%84-hugo-shortcode" class="header-anchor"&gt;&lt;/a&gt;구독 폼 구현 (Hugo Shortcode)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/shortcodes/subscribe.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;subscription-form&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;블로그 구독하기&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;subscribe-form&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;이메일 주소&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;관심 주제 선택 (선택한 주제의 글만 알림)&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kubernetes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Kubernetes&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;docker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Docker&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;golang&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Go&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;rust&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Rust&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;checkbox&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;devops&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; DevOps&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;구독하기&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;subscribe-form&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input[name=&amp;#34;keywords&amp;#34;]:checked&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Supabase에 저장
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://YOUR_PROJECT.supabase.co/rest/v1/subscribers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;apikey&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;YOUR_ANON_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;구독이 완료되었습니다!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;오류가 발생했습니다.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="supabase-함수-키워드-매칭"&gt;&lt;a href="#supabase-%ed%95%a8%ec%88%98-%ed%82%a4%ec%9b%8c%eb%93%9c-%eb%a7%a4%ec%b9%ad" class="header-anchor"&gt;&lt;/a&gt;Supabase 함수 (키워드 매칭)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 매칭되는 구독자를 찾는 함수
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_matching_subscribers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RETURNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matched_keywords&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;RETURN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTERSECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string_to_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matched_keywords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsubscribe_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;string_to_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 배열 겹침 연산자
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LANGUAGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="비용-분석"&gt;&lt;a href="#%eb%b9%84%ec%9a%a9-%eb%b6%84%ec%84%9d" class="header-anchor"&gt;&lt;/a&gt;비용 분석
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SendGrid&lt;/strong&gt;: 월 100통 무료 (이후 $19.95/월)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supabase&lt;/strong&gt;: 월 500MB DB, 2GB 전송 무료&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;: 월 2,000분 무료&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;총 비용&lt;/strong&gt;: 완전 무료 (소규모 블로그)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="5-완전-커스텀-supabase--github-actions--resend"&gt;&lt;a href="#5-%ec%99%84%ec%a0%84-%ec%bb%a4%ec%8a%a4%ed%85%80-supabase--github-actions--resend" class="header-anchor"&gt;&lt;/a&gt;5. 완전 커스텀 (Supabase + GitHub Actions + Resend)
&lt;/h2&gt;&lt;h3 id="sendgrid-대안-resend"&gt;&lt;a href="#sendgrid-%eb%8c%80%ec%95%88-resend" class="header-anchor"&gt;&lt;/a&gt;SendGrid 대안: Resend
&lt;/h3&gt;&lt;p&gt;SendGrid보다 개발자 친화적인 최신 이메일 API&lt;/p&gt;
&lt;h3 id="장점-5"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-5" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;무료 티어&lt;/strong&gt;: 월 3,000통 (SendGrid의 30배!)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;더 간단한 API&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;React Email 지원&lt;/strong&gt; (JSX로 이메일 작성)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;더 나은 개발자 경험&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="resend-사용-예시"&gt;&lt;a href="#resend-%ec%82%ac%ec%9a%a9-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;Resend 사용 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;resend&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Resend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESEND_API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;blog@0andwild.github.io&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`새 글: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;lt;/p&amp;gt;&amp;lt;a href=&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#34;&amp;gt;읽기&amp;lt;/a&amp;gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="비교표"&gt;&lt;a href="#%eb%b9%84%ea%b5%90%ed%91%9c" class="header-anchor"&gt;&lt;/a&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;th&gt;키워드 알림&lt;/th&gt;
 &lt;th&gt;난이도&lt;/th&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;Blogtrottr&lt;/td&gt;
 &lt;td&gt;무제한&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;⭐&lt;/td&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;FeedBurner&lt;/td&gt;
 &lt;td&gt;무제한&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;⭐⭐&lt;/td&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;strong&gt;Mailchimp&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;2,000명&lt;/td&gt;
 &lt;td&gt;⚠️ (Pro)&lt;/td&gt;
 &lt;td&gt;⭐⭐&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⚠️&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;일반 구독용&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Buttondown&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;1,000명&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;개발자용&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SendGrid + Actions&lt;/td&gt;
 &lt;td&gt;100통/월&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&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;strong&gt;Resend + Actions&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;3,000통/월&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
 &lt;td&gt;✅&lt;/td&gt;
 &lt;td&gt;✅✅&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;완벽한 통제&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="추천-로드맵"&gt;&lt;a href="#%ec%b6%94%ec%b2%9c-%eb%a1%9c%eb%93%9c%eb%a7%b5" class="header-anchor"&gt;&lt;/a&gt;추천 로드맵
&lt;/h2&gt;&lt;h3 id="단계-1-빠른-시작-즉시"&gt;&lt;a href="#%eb%8b%a8%ea%b3%84-1-%eb%b9%a0%eb%a5%b8-%ec%8b%9c%ec%9e%91-%ec%a6%89%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;단계 1: 빠른 시작 (즉시)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Mailchimp RSS Campaign&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10분 설정&lt;/li&gt;
&lt;li&gt;전체 구독자에게 모든 글 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단계-2-개선-1주-후"&gt;&lt;a href="#%eb%8b%a8%ea%b3%84-2-%ea%b0%9c%ec%84%a0-1%ec%a3%bc-%ed%9b%84" class="header-anchor"&gt;&lt;/a&gt;단계 2: 개선 (1주 후)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Buttondown&lt;/strong&gt;으로 마이그레이션&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;더 깔끔한 경험&lt;/li&gt;
&lt;li&gt;기본 태그 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단계-3-고급-기능-필요-시"&gt;&lt;a href="#%eb%8b%a8%ea%b3%84-3-%ea%b3%a0%ea%b8%89-%ea%b8%b0%eb%8a%a5-%ed%95%84%ec%9a%94-%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;단계 3: 고급 기능 (필요 시)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Resend + GitHub Actions + Supabase&lt;/strong&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;/ul&gt;
&lt;hr&gt;
&lt;h2 id="결론"&gt;&lt;a href="#%ea%b2%b0%eb%a1%a0" class="header-anchor"&gt;&lt;/a&gt;결론
&lt;/h2&gt;&lt;h3 id="일반-블로거라면"&gt;&lt;a href="#%ec%9d%bc%eb%b0%98-%eb%b8%94%eb%a1%9c%ea%b1%b0%eb%9d%bc%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;일반 블로거라면:
&lt;/h3&gt;&lt;p&gt;→ &lt;strong&gt;Mailchimp&lt;/strong&gt; (가장 쉽고 전문적)&lt;/p&gt;
&lt;h3 id="개발자-블로그라면"&gt;&lt;a href="#%ea%b0%9c%eb%b0%9c%ec%9e%90-%eb%b8%94%eb%a1%9c%ea%b7%b8%eb%9d%bc%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;개발자 블로그라면:
&lt;/h3&gt;&lt;p&gt;→ &lt;strong&gt;Buttondown&lt;/strong&gt; (개발자 친화적, API 제공)&lt;/p&gt;
&lt;h3 id="키워드-알림이-필수라면"&gt;&lt;a href="#%ed%82%a4%ec%9b%8c%eb%93%9c-%ec%95%8c%eb%a6%bc%ec%9d%b4-%ed%95%84%ec%88%98%eb%9d%bc%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;키워드 알림이 필수라면:
&lt;/h3&gt;&lt;p&gt;→ &lt;strong&gt;Resend + GitHub Actions + Supabase&lt;/strong&gt; (완전 커스텀)&lt;/p&gt;
&lt;h3 id="돈-안-쓰고-테스트하려면"&gt;&lt;a href="#%eb%8f%88-%ec%95%88-%ec%93%b0%ea%b3%a0-%ed%85%8c%ec%8a%a4%ed%8a%b8%ed%95%98%eb%a0%a4%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;돈 안 쓰고 테스트하려면:
&lt;/h3&gt;&lt;p&gt;→ &lt;strong&gt;Blogtrottr&lt;/strong&gt; (30초 설정)&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="빠른시작"&gt;&lt;a href="#%eb%b9%a0%eb%a5%b8%ec%8b%9c%ec%9e%91" class="header-anchor"&gt;&lt;/a&gt;빠른시작
&lt;/h2&gt;&lt;p&gt;실제 구현을 원하신다면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mailchimp로 시작 (학습 곡선 낮음)&lt;/li&gt;
&lt;li&gt;트래픽 증가 시 Buttondown 고려&lt;/li&gt;
&lt;li&gt;고급 기능 필요 시 커스텀 솔루션 구축&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;키워드 알림은 초기엔 과한 기능일 수 있으니, 기본 구독부터 시작하는 것을 권장합니다.&lt;/p&gt;</description></item><item><title>Hugo markdown 설명서</title><link>https://0andwild.com/posts/251016_blowfish_markdown/</link><pubDate>Thu, 16 Oct 2025 18:36:52 +0900</pubDate><guid>https://0andwild.com/posts/251016_blowfish_markdown/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Hugo markdown 설명서" /&gt;&lt;!-- 본문 작성 가이드 --&gt;
&lt;h2 id="제목-h2"&gt;&lt;a href="#%ec%a0%9c%eb%aa%a9-h2" class="header-anchor"&gt;&lt;/a&gt;제목 (H2)
&lt;/h2&gt;&lt;h3 id="소제목-h3"&gt;&lt;a href="#%ec%86%8c%ec%a0%9c%eb%aa%a9-h3" class="header-anchor"&gt;&lt;/a&gt;소제목 (H3)
&lt;/h3&gt;&lt;p&gt;일반 텍스트입니다. &lt;strong&gt;굵게&lt;/strong&gt;, &lt;em&gt;기울임&lt;/em&gt;, &lt;del&gt;취소선&lt;/del&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="이미지-삽입"&gt;&lt;a href="#%ec%9d%b4%eb%af%b8%ec%a7%80-%ec%82%bd%ec%9e%85" class="header-anchor"&gt;&lt;/a&gt;이미지 삽입
&lt;/h2&gt;&lt;h3 id="방법-1-로컬-이미지"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-1-%eb%a1%9c%ec%bb%ac-%ec%9d%b4%eb%af%b8%ec%a7%80" class="header-anchor"&gt;&lt;/a&gt;방법 1: 로컬 이미지
&lt;/h3&gt;&lt;p&gt;포스트 폴더 내에 이미지 파일을 넣고 사용:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;이미지 설명&lt;/span&gt;](&lt;span class="na"&gt;image.jpg&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="방법-2-외부-이미지-url"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-2-%ec%99%b8%eb%b6%80-%ec%9d%b4%eb%af%b8%ec%a7%80-url" class="header-anchor"&gt;&lt;/a&gt;방법 2: 외부 이미지 URL
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;이미지 설명&lt;/span&gt;](&lt;span class="na"&gt;https://example.com/image.jpg&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="방법-3-html-태그-크기-조정-가능"&gt;&lt;a href="#%eb%b0%a9%eb%b2%95-3-html-%ed%83%9c%ea%b7%b8-%ed%81%ac%ea%b8%b0-%ec%a1%b0%ec%a0%95-%ea%b0%80%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;방법 3: HTML 태그 (크기 조정 가능)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;image.jpg&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;이미지 설명&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;500&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="캐러셀-이미지-슬라이드-효과"&gt;&lt;a href="#%ec%ba%90%eb%9f%ac%ec%85%80-%ec%9d%b4%eb%af%b8%ec%a7%80-%ec%8a%ac%eb%9d%bc%ec%9d%b4%eb%93%9c-%ed%9a%a8%ea%b3%bc" class="header-anchor"&gt;&lt;/a&gt;캐러셀 이미지 (슬라이드 효과)
&lt;/h3&gt;&lt;h3 id="169"&gt;&lt;a href="#169" class="header-anchor"&gt;&lt;/a&gt;16:9
&lt;/h3&gt;&lt;div class="stack-carousel" style="--stack-carousel-ratio: ZgotmplZ;"&gt;&lt;img src="https://0andwild.com/posts/251016_blowfish_markdown/img/test1.jpeg" alt="img/test1.jpeg" loading="lazy"&gt;&lt;img src="https://0andwild.com/posts/251016_blowfish_markdown/img/test2.jpeg" alt="img/test2.jpeg" loading="lazy"&gt;&lt;img src="https://0andwild.com/posts/251016_blowfish_markdown/img/test3.jpeg" alt="img/test3.jpeg" loading="lazy"&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2 id="hahahugoshortcode67s1hbhb"&gt;&lt;a href="#hahahugoshortcode67s1hbhb" class="header-anchor"&gt;&lt;/a&gt;21:9
&lt;div class="stack-carousel" style="--stack-carousel-ratio: ZgotmplZ;"&gt;&lt;img src="https://0andwild.com/posts/251016_blowfish_markdown/img/test1.jpeg" alt="img/test1.jpeg" loading="lazy"&gt;&lt;img src="https://0andwild.com/posts/251016_blowfish_markdown/img/test2.jpeg" alt="img/test2.jpeg" loading="lazy"&gt;&lt;img src="https://0andwild.com/posts/251016_blowfish_markdown/img/test3.jpeg" alt="img/test3.jpeg" loading="lazy"&gt;&lt;/div&gt;

&lt;/h2&gt;&lt;h2 id="코드-삽입"&gt;&lt;a href="#%ec%bd%94%eb%93%9c-%ec%82%bd%ec%9e%85" class="header-anchor"&gt;&lt;/a&gt;코드 삽입
&lt;/h2&gt;&lt;h3 id="인라인-코드"&gt;&lt;a href="#%ec%9d%b8%eb%9d%bc%ec%9d%b8-%ec%bd%94%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;인라인 코드
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;inline code&lt;/code&gt; 형식으로 작성&lt;/p&gt;
&lt;h3 id="코드-블록"&gt;&lt;a href="#%ec%bd%94%eb%93%9c-%eb%b8%94%eb%a1%9d" class="header-anchor"&gt;&lt;/a&gt;코드 블록
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -p 8080:80 nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="링크"&gt;&lt;a href="#%eb%a7%81%ed%81%ac" class="header-anchor"&gt;&lt;/a&gt;링크
&lt;/h2&gt;&lt;h3 id="기본-링크"&gt;&lt;a href="#%ea%b8%b0%eb%b3%b8-%eb%a7%81%ed%81%ac" class="header-anchor"&gt;&lt;/a&gt;기본 링크
&lt;/h3&gt;&lt;p&gt;&lt;a class="link" href="https://example.com" target="_blank" rel="noopener"
 &gt;링크 텍스트&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="참조-스타일-링크"&gt;&lt;a href="#%ec%b0%b8%ec%a1%b0-%ec%8a%a4%ed%83%80%ec%9d%bc-%eb%a7%81%ed%81%ac" class="header-anchor"&gt;&lt;/a&gt;참조 스타일 링크
&lt;/h3&gt;&lt;p&gt;&lt;a class="link" href="https://example.com" title="링크 설명"
 target="_blank" rel="noopener"
 &gt;링크 텍스트&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="article-참조"&gt;&lt;a href="#article-%ec%b0%b8%ec%a1%b0" class="header-anchor"&gt;&lt;/a&gt;article 참조
&lt;/h3&gt;&lt;a class="stack-article-card" href="https://0andwild.com/docs/welcome/"&gt;
 &lt;span class="stack-article-card__title"&gt;/docs/welcome/&lt;/span&gt;&lt;span&gt;Open linked article&lt;/span&gt;&lt;/a&gt;

&lt;hr&gt;
&lt;h2 id="리스트"&gt;&lt;a href="#%eb%a6%ac%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;리스트
&lt;/h2&gt;&lt;h3 id="순서-없는-리스트"&gt;&lt;a href="#%ec%88%9c%ec%84%9c-%ec%97%86%eb%8a%94-%eb%a6%ac%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;순서 없는 리스트
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;항목 1&lt;/li&gt;
&lt;li&gt;항목 2
&lt;ul&gt;
&lt;li&gt;하위 항목 2-1&lt;/li&gt;
&lt;li&gt;하위 항목 2-2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;항목 3&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="순서-있는-리스트"&gt;&lt;a href="#%ec%88%9c%ec%84%9c-%ec%9e%88%eb%8a%94-%eb%a6%ac%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;순서 있는 리스트
&lt;/h3&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;h3 id="체크리스트"&gt;&lt;a href="#%ec%b2%b4%ed%81%ac%eb%a6%ac%ec%8a%a4%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;체크리스트
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 할 일 1&lt;/li&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; 완료된 일&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; 할 일 2&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="인용문"&gt;&lt;a href="#%ec%9d%b8%ec%9a%a9%eb%ac%b8" class="header-anchor"&gt;&lt;/a&gt;인용문
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;인용문 내용입니다.
여러 줄도 가능합니다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="표-table"&gt;&lt;a href="#%ed%91%9c-table" class="header-anchor"&gt;&lt;/a&gt;표 (Table)
&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;A&lt;/td&gt;
 &lt;td&gt;설명 A&lt;/td&gt;
 &lt;td&gt;비고 A&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;B&lt;/td&gt;
 &lt;td&gt;설명 B&lt;/td&gt;
 &lt;td&gt;비고 B&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="링크-임베드-shortcodes"&gt;&lt;a href="#%eb%a7%81%ed%81%ac-%ec%9e%84%eb%b2%a0%eb%93%9c-shortcodes" class="header-anchor"&gt;&lt;/a&gt;링크 임베드 (Shortcodes)
&lt;/h2&gt;&lt;h3 id="youtube-영상"&gt;&lt;a href="#youtube-%ec%98%81%ec%83%81" class="header-anchor"&gt;&lt;/a&gt;YouTube 영상
&lt;/h3&gt;&lt;p&gt;{{&amp;lt; youtube VIDEO_ID &amp;gt;}}&lt;/p&gt;
&lt;h3 id="twitterx"&gt;&lt;a href="#twitterx" class="header-anchor"&gt;&lt;/a&gt;Twitter/X
&lt;/h3&gt;&lt;p&gt;{{&amp;lt; twitter user=&amp;ldquo;username&amp;rdquo; id=&amp;ldquo;tweet_id&amp;rdquo; &amp;gt;}}&lt;/p&gt;
&lt;h3 id="github-gist"&gt;&lt;a href="#github-gist" class="header-anchor"&gt;&lt;/a&gt;GitHub Gist
&lt;/h3&gt;&lt;p&gt;{{&amp;lt; gist username gist_id &amp;gt;}}&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="알림-박스-blowfish-alert"&gt;&lt;a href="#%ec%95%8c%eb%a6%bc-%eb%b0%95%ec%8a%a4-blowfish-alert" class="header-anchor"&gt;&lt;/a&gt;알림 박스 (Blowfish Alert)
&lt;/h2&gt;&lt;p&gt;{{&amp;lt; alert &amp;ldquo;circle-info&amp;rdquo; &amp;gt;}}
정보 알림입니다.
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;p&gt;{{&amp;lt; alert &amp;ldquo;lightbulb&amp;rdquo; &amp;gt;}}
팁이나 아이디어입니다.
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;p&gt;{{&amp;lt; alert &amp;ldquo;triangle-exclamation&amp;rdquo; &amp;gt;}}
경고 메시지입니다.
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="접기펼치기-details"&gt;&lt;a href="#%ec%a0%91%ea%b8%b0%ed%8e%bc%ec%b9%98%ea%b8%b0-details" class="header-anchor"&gt;&lt;/a&gt;접기/펼치기 (Details)
&lt;/h2&gt;&lt;details&gt;
&lt;summary&gt;클릭하여 펼치기&lt;/summary&gt;
&lt;p&gt;숨겨진 내용이 여기에 표시됩니다.&lt;/p&gt;
&lt;/details&gt;
&lt;hr&gt;
&lt;h2 id="주석"&gt;&lt;a href="#%ec%a3%bc%ec%84%9d" class="header-anchor"&gt;&lt;/a&gt;주석
&lt;/h2&gt;&lt;!-- 이 부분은 화면에 표시되지 않습니다 --&gt;
&lt;hr&gt;
&lt;h2 id="수평선"&gt;&lt;a href="#%ec%88%98%ed%8f%89%ec%84%a0" class="header-anchor"&gt;&lt;/a&gt;수평선
&lt;/h2&gt;&lt;p&gt;위아래로 구분선을 만들 때 사용:&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="각주"&gt;&lt;a href="#%ea%b0%81%ec%a3%bc" class="header-anchor"&gt;&lt;/a&gt;각주
&lt;/h2&gt;&lt;p&gt;텍스트에 각주&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;를 추가할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="그래프-차트"&gt;&lt;a href="#%ea%b7%b8%eb%9e%98%ed%94%84-%ec%b0%a8%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;그래프 차트
&lt;/h3&gt;&lt;div class="stack-chart"&gt;
 &lt;canvas id="stack-chart-3"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/chart.js"&gt;&lt;/script&gt;
&lt;script&gt;
 (function () {
 const ctx = document.getElementById("stack-chart-3");
 if (!ctx || typeof Chart === "undefined") return;
 new Chart(ctx, { 
type: 'bar',
data: {
 labels: ['Tomato', 'Blueberry', 'Banana', 'Lime', 'Orange'],
 datasets: [{
 label: '# of votes',
 data: [12, 19, 3, 5, 3],
 }]
}
 });
 })();
&lt;/script&gt;

&lt;hr&gt;
&lt;h3 id="mermaid-차트"&gt;&lt;a href="#mermaid-%ec%b0%a8%ed%8a%b8" class="header-anchor"&gt;&lt;/a&gt;Mermaid 차트
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;
graph LR;
A[Lemons]--&gt;B[Lemonade];
B--&gt;C[Profit]
&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id="swatched-color-showcase"&gt;&lt;a href="#swatched-color-showcase" class="header-anchor"&gt;&lt;/a&gt;Swatched (color showcase)
&lt;/h3&gt;&lt;div class="stack-swatch-grid"&gt;&lt;div class="stack-swatch" style="background: #64748b;"&gt;
 &lt;span&gt;#64748b&lt;/span&gt;
 &lt;/div&gt;&lt;div class="stack-swatch" style="background: #3b82f6;"&gt;
 &lt;span&gt;#3b82f6&lt;/span&gt;
 &lt;/div&gt;&lt;div class="stack-swatch" style="background: #06b6d4;"&gt;
 &lt;span&gt;#06b6d4&lt;/span&gt;
 &lt;/div&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h3 id="typelt"&gt;&lt;a href="#typelt" class="header-anchor"&gt;&lt;/a&gt;TypeLt
&lt;/h3&gt;&lt;p&gt;(Ex1)&lt;/p&gt;
&amp;lt;p id="stack-typeit-6" class="stack-typeit stack-typeit-cursor"&gt;&amp;lt;/p&gt;
&lt;script&gt;
 (function () {
 const target = document.getElementById("stack-typeit-6");
 if (!target) return;

 const lines = "[\"Lorem ipsum dolor sit amet\"]";
 const speed = "65";
 const shouldLoop = false;
 let lineIndex = 0;
 let charIndex = 0;
 let deleting = false;

 function tick() {
 const line = lines[lineIndex] || "";
 if (!deleting) {
 target.textContent = line.slice(0, charIndex + 1);
 charIndex += 1;
 if (charIndex === line.length) {
 deleting = true;
 setTimeout(tick, 1200);
 return;
 }
 } else {
 target.textContent = line.slice(0, Math.max(charIndex - 1, 0));
 charIndex -= 1;
 if (charIndex &lt;= 0) {
 deleting = false;
 lineIndex = (lineIndex + 1) % lines.length;
 if (!shouldLoop &amp;&amp; lineIndex === 0) {
 target.textContent = lines[lines.length - 1] || "";
 target.classList.remove("stack-typeit-cursor");
 return;
 }
 }
 }
 setTimeout(tick, deleting ? speed / 2 : speed);
 }

 tick();
 })();
&lt;/script&gt;

&lt;p&gt;(Ex2)&lt;/p&gt;
&amp;lt;h1 id="stack-typeit-7" class="stack-typeit stack-typeit-cursor"&gt;&amp;lt;/h1&gt;
&lt;script&gt;
 (function () {
 const target = document.getElementById("stack-typeit-7");
 if (!target) return;

 const lines = "[\"Lorem ipsum dolor sit amet, \",\"consectetur adipiscing elit.\"]";
 const speed = "65";
 const shouldLoop = false;
 let lineIndex = 0;
 let charIndex = 0;
 let deleting = false;

 function tick() {
 const line = lines[lineIndex] || "";
 if (!deleting) {
 target.textContent = line.slice(0, charIndex + 1);
 charIndex += 1;
 if (charIndex === line.length) {
 deleting = true;
 setTimeout(tick, 1200);
 return;
 }
 } else {
 target.textContent = line.slice(0, Math.max(charIndex - 1, 0));
 charIndex -= 1;
 if (charIndex &lt;= 0) {
 deleting = false;
 lineIndex = (lineIndex + 1) % lines.length;
 if (!shouldLoop &amp;&amp; lineIndex === 0) {
 target.textContent = lines[lines.length - 1] || "";
 target.classList.remove("stack-typeit-cursor");
 return;
 }
 }
 }
 setTimeout(tick, deleting ? speed / 2 : speed);
 }

 tick();
 })();
&lt;/script&gt;

&lt;p&gt;(Ex3)&lt;/p&gt;
&amp;lt;h3 id="stack-typeit-8" class="stack-typeit stack-typeit-cursor"&gt;&amp;lt;/h3&gt;
&lt;script&gt;
 (function () {
 const target = document.getElementById("stack-typeit-8");
 if (!target) return;

 const lines = "[\"\\\"Frankly, my dear, I don't give a damn.\\\" Gone with the Wind (1939)\",\"\\\"I'm gonna make him an offer he can't refuse.\\\" The Godfather (1972)\",\"\\\"Toto, I've a feeling we're not in Kansas anymore.\\\" The Wizard of Oz (1939)\"]";
 const speed = 50 ;
 const shouldLoop = false;
 let lineIndex = 0;
 let charIndex = 0;
 let deleting = false;

 function tick() {
 const line = lines[lineIndex] || "";
 if (!deleting) {
 target.textContent = line.slice(0, charIndex + 1);
 charIndex += 1;
 if (charIndex === line.length) {
 deleting = true;
 setTimeout(tick, 1200);
 return;
 }
 } else {
 target.textContent = line.slice(0, Math.max(charIndex - 1, 0));
 charIndex -= 1;
 if (charIndex &lt;= 0) {
 deleting = false;
 lineIndex = (lineIndex + 1) % lines.length;
 if (!shouldLoop &amp;&amp; lineIndex === 0) {
 target.textContent = lines[lines.length - 1] || "";
 target.classList.remove("stack-typeit-cursor");
 return;
 }
 }
 }
 setTimeout(tick, deleting ? speed / 2 : speed);
 }

 tick();
 })();
&lt;/script&gt;

&lt;hr&gt;
&lt;h3 id="youtube-lite"&gt;&lt;a href="#youtube-lite" class="header-anchor"&gt;&lt;/a&gt;Youtube Lite
&lt;/h3&gt;&lt;div class="stack-youtube-lite"&gt;
 &lt;iframe
 src="https://www.youtube.com/embed/SgXhGb-7QbU"
 title="Blowfish-tools demo"
 loading="lazy"
 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
 referrerpolicy="strict-origin-when-cross-origin"
 allowfullscreen&gt;
 &lt;/iframe&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;작성 팁:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Front matter의 &lt;code&gt;draft: true&lt;/code&gt;를 &lt;code&gt;false&lt;/code&gt;로 변경하면 배포됩니다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;과 &lt;code&gt;summary&lt;/code&gt;를 작성하면 SEO에 도움이 됩니다&lt;/li&gt;
&lt;li&gt;이미지는 포스트 폴더에 함께 넣는 것을 권장합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;각주 내용입니다.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Hugo &amp; GithubPages 블로그로 넘어온 이유</title><link>https://0andwild.com/posts/251015_about_hugo/</link><pubDate>Wed, 15 Oct 2025 17:21:09 +0900</pubDate><guid>https://0andwild.com/posts/251015_about_hugo/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Hugo &amp; GithubPages 블로그로 넘어온 이유" /&gt;&lt;h2 id="왜-hugo--github-pages로-넘어왔는가"&gt;&lt;a href="#%ec%99%9c-hugo--github-pages%eb%a1%9c-%eb%84%98%ec%96%b4%ec%99%94%eb%8a%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;왜 Hugo &amp;amp; GitHub Pages로 넘어왔는가?
&lt;/h2&gt;&lt;p&gt;기존 &lt;a class="link" href="https://0andwild.tistory.com/" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;Tstory&lt;/strong&gt;&lt;/a&gt;로 운영하였던 기술 블로그를 &lt;strong&gt;Hugo &amp;amp; GitHub Pages&lt;/strong&gt;로 마이그레이션을 하기로 결심하게 되었다.&lt;/p&gt;
&lt;h3 id="1-흩어진-콘텐츠-관리의-어려움"&gt;&lt;a href="#1-%ed%9d%a9%ec%96%b4%ec%a7%84-%ec%bd%98%ed%85%90%ec%b8%a0-%ea%b4%80%eb%a6%ac%ec%9d%98-%ec%96%b4%eb%a0%a4%ec%9b%80" class="header-anchor"&gt;&lt;/a&gt;1. 흩어진 콘텐츠 관리의 어려움
&lt;/h3&gt;&lt;p&gt;여러가지 노트 툴을 사용하다보니 회사를 다니면서 또는 공부를 하면서 정리하는 글들이 중구난방하게 흩어지게 되었고 이걸 또 다시 블로그로 옮겨야 하는 번거로움이 생겨 블로그 관리를 소홀히 하게 되었다.&lt;/p&gt;
&lt;h3 id="2-마크다운-호환성-문제"&gt;&lt;a href="#2-%eb%a7%88%ed%81%ac%eb%8b%a4%ec%9a%b4-%ed%98%b8%ed%99%98%ec%84%b1-%eb%ac%b8%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;2. 마크다운 호환성 문제
&lt;/h3&gt;&lt;p&gt;기존 노트 툴에서 사용하는 마크다운 문법이 Tstory에서 글을 올릴때 완벽히 호환되지 않아 수정을 해야하는 일들이 자주 발생했었고 이 또한 번거로움이 생기는 일이였다.&lt;/p&gt;
&lt;p&gt;특히 다음과 같은 문제들이 있었다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드 블록의 syntax highlighting 지원 부족&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;h3 id="3-tstory-open-api-지원-종료"&gt;&lt;a href="#3-tstory-open-api-%ec%a7%80%ec%9b%90-%ec%a2%85%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;3. Tstory Open API 지원 종료
&lt;/h3&gt;&lt;p&gt;마지막으로는 최근에 다시 그동안 공부한 자료들을 다시 정리해서 Tstory에 올릴겸 블로그 스킨도 다시 이쁘게 꾸미고 &lt;strong&gt;Tstory 공식 Open API&lt;/strong&gt;를 활용하여 기존 노트 툴들과 연동하는 작업을 진행해보려 하였지만 Open API의 공식 지원이 종료 되어있었고 더이상 Tstory를 사용할 이유가 없어졌다.&lt;/p&gt;
&lt;h3 id="블로그-플랫폼-선택-기준"&gt;&lt;a href="#%eb%b8%94%eb%a1%9c%ea%b7%b8-%ed%94%8c%eb%9e%ab%ed%8f%bc-%ec%84%a0%ed%83%9d-%ea%b8%b0%ec%a4%80" class="header-anchor"&gt;&lt;/a&gt;블로그 플랫폼 선택 기준
&lt;/h3&gt;&lt;p&gt;여러 블로그를 참고하여 어떤 방식이 좋을지 고민을 많이 하였고 아래와 같이 기준점을 가지고 &lt;strong&gt;Hugo &amp;amp; GitHub Pages&lt;/strong&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;/li&gt;
&lt;li&gt;&lt;strong&gt;내가 원하는 기능을 추가하는 자유도가 높은가?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Pages를 사용해서 빌드하고 배포할때 속도가 빠른가?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obsidian과 같은 노트 툴들과 연동하기 편한가?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="hugo란"&gt;&lt;a href="#hugo%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;Hugo란?
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;Hugo&lt;/strong&gt;&lt;/a&gt;는 &lt;strong&gt;Go 언어&lt;/strong&gt;로 작성된 빠르고 유연한 &lt;strong&gt;정적 사이트 생성기&lt;/strong&gt;(Static Site Generator)이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주요 특징:&lt;/strong&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;: Markdown으로 콘텐츠를 작성하면 Hugo가 HTML로 변환해준다&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;h2 id="github-pages와-함께-사용되는-정적-사이트-생성기-비교"&gt;&lt;a href="#github-pages%ec%99%80-%ed%95%a8%ea%bb%98-%ec%82%ac%ec%9a%a9%eb%90%98%eb%8a%94-%ec%a0%95%ec%a0%81-%ec%82%ac%ec%9d%b4%ed%8a%b8-%ec%83%9d%ec%84%b1%ea%b8%b0-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;GitHub Pages와 함께 사용되는 정적 사이트 생성기 비교
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;특징&lt;/th&gt;
 &lt;th&gt;Hugo&lt;/th&gt;
 &lt;th&gt;Jekyll&lt;/th&gt;
 &lt;th&gt;Gatsby&lt;/th&gt;
 &lt;th&gt;Next.js (SSG)&lt;/th&gt;
 &lt;th&gt;VuePress&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;언어&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Go&lt;/td&gt;
 &lt;td&gt;Ruby&lt;/td&gt;
 &lt;td&gt;React (JavaScript)&lt;/td&gt;
 &lt;td&gt;React (JavaScript)&lt;/td&gt;
 &lt;td&gt;Vue.js&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;빌드 속도&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;⚡ 매우 빠름 (&amp;lt; 1ms/page)&lt;/td&gt;
 &lt;td&gt;🐢 느림&lt;/td&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;strong&gt;설치 복잡도&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;✅ 단일 바이너리&lt;/td&gt;
 &lt;td&gt;⚠️ Ruby 환경 필요&lt;/td&gt;
 &lt;td&gt;⚠️ Node.js + 많은 의존성&lt;/td&gt;
 &lt;td&gt;⚠️ Node.js + 의존성&lt;/td&gt;
 &lt;td&gt;⚠️ Node.js + 의존성&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;GitHub Pages 기본 지원&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;❌ (Actions 필요)&lt;/td&gt;
 &lt;td&gt;✅ 네이티브 지원&lt;/td&gt;
 &lt;td&gt;❌ (Actions 필요)&lt;/td&gt;
 &lt;td&gt;❌ (Actions 필요)&lt;/td&gt;
 &lt;td&gt;❌ (Actions 필요)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;학습 곡선&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;낮음&lt;/td&gt;
 &lt;td&gt;낮음&lt;/td&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;strong&gt;테마/플러그인&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;풍부&lt;/td&gt;
 &lt;td&gt;매우 풍부&lt;/td&gt;
 &lt;td&gt;풍부 (React 생태계)&lt;/td&gt;
 &lt;td&gt;풍부 (React 생태계)&lt;/td&gt;
 &lt;td&gt;보통&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;적합한 용도&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;블로그, 문서, 포트폴리오&lt;/td&gt;
 &lt;td&gt;블로그, GitHub 기본&lt;/td&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;strong&gt;빌드 시간 (1000 페이지)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;~1초&lt;/td&gt;
 &lt;td&gt;~2분&lt;/td&gt;
 &lt;td&gt;~30초&lt;/td&gt;
 &lt;td&gt;~30초&lt;/td&gt;
 &lt;td&gt;~20초&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Hugo를 선택한 이유:&lt;/strong&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;: 복잡한 JavaScript 프레임워크 없이 Markdown에 집중할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제로 의존성&lt;/strong&gt;: 단일 실행 파일로 환경 설정 문제가 없다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;풍부한 테마&lt;/strong&gt;: Blowfish 같은 고품질 테마를 쉽게 적용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="github-pages-배포"&gt;&lt;a href="#github-pages-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;GitHub Pages 배포
&lt;/h2&gt;&lt;p&gt;Hugo로 작성한 블로그는 &lt;strong&gt;GitHub Actions&lt;/strong&gt;를 통해 자동으로 빌드되고 배포된다.&lt;/p&gt;
&lt;h3 id="배포-워크플로우"&gt;&lt;a href="#%eb%b0%b0%ed%8f%ac-%ec%9b%8c%ed%81%ac%ed%94%8c%eb%a1%9c%ec%9a%b0" class="header-anchor"&gt;&lt;/a&gt;배포 워크플로우
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt; 브랜치에 변경사항을 push한다&lt;/li&gt;
&lt;li&gt;GitHub Actions가 자동으로 트리거된다&lt;/li&gt;
&lt;li&gt;Hugo가 정적 사이트를 빌드한다&lt;/li&gt;
&lt;li&gt;빌드된 파일이 GitHub Pages로 자동 배포된다&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="장점"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;자동화된 배포&lt;/strong&gt;: 코드를 push하면 자동으로 배포가 진행된다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;버전 관리&lt;/strong&gt;: Git을 통해 모든 변경사항을 추적할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무료 호스팅&lt;/strong&gt;: GitHub Pages는 무료로 제공된다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;커스텀 도메인&lt;/strong&gt;: 원하는 도메인을 연결할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS 지원&lt;/strong&gt;: 기본적으로 HTTPS가 제공된다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="obsidian-연동"&gt;&lt;a href="#obsidian-%ec%97%b0%eb%8f%99" class="header-anchor"&gt;&lt;/a&gt;Obsidian 연동
&lt;/h2&gt;&lt;p&gt;Hugo는 마크다운 기반이기 때문에 &lt;strong&gt;Obsidian&lt;/strong&gt;과 같은 노트 툴과 완벽하게 호환된다.&lt;/p&gt;
&lt;h3 id="연동-방법"&gt;&lt;a href="#%ec%97%b0%eb%8f%99-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;연동 방법
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Hugo 블로그의 &lt;code&gt;content/posts&lt;/code&gt; 디렉토리를 Obsidian vault로 설정한다&lt;/li&gt;
&lt;li&gt;Obsidian에서 글을 작성하고 편집한다&lt;/li&gt;
&lt;li&gt;작성이 완료되면 Git을 통해 commit &amp;amp; push한다&lt;/li&gt;
&lt;li&gt;GitHub Actions가 자동으로 빌드하고 배포한다&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="이점"&gt;&lt;a href="#%ec%9d%b4%ec%a0%90" class="header-anchor"&gt;&lt;/a&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;li&gt;&lt;strong&gt;로컬 우선&lt;/strong&gt;: 인터넷 없이도 글을 작성할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;강력한 링크 기능&lt;/strong&gt;: Obsidian의 백링크와 그래프 뷰를 활용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="기본적인-hugo-terminal-명령어"&gt;&lt;a href="#%ea%b8%b0%eb%b3%b8%ec%a0%81%ec%9d%b8-hugo-terminal-%eb%aa%85%eb%a0%b9%ec%96%b4" class="header-anchor"&gt;&lt;/a&gt;기본적인 Hugo Terminal 명령어
&lt;/h2&gt;&lt;h3 id="개발-서버-실행"&gt;&lt;a href="#%ea%b0%9c%eb%b0%9c-%ec%84%9c%eb%b2%84-%ec%8b%a4%ed%96%89" class="header-anchor"&gt;&lt;/a&gt;개발 서버 실행
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;로컬 개발 서버를 시작한다. 기본적으로 &lt;code&gt;http://localhost:1313&lt;/code&gt;에서 사이트를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주요 옵션:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-D&lt;/code&gt; 또는 &lt;code&gt;--buildDrafts&lt;/code&gt;: 초안(draft) 콘텐츠도 함께 빌드한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--bind 0.0.0.0&lt;/code&gt;: 모든 네트워크 인터페이스에서 접근 가능하도록 설정한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--port 8080&lt;/code&gt;: 기본 포트(1313) 대신 다른 포트를 사용한다&lt;/li&gt;
&lt;li&gt;파일 변경 시 자동으로 브라우저가 새로고침된다 (Live Reload)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server -D
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server --bind 0.0.0.0 --port &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="프로덕션-빌드"&gt;&lt;a href="#%ed%94%84%eb%a1%9c%eb%8d%95%ec%85%98-%eb%b9%8c%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;프로덕션 빌드
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --cleanDestinationDir
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;프로덕션용 정적 사이트를 빌드한다. &lt;code&gt;public/&lt;/code&gt; 디렉토리에 결과물이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주요 기능:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--cleanDestinationDir&lt;/code&gt;: 빌드 전에 대상 디렉토리(&lt;code&gt;public/&lt;/code&gt;)를 완전히 정리한다&lt;/li&gt;
&lt;li&gt;이전 빌드의 불필요한 파일을 제거하여 깨끗한 상태로 빌드를 수행한다&lt;/li&gt;
&lt;li&gt;파일명이 변경되거나 삭제된 경우에도 이전 버전의 파일이 남아있지 않도록 보장한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --cleanDestinationDir
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --cleanDestinationDir --minify &lt;span class="c1"&gt;# 파일 최소화 옵션 추가&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="테마-정보"&gt;&lt;a href="#%ed%85%8c%eb%a7%88-%ec%a0%95%eb%b3%b4" class="header-anchor"&gt;&lt;/a&gt;테마 정보
&lt;/h2&gt;&lt;h3 id="hugo-blowfish-theme"&gt;&lt;a href="#hugo-blowfish-theme" class="header-anchor"&gt;&lt;/a&gt;Hugo Blowfish Theme
&lt;/h3&gt;&lt;p&gt;이 블로그는 &lt;a class="link" href="https://blowfish.page/" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;Blowfish&lt;/strong&gt;&lt;/a&gt; 테마를 사용하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현대적이고 반응형 디자인을 제공한다&lt;/li&gt;
&lt;li&gt;다크 모드를 지원한다&lt;/li&gt;
&lt;li&gt;빠른 로딩 속도와 SEO 최적화가 되어있다&lt;/li&gt;
&lt;li&gt;다국어를 지원한다&lt;/li&gt;
&lt;li&gt;풍부한 커스터마이징 옵션을 제공한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;설정 파일:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/_default/hugo.toml&lt;/code&gt; - 기본 Hugo 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/_default/params.toml&lt;/code&gt; - Blowfish 테마 파라미터&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/_default/languages.en.toml&lt;/code&gt; - 언어별 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/_default/menus.en.toml&lt;/code&gt; - 메뉴 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="마치며"&gt;&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;마치며
&lt;/h2&gt;&lt;p&gt;Tstory에서 Hugo &amp;amp; GitHub Pages로의 마이그레이션은 개발자 친화적인 환경을 위한 선택이었다. 이제는 코드 버전 관리와 동일한 방식으로 블로그를 관리할 수 있게 되었고, Obsidian과의 완벽한 연동으로 노트 작성부터 블로그 포스팅까지 하나의 워크플로우로 통합할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;무엇보다 Hugo의 빠른 빌드 속도와 GitHub Actions의 자동화된 배포는 글 작성에만 집중할 수 있게 해주었고, 더 이상 플랫폼의 제약에 얽매이지 않고 자유롭게 커스터마이징할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;앞으로는 Tstory에 있던 기존 글들을 천천히 마이그레이션하면서 새로운 콘텐츠도 꾸준히 추가해 나갈 예정이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hugo Official Site:&lt;/strong&gt; &lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
 &gt;https://gohugo.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blowfish Theme:&lt;/strong&gt; &lt;a class="link" href="https://blowfish.page/" target="_blank" rel="noopener"
 &gt;https://blowfish.page/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blowfish Creator:&lt;/strong&gt; &lt;a class="link" href="https://github.com/nunocoracao" target="_blank" rel="noopener"
 &gt;@nunocoracao&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Creator Blog:&lt;/strong&gt; &lt;a class="link" href="https://n9o.xyz/" target="_blank" rel="noopener"
 &gt;https://n9o.xyz/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Official Docs:&lt;/strong&gt; &lt;a class="link" href="https://blowfish.page/docs/" target="_blank" rel="noopener"
 &gt;https://blowfish.page/docs/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Elasticsearch Highlighting 기법</title><link>https://0andwild.com/posts/240207_es/</link><pubDate>Wed, 07 Feb 2024 21:24:29 +0900</pubDate><guid>https://0andwild.com/posts/240207_es/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch Highlighting 기법" /&gt;&lt;h2 id="1-kind-of-highlighter"&gt;&lt;a href="#1-kind-of-highlighter" class="header-anchor"&gt;&lt;/a&gt;1) Kind of Highlighter
&lt;/h2&gt;&lt;p&gt;ES에서는 검색 결과에 대한 강조 표시를 지원하고 있으며 3가지 방식을 지원을 함.&lt;/p&gt;
&lt;p&gt;아래 타입은 Highlight 할 필드마다 각각 따로 적용할 수 있음&lt;/p&gt;
&lt;h3 id="unified-default--기본-하이라이터"&gt;&lt;a href="#unified-default--%ea%b8%b0%eb%b3%b8-%ed%95%98%ec%9d%b4%eb%9d%bc%ec%9d%b4%ed%84%b0" class="header-anchor"&gt;&lt;/a&gt;Unified (Default : 기본 하이라이터)
&lt;/h3&gt;&lt;p&gt;통합 형광펜은 Lucene 통합 형광펜을 사용함. 이 하이라이터는 텍스트를 문장으로 나누고 BM25 알고리즘을 사용해 마치 말뭉치에 있는 문서처럼 개별 문장에 점수를 매김. 또한 정확한 구문 및 다중 용어(퍼지, 접두사, 정규식) 하이라이트를 지원함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BM25 알고리즘 기반 문장 점수 매기기&lt;/li&gt;
&lt;li&gt;정확한 구문 하이라이트 지원&lt;/li&gt;
&lt;li&gt;다중 용어 쿼리 지원 (퍼지, 접두사, 정규식)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="plain"&gt;&lt;a href="#plain" class="header-anchor"&gt;&lt;/a&gt;Plain
&lt;/h3&gt;&lt;p&gt;일반 형광펜은 단일 필드에서 간단한 쿼리 일치 항목을 강조 표시하는 데 가장 적합.&lt;/p&gt;
&lt;p&gt;쿼리 로직을 정확하게 반영하기 위해 작은 &lt;strong&gt;인메모리 인덱스를 생성&lt;/strong&gt;하고 Lucene의 쿼리 실행 플래너를 통해 &lt;strong&gt;원래 쿼리 기준을 다시 실행&lt;/strong&gt;하여 현재 문서에 대한 낮은 수준의 일치 정보에 액세스함함.&lt;/p&gt;
&lt;p&gt;이 작업은 &lt;strong&gt;강조 표시해야 하는 모든 필드와 모든 문서에 대해 반복&lt;/strong&gt;됨. 복잡한 쿼리가 있는 많은 문서에서 많은 필드를 강조 표시하려면 게시글 또는 term_vector 필드에 통합 형광펜을 사용하는 것이 좋음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&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;/ul&gt;
&lt;hr&gt;
&lt;h3 id="fvh-fast-vector-highlighter"&gt;&lt;a href="#fvh-fast-vector-highlighter" class="header-anchor"&gt;&lt;/a&gt;FVH (Fast Vector Highlighter)
&lt;/h3&gt;&lt;p&gt;fvh 형광펜은 Lucene 고속 벡터 형광펜을 사용함. 이 형광펜은 매핑에서 &lt;code&gt;term_vector&lt;/code&gt;가 &lt;code&gt;with_positions_offsets&lt;/code&gt;로 설정된 필드에 사용할 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boundary_scanner&lt;/code&gt;로 사용자 지정 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;term_vector&lt;/code&gt;를 &lt;code&gt;with_positions_offsets&lt;/code&gt;로 설정해야 하므로 인덱스 크기가 커짐&lt;/li&gt;
&lt;li&gt;여러 필드에서 일치하는 항목을 하나의 결과로 결합 가능 (&lt;code&gt;matched_fields&lt;/code&gt; 참조)&lt;/li&gt;
&lt;li&gt;구문 일치를 용어 일치보다 부스팅하는 부스팅 쿼리를 강조 표시할 때 구문 일치가 용어 일치보다 정렬되는 것과 같이 서로 다른 위치의 일치에 서로 다른 가중치를 할당 가능&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;주의&lt;/strong&gt;: fvh 형광펜은 스팬 쿼리를 지원하지 않음. 스팬 쿼리에 대한 지원이 필요한 경우 통합형 하이라이터와 같은 다른 하이라이터를 사용.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="2-offsets-strategy"&gt;&lt;a href="#2-offsets-strategy" class="header-anchor"&gt;&lt;/a&gt;2) Offsets Strategy
&lt;/h2&gt;&lt;p&gt;검색 중인 용어에서 의미 있는 검색 스니펫을 만들려면 하이라이터가 원본 텍스트에서 각 단어의 시작 및 끝 문자 오프셋을 알아야 함. 이러한 오프셋은 다음에서 얻을 수 있음.&lt;/p&gt;
&lt;h3 id="postings-list-글-목록"&gt;&lt;a href="#postings-list-%ea%b8%80-%eb%aa%a9%eb%a1%9d" class="header-anchor"&gt;&lt;/a&gt;Postings List (글 목록)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;매핑에서 &lt;code&gt;index_options&lt;/code&gt;가 &lt;code&gt;offsets&lt;/code&gt;로 설정&lt;/strong&gt;되어 있으면 통합 하이라이터는 이 정보를 사용하여 텍스트를 다시 분석하지 않고 문서를 강조 표시함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원래 쿼리를 글에 대해 직접 다시 실행하고 색인에서 일치하는 오프셋을 추출하여 컬렉션을 강조 표시된 문서로 제한&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;강조 표시할 텍스트를 다시 분석할 필요가 없으므로 큰 필드가 있는 경우에 유용&lt;/strong&gt;. 또한 &lt;strong&gt;&lt;code&gt;term_vectors&lt;/code&gt;를 사용할 때보다 디스크 공간도 덜 필요&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;텍스트 재분석 불필요&lt;/li&gt;
&lt;li&gt;큰 필드에 유용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;term_vectors&lt;/code&gt;보다 디스크 공간 절약&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="term-vectors-용어-벡터"&gt;&lt;a href="#term-vectors-%ec%9a%a9%ec%96%b4-%eb%b2%a1%ed%84%b0" class="header-anchor"&gt;&lt;/a&gt;Term Vectors (용어 벡터)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;매핑에서 &lt;code&gt;term_vector&lt;/code&gt;를 &lt;code&gt;with_positions_offsets&lt;/code&gt;로 설정&lt;/strong&gt;하여 용어 벡터 정보를 제공하면 &lt;strong&gt;unified 하이라이터가 자동으로 용어 벡터를 사용&lt;/strong&gt;하여 필드를 강조 표시함.&lt;/p&gt;
&lt;p&gt;각 문서의 용어 사전에 액세스할 수 있기 때문에 &lt;strong&gt;특히 대용량 필드(1MB 초과)나 접두사 또는 와일드카드&lt;/strong&gt;와 같은 다중 용어 쿼리를 강조 표시할 때 빠름.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;fvh 하이라이터는 항상 용어 벡터를 사용&lt;/strong&gt;함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대용량 필드(1MB 초과)에 빠른 성능&lt;/li&gt;
&lt;li&gt;다중 용어 쿼리에 효율적 (접두사, 와일드카드)&lt;/li&gt;
&lt;li&gt;문서의 용어 사전 직접 액세스&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="plain-highlighting-일반-하이라이팅"&gt;&lt;a href="#plain-highlighting-%ec%9d%bc%eb%b0%98-%ed%95%98%ec%9d%b4%eb%9d%bc%ec%9d%b4%ed%8c%85" class="header-anchor"&gt;&lt;/a&gt;Plain Highlighting (일반 하이라이팅)
&lt;/h3&gt;&lt;p&gt;이 모드는 &lt;strong&gt;다른 대안이 없을 때 통합에서 사용&lt;/strong&gt;함.&lt;/p&gt;
&lt;p&gt;작은 인메모리 인덱스를 생성하고 Lucene의 쿼리 실행 플래너를 통해 원래 쿼리 기준을 다시 실행하여 현재 문서에 대한 낮은 수준의 일치 정보에 액세스함함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이 작업은 강조 표시가 필요한 모든 필드와 모든 문서에 대해 반복&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;plain highlighter은 항상 plain highlighting을 사용&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&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;/ul&gt;
&lt;hr&gt;
&lt;h2 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://elastic.co/guide/en/elasticsearch/reference/current/highlighting.html#boundary-scanners" target="_blank" rel="noopener"
 &gt;Highlighting | Elasticsearch Guide [8.13]&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Elasticsearch Pagination Technique</title><link>https://0andwild.com/posts/240206_es/</link><pubDate>Tue, 06 Feb 2024 21:16:24 +0900</pubDate><guid>https://0andwild.com/posts/240206_es/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch Pagination Technique" /&gt;&lt;h2 id="elasticsearch-pagination-3가지-옵션"&gt;&lt;a href="#elasticsearch-pagination-3%ea%b0%80%ec%a7%80-%ec%98%b5%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;ElasticSearch Pagination 3가지 옵션
&lt;/h2&gt;&lt;h3 id="1-fromsize-pagination"&gt;&lt;a href="#1-fromsize-pagination" class="header-anchor"&gt;&lt;/a&gt;1. From/Size Pagination
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;from&lt;/code&gt; + &lt;code&gt;size&lt;/code&gt; = offset 방식으로 그때그때 메모리에 적재하여 검색을 함. 최대 10,000건까지 지원&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules.html#index-max-result-window" target="_blank" rel="noopener"
 &gt;&lt;code&gt;index.max_result_window&lt;/code&gt;&lt;/a&gt; 옵션을 사용하여 10,000건 이상을 로드할 수 있지만 추천하지 않음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;간단한 구현&lt;/li&gt;
&lt;li&gt;최대 10,000건 제한&lt;/li&gt;
&lt;li&gt;Deep pagination 시 성능 저하&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="2-search-after"&gt;&lt;a href="#2-search-after" class="header-anchor"&gt;&lt;/a&gt;2. Search After
&lt;/h3&gt;&lt;p&gt;Pagination이 최대 10,000건까지 페이징 처리할 수 있는 한계를 극복할 수 있음. 일반적인 커서 방식과 유사함.&lt;/p&gt;
&lt;p&gt;검색 결과의 &lt;code&gt;sort&lt;/code&gt; 조건 필드를 키 값으로 사용하며 그 다음부터 조회를 함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;search-after&lt;/code&gt;만 사용할 경우 중간에 인덱싱이 업데이트되거나 하면 응답 결과가 불규칙해질 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PIT(Point In Time)와 함께 사용&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이를 보완하기 위해 PIT(Point In Time)를 사용함&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;POST /my-index-000001/_pit?keep_alive&lt;span class="o"&gt;=&lt;/span&gt;1m
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;위 요청을 통해 해당 인덱스의 현재 시점의 스냅샷을 찍어두고 다음과 같이 &lt;code&gt;id&lt;/code&gt; 값으로 사용함&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sort&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_sort&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;desc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;search_after&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{{pit_value}}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;중간에 인덱스에 변경사항이 있어도 스냅샷을 기준으로 응답 결과가 반환됨.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;keep_alive&lt;/code&gt;는 해당 PIT의 유효기간과 같음. 최신 시점을 기준으로 PIT를 관리하는 것이 권장됨.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10,000건 이상 페이징 가능&lt;/li&gt;
&lt;li&gt;커서 기반 방식&lt;/li&gt;
&lt;li&gt;PIT와 함께 사용 시 일관된 결과 보장&lt;/li&gt;
&lt;li&gt;Deep pagination에 효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="3-scroll"&gt;&lt;a href="#3-scroll" class="header-anchor"&gt;&lt;/a&gt;3. Scroll
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: ES 공식 문서에 따라 Scroll 방식 대신 Search After 방식을 권장함&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대량 데이터 추출용&lt;/li&gt;
&lt;li&gt;실시간 검색에는 부적합&lt;/li&gt;
&lt;li&gt;현재는 Search After + PIT 조합을 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://opster.com/guides/elasticsearch/how-tos/elasticsearch-pagination-techniques/" target="_blank" rel="noopener"
 &gt;Elasticsearch Pagination Techniques: SearchAfter, Scroll, Pagination &amp;amp; PIT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://danawalab.github.io/elastic/2023/02/13/Search-After.html" target="_blank" rel="noopener"
 &gt;Elasticsearch Search After 성능 체크&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#search-after" target="_blank" rel="noopener"
 &gt;Paginate search results | Elasticsearch Guide [7.17]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html" target="_blank" rel="noopener"
 &gt;Point in time API | Elasticsearch Guide [7.17]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/scroll-api.html" target="_blank" rel="noopener"
 &gt;Scroll API | Elasticsearch Guide [7.17]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Elasticsearch 자동완성 검색 처리 방법</title><link>https://0andwild.com/posts/240204_es/</link><pubDate>Sun, 04 Feb 2024 20:59:35 +0900</pubDate><guid>https://0andwild.com/posts/240204_es/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch 자동완성 검색 처리 방법" /&gt;&lt;h2 id="elastic-search-자동완성-처리-방법"&gt;&lt;a href="#elastic-search-%ec%9e%90%eb%8f%99%ec%99%84%ec%84%b1-%ec%b2%98%eb%a6%ac-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;Elastic search 자동완성 처리 방법
&lt;/h2&gt;&lt;h3 id="edge-n-gram-tokenizer"&gt;&lt;a href="#edge-n-gram-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Edge N-Gram Tokenizer
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;min_gram&lt;/code&gt;: 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_gram&lt;/code&gt;: 10&lt;/li&gt;
&lt;li&gt;&lt;code&gt;token_chars&lt;/code&gt;: letter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;적절한 사용처&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;용어의 순서가 중요하지 않은 곳&lt;/li&gt;
&lt;li&gt;토큰의 시작점 및 위치가 중요하지 않은 곳&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="edge-n-gram-token-filter"&gt;&lt;a href="#edge-n-gram-token-filter" class="header-anchor"&gt;&lt;/a&gt;Edge N-Gram Token Filter
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;min_gram&lt;/code&gt;: 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_gram&lt;/code&gt;: 10&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;적절한 사용처&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;용어의 순서가 중요하지 않은 곳&lt;/li&gt;
&lt;li&gt;토큰의 시작점 및 위치가 중요하지 않은 곳&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="index_prefixes-parameter"&gt;&lt;a href="#index_prefixes-parameter" class="header-anchor"&gt;&lt;/a&gt;Index_prefixes Parameter
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;min_chars&lt;/code&gt;: 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_chars&lt;/code&gt;: 10&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;적절한 사용처&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;N-gram과 동일함&lt;/p&gt;
&lt;p&gt;다만 한가지 차이점은 후자의 경우 생성된 토큰을 넣는 추가적인 필드를 넣는다는 것&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="search-as-you-type-data-type"&gt;&lt;a href="#search-as-you-type-data-type" class="header-anchor"&gt;&lt;/a&gt;Search-as-you-type Data Type
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_shingle_size&lt;/code&gt;: 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Generated Tokens (지원하는 서브 필드)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예시: &amp;ldquo;real panda blog&amp;rdquo;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;._2gram&lt;/code&gt; additional field: real panda, panda blog (shingle token filter 적용됨)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;._3gram&lt;/code&gt; additional field: real panda blog (shingle token filter 적용됨)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;._index_prefix&lt;/code&gt; additional field: r, re, rea, real, &amp;ldquo;real &amp;ldquo;, real p, real pa, real pan, real pand, real panda, &amp;ldquo;real panda &amp;ldquo;, real panda b, real panda bl, real panda blo, real panda blog, p, pa, pan, pand, panda, &amp;ldquo;panda &amp;ldquo;, panda b, panda bl, panda blo, panda blog, &amp;ldquo;panda blog &amp;ldquo;, b, bl, blo, blog, &amp;ldquo;blog &amp;quot; (&lt;strong&gt;._3gram 필드에 적용이 되고 n gram max 는 3으로 적용된다&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;ES에서 말하는 가장 효율적인 쿼리 방법은 루트 필드와 해당 shingle 하위 필드를 대상으로 하는 bool_prefix 타입의 다중 일치 쿼리이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 쿼리는 어떤 순서로든 쿼리 용어와 일치할 수 있지만, 문서에서 shingle 서브 필드에 순서대로 용어가 포함된 경우 더 높은 점수를 부여한다.&lt;/p&gt;
&lt;p&gt;쿼리 용어와 문서의 용어가 정확하게 순서대로 일치하게 검색을 하거나 구문 쿼리의 다른 속성을 사용하려면 루트 필드에 match_phrase_prefix 쿼리를 사용할 수 있다. 접두사가 아닌 마지막 용어가 정확하게 일치해야 하는 경우도 그렇다. 하지만 match_bool_prefix 쿼리를 사용하는 것 보다 효율성이 떨어질 수 있다.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;shingle token filter 는 default 로 2가 적용됨&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;적절한 사용처&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;용어의 순서가 중요한 곳&lt;/li&gt;
&lt;li&gt;토큰의 시작점과 위치가 중요한 곳&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;인덱싱을 할때 설정된 analyzer 가 없으면 default 로 standard analyzer 가 적용된다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="suggester-api"&gt;&lt;a href="#suggester-api" class="header-anchor"&gt;&lt;/a&gt;Suggester API
&lt;/h2&gt;&lt;h3 id="in-memory-completion-suggester-context-suggester"&gt;&lt;a href="#in-memory-completion-suggester-context-suggester" class="header-anchor"&gt;&lt;/a&gt;In-memory (Completion Suggester, Context Suggester)
&lt;/h3&gt;&lt;p&gt;completion suggester는 auto-complete과 search-as-your-type 기능을 제공함. (오타 수정은 지원 x)&lt;/p&gt;
&lt;p&gt;completion suggester는 속도에 최적화 되어 있어 유저가 타이핑 하는 것에 즉각적으로 반응해줌.&lt;/p&gt;
&lt;p&gt;하지만 빌드와 인메모리 방식에 저장을 하는데 있어 많은 리소스 비용이 부담됨.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="term-suggester"&gt;&lt;a href="#term-suggester" class="header-anchor"&gt;&lt;/a&gt;Term Suggester
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;사용자가 입력한 텍스트에 대한 결과가 없을경우 추천 단어를 기준으로 검색결과를 제공할때 사용&lt;/strong&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;예를들어 tamming test 문자열을 taming text로 바꾸는데 m을 삭제하는 연산 1회와 s를 x로 바꾸는 연산 1회가 필요하다. 그러므로 편집거리는 2가 된다.&lt;/p&gt;
&lt;p&gt;만약 색인된 데이터와 일치하는 텀이 존재하지 않을 경우 term suggest 결과로 비슷한 단어를 추천해준다.&lt;/p&gt;
&lt;p&gt;결과에서 text는 제안한 문자를 나타내고, score는 제안하고자 하는 텍스트가 원본과 얼마나 가까운지를 나타낸다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;알고리즘&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;엘라스틱 서치에서는 편집거리 계산 알고리즘으로 Levenshtein 편집거리 측정 또는 Jaro-Winkler 편집거리 측정을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;한글 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한글의 경우 term suggest를 이용해도 데이터가 추천되지 않는다. 기본적으로 한글 유니코드 체계가 복잡하기 때문이다.&lt;/li&gt;
&lt;li&gt;ICU 분석기를 통해 한글 오타를 처리하는 것이 가능. ICU 분석기는 국제화 처리를 위해 특별히 개발된 분석기로 내부에 한글 자소를 분해하는 기능과 합치는 기능을 가지고 있음. 하지만 정교한 오타교정, 한영 변환, 자동완성 등의 전문적인 기능은 별도의 플러그인을 개발해서 사용하는 것이 좋다. (Ex. 자바카페 플러그인)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="phrase-suggester-추천-문장-제안"&gt;&lt;a href="#phrase-suggester-%ec%b6%94%ec%b2%9c-%eb%ac%b8%ec%9e%a5-%ec%a0%9c%ec%95%88" class="header-anchor"&gt;&lt;/a&gt;Phrase Suggester (추천 문장 제안)
&lt;/h3&gt;&lt;h4 id="completion-suggester"&gt;&lt;a href="#completion-suggester" class="header-anchor"&gt;&lt;/a&gt;Completion Suggester
&lt;/h4&gt;&lt;p&gt;자동완성 제안, 사용자가 입력을 완료하기 전에 자동완성을 사용해 검색어를 예측해서 보여줌&lt;/p&gt;
&lt;h4 id="context-suggester"&gt;&lt;a href="#context-suggester" class="header-anchor"&gt;&lt;/a&gt;Context Suggester
&lt;/h4&gt;&lt;p&gt;추천 문맥 제안&lt;/p&gt;</description></item><item><title>Elasticsearch Query 처리 순서</title><link>https://0andwild.com/posts/240203_es_query/</link><pubDate>Sat, 03 Feb 2024 21:45:06 +0900</pubDate><guid>https://0andwild.com/posts/240203_es_query/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch Query 처리 순서" /&gt;&lt;h1 id="elasticsearch-query-처리-순서"&gt;&lt;a href="#elasticsearch-query-%ec%b2%98%eb%a6%ac-%ec%88%9c%ec%84%9c" class="header-anchor"&gt;&lt;/a&gt;Elasticsearch Query 처리 순서
&lt;/h1&gt;&lt;h2 id="summary"&gt;&lt;a href="#summary" class="header-anchor"&gt;&lt;/a&gt;Summary
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Filtered Query&lt;/strong&gt;는 최종 search와 aggregation 둘 다의 결과에 영향을 미치지만, &lt;strong&gt;PostFiltered Query&lt;/strong&gt;는 최종 search 결과에만 영향을 미치고 aggregation에는 영향을 미치지 않는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="query-처리-순서"&gt;&lt;a href="#query-%ec%b2%98%eb%a6%ac-%ec%88%9c%ec%84%9c" class="header-anchor"&gt;&lt;/a&gt;Query 처리 순서
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;SearchRequest → (Filtered) → Query → (PostFilter) → Result → RescoreQuery
 ↓
 Aggregation → AggregationResult
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="804px" data-flex-grow="335" height="191" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/240203_es_query/sequence1.jpg" width="640"&gt;&lt;/p&gt;
&lt;h3 id="filter-query-예시"&gt;&lt;a href="#filter-query-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;Filter Query 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;filtered&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;denver&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Filter Query는 검색 결과와 Aggregation 모두에 영향을 미친다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="882px" data-flex-grow="367" height="174" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/240203_es_query/sequence2.jpg" width="640"&gt;&lt;/p&gt;
&lt;h3 id="postfilter-query-예시"&gt;&lt;a href="#postfilter-query-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;PostFilter Query 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;post_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;denver&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PostFilter Query는 검색 결과에만 영향을 미치고, Aggregation 결과에는 영향을 미치지 않는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="rescore-query-파라미터"&gt;&lt;a href="#rescore-query-%ed%8c%8c%eb%9d%bc%eb%af%b8%ed%84%b0" class="header-anchor"&gt;&lt;/a&gt;Rescore Query 파라미터
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;window_size&lt;/strong&gt;: 각 shard에서 재점수화할 상위 결과의 수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;score_mode&lt;/strong&gt;: main query score와 rescore query의 점수를 결합하는 방식&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Elasticsearch Token Filter</title><link>https://0andwild.com/posts/240202_es_analyzer3/</link><pubDate>Fri, 02 Feb 2024 21:30:06 +0900</pubDate><guid>https://0andwild.com/posts/240202_es_analyzer3/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch Token Filter" /&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="598px" data-flex-grow="249" data-title-escaped="CharFilter → Tokenizer → TokenFilter 순으로 진행" height="614" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/240202_es_analyzer3/sequence.png" srcset="https://0andwild.com/posts/240202_es_analyzer3/sequence_hu_fc41f98e8e2a0d34.png 800w, https://0andwild.com/posts/240202_es_analyzer3/sequence.png 1530w" title="CharFilter → Tokenizer → TokenFilter 순으로 진행" width="1530"&gt;&lt;/p&gt;
&lt;h1 id="token-filter"&gt;&lt;a href="#token-filter" class="header-anchor"&gt;&lt;/a&gt;Token Filter
&lt;/h1&gt;&lt;p&gt;Token Filter는 Tokenizer에서 생성된 토큰 스트림을 받아 토큰을 추가, 제거 또는 변경하는 역할을 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="word-delimiter-graph-filter"&gt;&lt;a href="#word-delimiter-graph-filter" class="header-anchor"&gt;&lt;/a&gt;Word Delimiter Graph Filter
&lt;/h2&gt;&lt;p&gt;Word delimiter graph 필터는 제품 ID나 부품 번호와 같은 복잡한 식별자에서 문장 부호를 제거하도록 설계되었다. 이러한 사용 사례의 경우 &lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/analysis-keyword-tokenizer.html" target="_blank" rel="noopener"
 &gt;keyword tokenizer&lt;/a&gt;와 함께 사용하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;wi-fi와 같은 하이픈으로 연결된 단어를 분리할 때는 word delimiter graph 필터를 사용하지 않는 것이 좋다. 사용자들은 하이픈을 포함하기도 하고 포함하지 않고도 검색을 하기 때문에 &lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/analysis-synonym-graph-tokenfilter.html" target="_blank" rel="noopener"
 &gt;synonym graph&lt;/a&gt; 필터를 사용하는 것이 좋다.&lt;/p&gt;
&lt;h3 id="변환-규칙"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ea%b7%9c%ec%b9%99" class="header-anchor"&gt;&lt;/a&gt;변환 규칙
&lt;/h3&gt;&lt;p&gt;다음과 같은 방식으로 토큰을 분할한다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;비영숫자 문자로 토큰 분할&lt;/strong&gt;: &lt;code&gt;Super-Duper&lt;/code&gt; → &lt;code&gt;Super, Duper&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;앞뒤 구분 기호 제거&lt;/strong&gt;: &lt;code&gt;XL---42+'Autocoder'&lt;/code&gt; → &lt;code&gt;XL, 42, Autocoder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대소문자 전환 시 분할&lt;/strong&gt;: &lt;code&gt;PowerShot&lt;/code&gt; → &lt;code&gt;Power, Shot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문자-숫자 전환 시 분할&lt;/strong&gt;: &lt;code&gt;XL500&lt;/code&gt; → &lt;code&gt;XL, 500&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;영어 소유격 제거&lt;/strong&gt;: &lt;code&gt;Neil's&lt;/code&gt; → &lt;code&gt;Neil&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="api-사용-예시"&gt;&lt;a href="#api-%ec%82%ac%ec%9a%a9-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;API 사용 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;/_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;keyword&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;word_delimiter_graph&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Neil&amp;#39;s-Super-Duper-XL500--42+AutoCoder&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ Neil, Super, Duper, XL, 500, 42, Auto, Coder ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="custom-analyzer-설정"&gt;&lt;a href="#custom-analyzer-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;Custom Analyzer 설정
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;/my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analysis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;keyword&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;word_delimiter_graph&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="configurable-parameters"&gt;&lt;a href="#configurable-parameters" class="header-anchor"&gt;&lt;/a&gt;Configurable Parameters
&lt;/h3&gt;&lt;h4 id="adjust_offsets"&gt;&lt;a href="#adjust_offsets" class="header-anchor"&gt;&lt;/a&gt;adjust_offsets
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true일 경우 필터는 토큰 스트림에서 실제 위치를 더 잘 반영하도록 분할 토큰 또는 체이닝된 토큰의 시작점을 조정함&lt;/li&gt;
&lt;li&gt;만약 trim 같은 필터를 사용한다면 offset의 변화 없이 token의 길이를 변경하기 때문에 함께 사용할 때는 &lt;code&gt;false&lt;/code&gt;로 해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="catenate_all"&gt;&lt;a href="#catenate_all" class="header-anchor"&gt;&lt;/a&gt;catenate_all
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 영숫자가 아닌 구분 기호로 구분된 영숫자 체인에 대해 체이닝 토큰을 생성함&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;super-duper-xl-500&lt;/code&gt; → &lt;code&gt;[ superduperxl500, super, duper, xl, 500 ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="catenate_numbers"&gt;&lt;a href="#catenate_numbers" class="header-anchor"&gt;&lt;/a&gt;catenate_numbers
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 알파벳이 아닌 구분 기호로 구분된 숫자 문자 체인에 대해 카테네이트 토큰을 생성함&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;01-02-03&lt;/code&gt; → &lt;code&gt;[ 010203, 01, 02, 03 ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="catenate_words"&gt;&lt;a href="#catenate_words" class="header-anchor"&gt;&lt;/a&gt;catenate_words
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 알파벳이 아닌 구분 기호로 구분된 알파벳 문자 체인에 대해 카테네이트 토큰을 생성함&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;super-duper-xl&lt;/code&gt; → &lt;code&gt;[ superduperxl, super, duper, xl ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;⚠️ &lt;strong&gt;Catenate 파라미터 사용 시 주의점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 매개변수를 true로 설정하면 인덱싱에서 지원되지 않는 다중 위치 토큰이 생성함&lt;/p&gt;
&lt;p&gt;이 매개변수가 true이면 인덱스 분석기에서 이 필터를 사용하지 않거나 이 필터 뒤에 flatten_graph 필터를 사용하여 토큰 스트림을 인덱싱에 적합하게 만들어야함&lt;/p&gt;
&lt;p&gt;검색 분석에 사용할 경우, 카테네이트된 토큰은 match_phrase 쿼리 및 일치하는 토큰 위치에 의존하는 다른 쿼리에 문제를 일으킬 수 있음. 이러한 쿼리를 사용하려는 경우 이 매개변수를 true로 설정하지 않아야 함.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="generate_number_parts"&gt;&lt;a href="#generate_number_parts" class="header-anchor"&gt;&lt;/a&gt;generate_number_parts
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 출력에 숫자로 구성된 토큰을 포함함&lt;/li&gt;
&lt;li&gt;false인 경우 필터는 이러한 토큰을 출력에서 제외함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="generate_word_parts"&gt;&lt;a href="#generate_word_parts" class="header-anchor"&gt;&lt;/a&gt;generate_word_parts
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 출력에 알파벳 문자로 구성된 토큰을 포함함&lt;/li&gt;
&lt;li&gt;false일 경우 이러한 토큰을 출력에서 제외함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ignore_keywords"&gt;&lt;a href="#ignore_keywords" class="header-anchor"&gt;&lt;/a&gt;ignore_keywords
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true일 경우 필터는 키워드 속성이 true인 토큰을 건너뜀&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="preserve_original"&gt;&lt;a href="#preserve_original" class="header-anchor"&gt;&lt;/a&gt;preserve_original
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 출력에 분할된 토큰의 원본 버전을 포함함&lt;/li&gt;
&lt;li&gt;이 원본 버전에는 영숫자가 아닌 구분 기호가 포함됨&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;super-duper-xl-500&lt;/code&gt; → &lt;code&gt;[ super-duper-xl-500, super, duper, xl, 500 ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;⚠️ &lt;strong&gt;preserve_original 파라미터 사용 시 주의점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 매개변수를 true로 설정하면 인덱싱에서 지원되지 않는 다중 위치 토큰이 생성함&lt;/p&gt;
&lt;p&gt;이 매개변수가 true이면 인덱스 분석기에서 이 필터를 사용하지 않거나 이 필터 뒤에 flatten_graph 필터를 사용하여 토큰 스트림을 인덱싱에 적합하게 만들어야함&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="protected_words"&gt;&lt;a href="#protected_words" class="header-anchor"&gt;&lt;/a&gt;protected_words
&lt;/h4&gt;&lt;p&gt;(Optional, array of strings)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;필터가 분할하지 않는 토큰의 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="protected_words_path"&gt;&lt;a href="#protected_words_path" class="header-anchor"&gt;&lt;/a&gt;protected_words_path
&lt;/h4&gt;&lt;p&gt;(Optional, string)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;필터가 분할하지 않는 토큰 목록이 포함된 파일의 경로&lt;/li&gt;
&lt;li&gt;이 경로는 구성 위치에 대한 절대 경로이거나 상대 경로여야 하며, 파일은 UTF-8로 인코딩되어야 함&lt;/li&gt;
&lt;li&gt;파일의 각 토큰은 줄 바꿈으로 구분해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="split_on_case_change"&gt;&lt;a href="#split_on_case_change" class="header-anchor"&gt;&lt;/a&gt;split_on_case_change
&lt;/h4&gt;&lt;p&gt;(Optional, Boolean)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 대소문자 전환 시 토큰을 분할함&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;camelCase&lt;/code&gt; → &lt;code&gt;[ camel, Case ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="split_on_numerics"&gt;&lt;a href="#split_on_numerics" class="header-anchor"&gt;&lt;/a&gt;split_on_numerics
&lt;/h4&gt;&lt;p&gt;(Optional, Boolean)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 문자-숫자 전환 시 토큰을 분할함&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;j2se&lt;/code&gt; → &lt;code&gt;[ j, 2, se ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="stem_english_possessive"&gt;&lt;a href="#stem_english_possessive" class="header-anchor"&gt;&lt;/a&gt;stem_english_possessive
&lt;/h4&gt;&lt;p&gt;(Optional, Boolean)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default: &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true이면 필터는 각 토큰의 끝에서 영어 소유격(&amp;rsquo;s)을 제거함&lt;/li&gt;
&lt;li&gt;예시: &lt;code&gt;O'Neil's&lt;/code&gt; → &lt;code&gt;[ O, Neil ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="type_table"&gt;&lt;a href="#type_table" class="header-anchor"&gt;&lt;/a&gt;type_table
&lt;/h4&gt;&lt;p&gt;(Optional, array of strings)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문자에 대한 사용자 지정 유형 매핑의 배열&lt;/li&gt;
&lt;li&gt;이를 통해 영숫자가 아닌 문자를 숫자 또는 영숫자로 매핑하여 해당 문자의 분할을 방지할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;+ =&amp;gt; ALPHA&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;- =&amp;gt; ALPHA&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;위 배열은 더하기(+) 및 하이픈(-) 문자를 영숫자로 매핑하므로 구분 기호로 취급되지 않음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지원되는 타입&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ALPHA&lt;/code&gt; (Alphabetical)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALPHANUM&lt;/code&gt; (Alphanumeric)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DIGIT&lt;/code&gt; (Numeric)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOWER&lt;/code&gt; (Lowercase alphabetical)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SUBWORD_DELIM&lt;/code&gt; (Non-alphanumeric delimiter)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UPPER&lt;/code&gt; (Uppercase alphabetical)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="type_table_path"&gt;&lt;a href="#type_table_path" class="header-anchor"&gt;&lt;/a&gt;type_table_path
&lt;/h4&gt;&lt;p&gt;(Optional, string)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;custom type mapping 파일 경로&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;작성 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Map the $, %, &amp;#39;.&amp;#39;, and &amp;#39;,&amp;#39; characters to DIGIT
# This might be useful for financial data.
$ =&amp;gt; DIGIT
% =&amp;gt; DIGIT
. =&amp;gt; DIGIT
\u002C =&amp;gt; DIGIT

# in some cases you might not want to split on ZWJ
# this also tests the case where we need a bigger byte[]
# see https://en.wikipedia.org/wiki/Zero-width_joiner
\u200D =&amp;gt; ALPHANUM
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 파일 경로는 설정 위치에 대한 절대 경로이거나 상대 경로여야 하며, 파일은 UTF-8로 인코딩되어야 함. 파일의 각 매핑은 줄 바꿈으로 구분함.&lt;/p&gt;
&lt;h3 id="사용-시-주의사항"&gt;&lt;a href="#%ec%82%ac%ec%9a%a9-%ec%8b%9c-%ec%a3%bc%ec%9d%98%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;사용 시 주의사항
&lt;/h3&gt;&lt;p&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/analysis-standard-tokenizer.html" target="_blank" rel="noopener"
 &gt;Standard tokenizer&lt;/a&gt;와 같이 구두점을 제거하는 토큰화기와 함께 word_delimiter_graph 필터를 사용하지 않는 것이 좋다. 이렇게 하면 word_delimiter_graph 필터가 토큰을 올바르게 분할하지 못할 수 있다.&lt;/p&gt;
&lt;p&gt;또한 catenate_all 또는 preserve_original과 같은 필터의 구성 가능한 매개변수를 방해할 수도 있다. 대신 &lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/analysis-keyword-tokenizer.html" target="_blank" rel="noopener"
 &gt;keyword&lt;/a&gt; 또는 &lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/analysis-whitespace-tokenizer.html" target="_blank" rel="noopener"
 &gt;whitespace tokenizer&lt;/a&gt;를 사용하는 것이 좋다.&lt;/p&gt;</description></item><item><title>Elasticsearch Tokenizer</title><link>https://0andwild.com/posts/240202_es_analyzer2/</link><pubDate>Fri, 02 Feb 2024 21:23:06 +0900</pubDate><guid>https://0andwild.com/posts/240202_es_analyzer2/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch Tokenizer" /&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="598px" data-flex-grow="249" data-title-escaped="CharFilter → Tokenizer → TokenFilter 순으로 진행" height="614" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/240202_es_analyzer2/sequence.png" srcset="https://0andwild.com/posts/240202_es_analyzer2/sequence_hu_fc41f98e8e2a0d34.png 800w, https://0andwild.com/posts/240202_es_analyzer2/sequence.png 1530w" title="CharFilter → Tokenizer → TokenFilter 순으로 진행" width="1530"&gt;&lt;/p&gt;
&lt;h1 id="tokenizer"&gt;&lt;a href="#tokenizer" class="header-anchor"&gt;&lt;/a&gt;Tokenizer
&lt;/h1&gt;&lt;p&gt;Tokenizer는 문자열 스트림을 받고 각각의 토큰 단위로 자른다. (주로 각각의 단어별로 토큰화가 이루어짐)&lt;/p&gt;
&lt;p&gt;가장 standard하게 사용되는 &lt;code&gt;whitespace&lt;/code&gt; tokenizer는 공백을 기준으로 잘라 토큰화를 진행한다.&lt;/p&gt;
&lt;h3 id="whitespace-tokenizer-예시"&gt;&lt;a href="#whitespace-tokenizer-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;Whitespace Tokenizer 예시
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;// character streams
Quick brown fox!

// Result
[Quick, brown, fox!]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="tokenizer의-책임"&gt;&lt;a href="#tokenizer%ec%9d%98-%ec%b1%85%ec%9e%84" class="header-anchor"&gt;&lt;/a&gt;Tokenizer의 책임
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;각 용어의 정렬과 포지션 (구 또는 단어 유사성 쿼리에서 사용)&lt;/li&gt;
&lt;li&gt;변형 전의 기존 단어의 시작과 끝 문자는 검색 스니펫 하이라이팅에 사용됨&lt;/li&gt;
&lt;li&gt;Token별 타입: &lt;code&gt;&amp;lt;ALPHANUM&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;HANGUL&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;NUM&amp;gt;&lt;/code&gt; 등 (단순한 analyzer는 오직 단어 토큰 타입만 제공함)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="word-oriented-tokenizer"&gt;&lt;a href="#word-oriented-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Word Oriented Tokenizer
&lt;/h2&gt;&lt;p&gt;아래 tokenizer들은 풀 텍스트에서 각각의 단어로 토큰화를 하는데 사용된다.&lt;/p&gt;
&lt;h3 id="standard-tokenizer"&gt;&lt;a href="#standard-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Standard Tokenizer
&lt;/h3&gt;&lt;p&gt;standard tokenizer는 Unicode Text Segmentation algorithm을 기반으로 토큰화를 진행한다.&lt;/p&gt;
&lt;h4 id="configuration"&gt;&lt;a href="#configuration" class="header-anchor"&gt;&lt;/a&gt;Configuration
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_token_length&lt;/code&gt;: 지정된 길이를 초과하는 문자열을 기점으로 잘라 토큰화를 진행&lt;/li&gt;
&lt;li&gt;Default = 255&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="변환-예시"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The 2 QUICK Brown-Foxes jumped over the lazy dog&amp;#39;s bone.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog&amp;#39;s, bone ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="max_token_length-적용-예시"&gt;&lt;a href="#max_token_length-%ec%a0%81%ec%9a%a9-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;max_token_length 적용 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analysis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my_tokenizer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;max_token_length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;&lt;span class="err"&gt;/_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The 2 QUICK Brown-Foxes jumped over the lazy dog&amp;#39;s bone.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ The, 2, QUICK, Brown, Foxes, jumpe, d, over, the, lazy, dog&amp;#39;s, bone ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="letter-tokenizer"&gt;&lt;a href="#letter-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Letter Tokenizer
&lt;/h3&gt;&lt;p&gt;letter tokenizer는 문자열이 아닌 것을 기점으로 잘라 토큰화를 진행한다. 이는 유럽권 언어(영미권)에는 적합하나 아시아 언어, 특히 공백을 기점으로 단어가 나뉘지 않는 언어에는 적합하지 않다.&lt;/p&gt;
&lt;h4 id="변환-예시-1"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-1" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;letter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The 2 QUICK Brown-Foxes jumped over the lazy dog&amp;#39;s bone.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ The, QUICK, Brown, Foxes, jumped, over, the, lazy, dog, s, bone ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="lowercase-tokenizer"&gt;&lt;a href="#lowercase-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Lowercase Tokenizer
&lt;/h3&gt;&lt;p&gt;lowercase tokenizer는 letter tokenizer처럼 문자열이 아닌 것을 기점으로 잘라 토큰화를 진행하며, 추가적으로 모든 문자열을 소문자로 변환한다. 기능적으로는 letter tokenizer와 동일하면서도 소문자로 변환하는 기능을 한번에 수행함으로 효율적이다.&lt;/p&gt;
&lt;h4 id="변환-예시-2"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-2" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lowercase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The 2 QUICK Brown-Foxes jumped over the lazy dog&amp;#39;s bone.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="whitespace-tokenizer"&gt;&lt;a href="#whitespace-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Whitespace Tokenizer
&lt;/h3&gt;&lt;p&gt;whitespace tokenizer는 공백 문자열을 기준으로 토큰화를 진행한다.&lt;/p&gt;
&lt;h4 id="configuration-1"&gt;&lt;a href="#configuration-1" class="header-anchor"&gt;&lt;/a&gt;Configuration
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_token_length&lt;/code&gt;: 지정된 길이를 초과하는 문자열을 기점으로 잘라 토큰화를 진행&lt;/li&gt;
&lt;li&gt;Default = 255&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="변환-예시-3"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-3" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;whitespace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The 2 QUICK Brown-Foxes jumped over the lazy dog&amp;#39;s bone.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog&amp;#39;s, bone. ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="uax-url-email-tokenizer"&gt;&lt;a href="#uax-url-email-tokenizer" class="header-anchor"&gt;&lt;/a&gt;UAX URL Email Tokenizer
&lt;/h3&gt;&lt;p&gt;uax_url_email tokenizer는 standard tokenizer와 동일하지만, 한 가지 다른점은 URLs이나 email 주소를 인식하고 하나의 토큰으로 취급을 한다.&lt;/p&gt;
&lt;h4 id="configuration-2"&gt;&lt;a href="#configuration-2" class="header-anchor"&gt;&lt;/a&gt;Configuration
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_token_length&lt;/code&gt;: 지정된 길이를 초과하는 문자열을 기점으로 잘라 토큰화를 진행&lt;/li&gt;
&lt;li&gt;Default = 255&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="변환-예시-4"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-4" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;uax_url_email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Email me at john.smith@global-international.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ Email, me, at, john.smith@global-international.com ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 위의 예시를 만약 standard tokenizer로 한다면 다음과 같은 결과가 나옴
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ Email, me, at, john.smith, global, international.com ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="configuration-예시"&gt;&lt;a href="#configuration-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;Configuration 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analysis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my_tokenizer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;uax_url_email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;max_token_length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;&lt;span class="err"&gt;/_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;john.smith@global-international.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result (email 형식을 무시하고 max_token_length가 우선순위가 됨)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// [ john, smith, globa, l, inter, natio, nal.c, om ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="classic-tokenizer"&gt;&lt;a href="#classic-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Classic Tokenizer
&lt;/h3&gt;&lt;p&gt;classic tokenizer는 문법 베이스의 토큰화를 진행하고 영어 document에 좋다. 이 토큰화 방식은 약어, 회사 이름, 이메일 주소, 인터넷 호스트 이름을 특별하게 처리하는 방식이 있다. 그러나 이러한 규칙들이 항상 동작하지는 않고, 영어 이외의 언어에서는 잘 동작하지 않는다.&lt;/p&gt;
&lt;h4 id="tokenizing-규칙"&gt;&lt;a href="#tokenizing-%ea%b7%9c%ec%b9%99" class="header-anchor"&gt;&lt;/a&gt;Tokenizing 규칙
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;대부분 구두점을 기준으로, 구두점을 제거하며 단어를 나눈다. 하지만 공백이 뒤따르지 않는 점은 토큰의 일부로 간주된다.&lt;/li&gt;
&lt;li&gt;하이픈을 기준으로 단어를 나누긴 하지만 만약 해당 단어에 하이픈이 있을 경우 제품번호로 인식을 하고 나누지 않는다. (예: 123-23)&lt;/li&gt;
&lt;li&gt;email과 internet hostname은 하나의 토큰으로 간주한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="configuration-3"&gt;&lt;a href="#configuration-3" class="header-anchor"&gt;&lt;/a&gt;Configuration
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_token_length&lt;/code&gt;: 지정된 길이를 초과하는 문자열을 기점으로 잘라 토큰화를 진행&lt;/li&gt;
&lt;li&gt;Default = 255&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="변환-예시-5"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-5" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;classic&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The 2 QUICK Brown-Foxes jumped over the lazy dog&amp;#39;s bone.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog&amp;#39;s, bone ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="thai-tokenizer"&gt;&lt;a href="#thai-tokenizer" class="header-anchor"&gt;&lt;/a&gt;Thai Tokenizer
&lt;/h3&gt;&lt;p&gt;thai tokenizer는 태국어 텍스트를 단어 단위로 토큰화를 시킨다. Java에 포함되어 있는 Thai segmentation algorithm을 사용한다. 만약 input text에 태국어가 아닌 다른 언어의 문자열이 함께 포함되어 있는 경우는 해당 문자열에 한해 standard tokenizer가 적용된다.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;⚠️ &lt;strong&gt;주의사항&lt;/strong&gt;: 해당 토큰화 방식은 일부 JRE에서 지원되지 않을 수 있음. 이 토큰화 방식은 Sun/Oracle 및 OpenJDK에서 작동하는 것으로 알려져 있음. 어플리케이션에 완전한 이식성을 고려하는 경우 &lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/plugins/8.8/analysis-icu-tokenizer.html" target="_blank" rel="noopener"
 &gt;ICU tokenizer&lt;/a&gt;를 대신 사용하는 것을 고려하는 것이 좋음&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="변환-예시-6"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-6" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;thai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;การที่ได้ต้องแสดงว่างานดี&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ การ, ที่, ได้, ต้อง, แสดง, ว่า, งาน, ดี ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="참고-문서"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%eb%ac%b8%ec%84%9c" class="header-anchor"&gt;&lt;/a&gt;참고 문서
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.8/analysis-tokenizers.html" target="_blank" rel="noopener"
 &gt;Tokenizer reference | Elasticsearch Guide [8.8]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Elasticsearch Character Filter</title><link>https://0andwild.com/posts/240202_es_analyzer1/</link><pubDate>Fri, 02 Feb 2024 21:00:06 +0900</pubDate><guid>https://0andwild.com/posts/240202_es_analyzer1/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Elasticsearch Character Filter" /&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="598px" data-flex-grow="249" data-title-escaped="CharFilter → Tokenizer → TokenFilter 순으로 진행" height="614" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/240202_es_analyzer1/sequence.png" srcset="https://0andwild.com/posts/240202_es_analyzer1/sequence_hu_fc41f98e8e2a0d34.png 800w, https://0andwild.com/posts/240202_es_analyzer1/sequence.png 1530w" title="CharFilter → Tokenizer → TokenFilter 순으로 진행" width="1530"&gt;&lt;/p&gt;
&lt;h1 id="character-filters"&gt;&lt;a href="#character-filters" class="header-anchor"&gt;&lt;/a&gt;Character Filters
&lt;/h1&gt;&lt;p&gt;Character Filter는 토크나이저 단계 이전에 입력된 문자열을 전처리하는 과정이다.&lt;/p&gt;
&lt;p&gt;문자열들에 더하거나, 제거하거나, 문자열을 바꾸는 작업을 한다.&lt;/p&gt;
&lt;p&gt;Elasticsearch에서는 다음과 같이 기본적인 Character Filter들을 제공하고 커스텀 필터도 적용 가능하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="html-strip-character-filter"&gt;&lt;a href="#html-strip-character-filter" class="header-anchor"&gt;&lt;/a&gt;HTML Strip Character Filter
&lt;/h2&gt;&lt;p&gt;HTML 형태로 입력받은 값을 decoding된 값으로 변환시켜준다.&lt;/p&gt;
&lt;h3 id="변환-예시"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;/_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;keyword&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;html_strip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;p&amp;gt;I&amp;amp;apos;m so &amp;lt;b&amp;gt;happy&amp;lt;/b&amp;gt;!&amp;lt;/p&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ \nI&amp;#39;m so happy!\n ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="적용-방법"&gt;&lt;a href="#%ec%a0%81%ec%9a%a9-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;적용 방법
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;/my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analysis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;keyword&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;html_strip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="mapping-character-filter"&gt;&lt;a href="#mapping-character-filter" class="header-anchor"&gt;&lt;/a&gt;Mapping Character Filter
&lt;/h2&gt;&lt;p&gt;Mapping Character Filter는 입력받은 문자열이 key 값으로 지정된 문자와 동일하면 해당 key의 value 값으로 변환을 시켜준다.&lt;/p&gt;
&lt;p&gt;Matching 방식은 탐욕법으로 가장 많이 매칭된 패턴으로 변환되며 변환값(value)은 빈 문자열도 가능하다.&lt;/p&gt;
&lt;h3 id="변환-예시-1"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-1" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;/_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;keyword&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mapping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;mappings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٠ =&amp;gt; 0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;١ =&amp;gt; 1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٢ =&amp;gt; 2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٣ =&amp;gt; 3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٤ =&amp;gt; 4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٥ =&amp;gt; 5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٦ =&amp;gt; 6&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٧ =&amp;gt; 7&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٨ =&amp;gt; 8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;٩ =&amp;gt; 9&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;My license plate is ٢٥٠١٥&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ My license plate is 25015 ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="pattern-replace-character-filter"&gt;&lt;a href="#pattern-replace-character-filter" class="header-anchor"&gt;&lt;/a&gt;Pattern Replace Character Filter
&lt;/h2&gt;&lt;p&gt;pattern_replace 필터는 정규식에 매칭되는 문자열들을 지정된 문자열로 변환시켜준다.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;⚠️ &lt;strong&gt;주의사항&lt;/strong&gt;: 정규식은 Java 정규식을 따르며, 안 좋은 정규식은 성능 저하 또는 StackOverflow 에러를 발생시키고, 실행 중인 노드를 갑자기 종료시킬 수 있다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="파라미터"&gt;&lt;a href="#%ed%8c%8c%eb%9d%bc%eb%af%b8%ed%84%b0" class="header-anchor"&gt;&lt;/a&gt;파라미터
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pattern&lt;/code&gt;: Java 정규표현식&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replacement&lt;/code&gt;: 치환할 문자열&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flags&lt;/code&gt;: Java의 정규 표현식 플래그로 &lt;code&gt;|&lt;/code&gt;로 분리되어야 함 (예: &amp;ldquo;CASE_INSENSITIVE|COMMENTS&amp;rdquo;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="변환-예시-2"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ec%98%88%ec%8b%9c-2" class="header-anchor"&gt;&lt;/a&gt;변환 예시
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analysis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tokenizer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my_char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;my_char_filter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pattern_replace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pattern&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;(\\d+)-(?=\\d)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;replacement&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;$1_&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST&lt;/span&gt; &lt;span class="err"&gt;my-index&lt;/span&gt;&lt;span class="mi"&gt;-000001&lt;/span&gt;&lt;span class="err"&gt;/_analyze&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my_analyzer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;My credit card is 123-456-789&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result -&amp;gt; [ My, credit, card, is, 123_456_789 ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="참고-문서"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%eb%ac%b8%ec%84%9c" class="header-anchor"&gt;&lt;/a&gt;참고 문서
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/elasticsearch/reference/8.8/analysis-charfilters.html" target="_blank" rel="noopener"
 &gt;Character filters reference | Elasticsearch Guide [8.8]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>ElasticSearch 란?</title><link>https://0andwild.com/posts/240201_es/</link><pubDate>Thu, 01 Feb 2024 21:07:14 +0900</pubDate><guid>https://0andwild.com/posts/240201_es/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post ElasticSearch 란?" /&gt;&lt;p&gt;Elastic Search 는 Apache Lucene 기반의 Java 오픈소스 분산 검색 엔진이다.&lt;/p&gt;
&lt;p&gt;ElasticSearch 를 통해 루씬 라이브러리(Java에서 개발한 정보 검색용 라이브러리)를 단독으로 사용할 수 있으며, 방대한 양의 데이터를 실시간에 가깝게 저장, 검색, 분석을 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;ElasticeSearch 는 검색을 위해 단독으로 사용되기도 하며, &lt;strong&gt;ELK(Elasticsearch / Logstash / Kibana)&lt;/strong&gt; 스택으로 사용되기도 한다.&lt;/p&gt;
&lt;h3 id="elk-스택-구성-요소"&gt;&lt;a href="#elk-%ec%8a%a4%ed%83%9d-%ea%b5%ac%ec%84%b1-%ec%9a%94%ec%86%8c" class="header-anchor"&gt;&lt;/a&gt;ELK 스택 구성 요소
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Elasticsearch&lt;/strong&gt;: Logstash로 부터 받은 데이터를 검색 및 집계하여 필요한 정보를 획득&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logstash&lt;/strong&gt;: 다양한 소스(DB, csv 파일 등)의 로그 또는 트랜잭션 데이터를 수집, 집계, 파싱하여 Elasticsearch로 전달&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kibana&lt;/strong&gt;: Elasticsearch 의 빠른 검색을 통해 데이터를 시각화 및 모니터링&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;💡 주로 ELK는 로드밸런싱되어 있는 WAS의 흩어져있는 로그를 한 곳으로 모으고, 원하는 데이터를 빠르게 검색한 뒤 시각화하여 모니터링하기 위해 사용한다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="elasticsearch-와-rdb-의-명칭-비교"&gt;&lt;a href="#elasticsearch-%ec%99%80-rdb-%ec%9d%98-%eb%aa%85%ec%b9%ad-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;Elasticsearch 와 RDB 의 명칭 비교
&lt;/h2&gt;&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/240201_es/es1.png"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/240201_es/es2.png"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="elasticsearch-70-부터는-하나의-인덱스에-하나의-타입만-가질-수-있음"&gt;&lt;a href="#elasticsearch-70-%eb%b6%80%ed%84%b0%eb%8a%94-%ed%95%98%eb%82%98%ec%9d%98-%ec%9d%b8%eb%8d%b1%ec%8a%a4%ec%97%90-%ed%95%98%eb%82%98%ec%9d%98-%ed%83%80%ec%9e%85%eb%a7%8c-%ea%b0%80%ec%a7%88-%ec%88%98-%ec%9e%88%ec%9d%8c" class="header-anchor"&gt;&lt;/a&gt;Elasticsearch 7.0 부터는 하나의 인덱스에 하나의 타입만 가질 수 있음
&lt;/h3&gt;&lt;p&gt;이 이유는 Elasticsearch는 하나의 인덱스(DB) 안에서의 타입은 같은 Lucene 필드를 사용한다. 따라서 타입은 다를지라도 동일한 이름을 가진 필드는 독립적이지 않아으므로 여러가지 문제가 발생할 수 있으므로 하나의 인덱스는 하나의 타입만을 갖도록 수정이 되었다.&lt;/p&gt;
&lt;h4 id="rdb와의-비교"&gt;&lt;a href="#rdb%ec%99%80%ec%9d%98-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;RDB와의 비교
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;RDB의 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 DB에 여러 테이블이 있고 각 테이블에 동일한 이름의 컬럼이 존재해도 서로 영향을 미치지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Elasticsearch의 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 인덱스(=DB) 내의 각 타입(=테이블)에 동일한 이름을 가진 필드(=컬럼)가 있을 경우, 해당 필드는 독립적이지 않고 동일한 Lucene 필드에 저장되며 동일한 정의를 가져야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="elasticsearch-구조"&gt;&lt;a href="#elasticsearch-%ea%b5%ac%ec%a1%b0" class="header-anchor"&gt;&lt;/a&gt;Elasticsearch 구조
&lt;/h2&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/240201_es/es3.png"&gt;
&lt;/figure&gt;

&lt;h3 id="클러스터-cluster"&gt;&lt;a href="#%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-cluster" class="header-anchor"&gt;&lt;/a&gt;클러스터 (Cluster)
&lt;/h3&gt;&lt;p&gt;클러스터란 Elasticsearch 에서 가장 큰 시스템 단위를 의미하며, 최소 하나 이상의 노드로 이루어진 노드의 집합이다.&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;/ul&gt;
&lt;h3 id="노드-node"&gt;&lt;a href="#%eb%85%b8%eb%93%9c-node" class="header-anchor"&gt;&lt;/a&gt;노드 (Node)
&lt;/h3&gt;&lt;p&gt;노드는 클러스터에 포함된 단일 서버로서 데이터를 저장하고 클러스터의 색인화 및 검색 기능에 참여한다. 노드는 역할에 따라 다음과 같이 구분한다.&lt;/p&gt;
&lt;h4 id="master-eligible-node"&gt;&lt;a href="#master-eligible-node" class="header-anchor"&gt;&lt;/a&gt;Master-eligible Node
&lt;/h4&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;/ul&gt;
&lt;h4 id="data-node"&gt;&lt;a href="#data-node" class="header-anchor"&gt;&lt;/a&gt;Data Node
&lt;/h4&gt;&lt;p&gt;데이터(Document)가 저장되는 노드이며, 데이터가 분산 저장되는 공간인 샤드가 배치되는 노드&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CRUD, 색인, 검색, 통계 등의 데이터 작업을 수행&lt;/li&gt;
&lt;li&gt;많은 리소스(CPU, 메모리 등)를 필요로 함&lt;/li&gt;
&lt;li&gt;모니터링 작업이 필요하며, 마스터 노드와는 분리해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ingest-node"&gt;&lt;a href="#ingest-node" class="header-anchor"&gt;&lt;/a&gt;Ingest Node
&lt;/h4&gt;&lt;p&gt;데이터를 변환하는 등 사전 처리 파이프라인을 실행하는 역할&lt;/p&gt;
&lt;h4 id="coordination-only-node"&gt;&lt;a href="#coordination-only-node" class="header-anchor"&gt;&lt;/a&gt;Coordination Only Node
&lt;/h4&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;/ul&gt;
&lt;h3 id="인덱스--샤드--복제"&gt;&lt;a href="#%ec%9d%b8%eb%8d%b1%ec%8a%a4--%ec%83%a4%eb%93%9c--%eb%b3%b5%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;인덱스 / 샤드 / 복제
&lt;/h3&gt;&lt;h4 id="인덱스-index"&gt;&lt;a href="#%ec%9d%b8%eb%8d%b1%ec%8a%a4-index" class="header-anchor"&gt;&lt;/a&gt;인덱스 (Index)
&lt;/h4&gt;&lt;p&gt;RDB의 데이터베이스와 대응하는 개념&lt;/p&gt;
&lt;h4 id="샤드-shard"&gt;&lt;a href="#%ec%83%a4%eb%93%9c-shard" class="header-anchor"&gt;&lt;/a&gt;샤드 (Shard)
&lt;/h4&gt;&lt;p&gt;인덱스 내부에 색인된 데이터들이 하나로 뭉쳐서 존재하지 않고 여러 부분으로 나뉘어 존재함. 스케일 아웃을 위해 하나의 인덱스를 여러 샤드로 쪼갬.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;💡 샤드는 프라이머리 샤드와 레플리카 샤드로 나뉜다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;프라이머리 샤드 (Primary Shard)&lt;/strong&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;/ul&gt;
&lt;p&gt;&lt;strong&gt;레플리카 샤드 (Replica Shard)&lt;/strong&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;/ul&gt;
&lt;h3 id="세그먼트-segment"&gt;&lt;a href="#%ec%84%b8%ea%b7%b8%eb%a8%bc%ed%8a%b8-segment" class="header-anchor"&gt;&lt;/a&gt;세그먼트 (Segment)
&lt;/h3&gt;&lt;p&gt;세그먼트는 ElasticSearch 에서 문서의 빠른 검색을 위해 설계된 자료 구조이며, 샤드의 데이터를 가지고 있는 물리적인 파일이다.&lt;/p&gt;
&lt;h4 id="세그먼트의-특징"&gt;&lt;a href="#%ec%84%b8%ea%b7%b8%eb%a8%bc%ed%8a%b8%ec%9d%98-%ed%8a%b9%ec%a7%95" class="header-anchor"&gt;&lt;/a&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;/ul&gt;
&lt;h4 id="세그먼트-생성-과정"&gt;&lt;a href="#%ec%84%b8%ea%b7%b8%eb%a8%bc%ed%8a%b8-%ec%83%9d%ec%84%b1-%ea%b3%bc%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;세그먼트 생성 과정
&lt;/h4&gt;&lt;p&gt;매 요청마다 새로운 세그먼트를 만들면 너무 많은 세그먼트가 생성되므로 이를 방지하기 위해 &lt;strong&gt;인메모리 버퍼&lt;/strong&gt;를 사용한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flush&lt;/strong&gt;: 인메모리 버퍼에 쌓인 내용이 일정 시간이 지나거나 버퍼가 가득 차면 flush를 수행하고, 시스템 캐시에 세그먼트가 생성됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 시점부터 데이터 검색이 가능&lt;/li&gt;
&lt;li&gt;이 상태는 세그먼트가 시스템 캐시에 저장된 상태이지 디스크에 저장된 상태가 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt;: 일정 시간이 지나면 commit을 통해 물리적인 디스크에 세그먼트를 저장&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;병합&lt;/strong&gt;: 저장된 세그먼트는 시간이 지날수록 하나로 병합하는 과정을 수행&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;병합을 통해 세그먼트를 하나로 줄여주면 검색할 세그먼트 개수가 줄어 검색 성능이 향상됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>BigQuery Clustering 최적화</title><link>https://0andwild.com/posts/231220_bigquery/</link><pubDate>Wed, 20 Dec 2023 21:50:16 +0900</pubDate><guid>https://0andwild.com/posts/231220_bigquery/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post BigQuery Clustering 최적화" /&gt;&lt;h2 id="clustering"&gt;&lt;a href="#clustering" class="header-anchor"&gt;&lt;/a&gt;Clustering
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Clustering Concept" class="gallery-image" data-flex-basis="457px" data-flex-grow="190" height="483" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231220_bigquery/img1.png" srcset="https://0andwild.com/posts/231220_bigquery/img1_hu_3ecc95e8ba98b1db.png 800w, https://0andwild.com/posts/231220_bigquery/img1.png 920w" width="920"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_signups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Lebanon&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registration_date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2023-12-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Clustering을 통해 BigQuery가 데이터 접근에 있어 더 적은 일을 수행하게 하여 Query 속도를 올릴 수 있음. 하지만 Clustering을 하기 전에 테이블의 데이터 양을 생각하여 클러스터링을 처리하는 비용이 더 안 좋을 수 있을지를 생각하는 것이 좋음.&lt;/p&gt;
&lt;p&gt;예를 들어 BigQuery의 column based 테이블이 10 rows밖에 데이터가 없다고 생각을 하면 clustering을 하는 비용이 데이터를 풀스캔하는 비용보다 더 많이 나올 것임을 인지해야 함.&lt;/p&gt;
&lt;p&gt;전 구글 엔지니어의 말을 인용하면 클러스터링을 하기 위한 데이터 그룹당 100MB 미만이라면 클러스터링을 하는 것보다 풀스캔을 하는 것이 더 나을 수 있다고 함.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;: &lt;a class="link" href="https://stackoverflow.com/questions/52669725/google-bigquery-clustered-table-not-reducing-query-size-when-running-query-with/52674573#52674573" target="_blank" rel="noopener"
 &gt;Google BigQuery clustered table not reducing query size&lt;/a&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;중요 사항&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;추가적으로 클러스터링을 한 base 컬럼을 query 시에 필터링하지 않으면 아무런 query performance에 아무런 도움이 되지 않음.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="example-of-creating-clustered-tables"&gt;&lt;a href="#example-of-creating-clustered-tables" class="header-anchor"&gt;&lt;/a&gt;Example of Creating Clustered Tables
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="n"&gt;myproject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mydataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clustered_table&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registration_date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CLUSTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img alt="Clustered Table Structure" class="gallery-image" data-flex-basis="610px" data-flex-grow="254" height="371" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231220_bigquery/img2.png" srcset="https://0andwild.com/posts/231220_bigquery/img2_hu_e4a55682afde7f7.png 800w, https://0andwild.com/posts/231220_bigquery/img2.png 943w" width="943"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clustering 특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최대 4개의 column까지 클러스터링 가능&lt;/li&gt;
&lt;li&gt;Partitioning과 다르게 INT64, DATE 타입만 사용할 수 있지 않음&lt;/li&gt;
&lt;li&gt;STRING과 GEOGRAPHY와 같은 타입도 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="combine-clustering-with-partitioning"&gt;&lt;a href="#combine-clustering-with-partitioning" class="header-anchor"&gt;&lt;/a&gt;Combine Clustering with Partitioning
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Clustering + Partitioning" class="gallery-image" data-flex-basis="411px" data-flex-grow="171" height="538" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231220_bigquery/img3.png" srcset="https://0andwild.com/posts/231220_bigquery/img3_hu_d68a5613875dfcea.png 800w, https://0andwild.com/posts/231220_bigquery/img3.png 923w" width="923"&gt;&lt;/p&gt;
&lt;p&gt;파티셔닝과 클러스터링을 함께 사용하면 더욱 효율적인 데이터 접근이 가능함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;조합 전략&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Partitioning: 날짜 기반 데이터 분할&lt;/li&gt;
&lt;li&gt;Clustering: 파티션 내부에서 추가적인 정렬&lt;/li&gt;
&lt;li&gt;쿼리 성능과 비용 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/towards-data-science/add-one-line-of-sql-to-optimise-your-bigquery-tables-304761b048f0" target="_blank" rel="noopener"
 &gt;Add One Line of SQL to Optimise Your BigQuery Tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://towardsdatascience.com/use-the-partitions-luke-a-simple-and-proven-way-to-optimise-your-sql-queries-43e24ea4c5d0" target="_blank" rel="noopener"
 &gt;Use the Partitions, Luke! A Simple and Proven Way to Optimise Your SQL Queries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>BigQuery란?</title><link>https://0andwild.com/posts/231218_bigquery/</link><pubDate>Mon, 18 Dec 2023 21:37:29 +0900</pubDate><guid>https://0andwild.com/posts/231218_bigquery/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post BigQuery란?" /&gt;&lt;h2 id="bigquery-특징"&gt;&lt;a href="#bigquery-%ed%8a%b9%ec%a7%95" class="header-anchor"&gt;&lt;/a&gt;BigQuery 특징
&lt;/h2&gt;&lt;h3 id="column-based-database"&gt;&lt;a href="#column-based-database" class="header-anchor"&gt;&lt;/a&gt;Column-based Database
&lt;/h3&gt;&lt;p&gt;일반적인 로우 단위로 저장이 되는 RDB와 다르게 특정 컬럼의 데이터를 접근할 때 해당 로우를 모두 스캔하지 않고 찾고자 하는 컬럼 파일 하나만 스캔하여 접근.&lt;/p&gt;
&lt;p&gt;특정 컬럼만을 읽어 개수를 세거나 통계를 내는 분석용 데이터베이스(OLAP) 작업에 유리.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&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;/ul&gt;
&lt;hr&gt;
&lt;h3 id="데이터-처리-구조"&gt;&lt;a href="#%eb%8d%b0%ec%9d%b4%ed%84%b0-%ec%b2%98%eb%a6%ac-%ea%b5%ac%ec%a1%b0" class="header-anchor"&gt;&lt;/a&gt;데이터 처리 구조
&lt;/h3&gt;&lt;p&gt;&lt;img alt="BigQuery Architecture" class="gallery-image" data-flex-basis="492px" data-flex-grow="205" height="624" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231218_bigquery/img1.png" srcset="https://0andwild.com/posts/231218_bigquery/img1_hu_fad767db3e02e3de.png 800w, https://0andwild.com/posts/231218_bigquery/img1.png 1280w" width="1280"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Colossus (분산 스토리지)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google File System(GFS)를 잇는 Google의 클러스터 수준의 파일시스템&lt;/li&gt;
&lt;li&gt;맨 아래에서 저장소를 제공하고 Jupiter라는 TB급 네트워크 망을 통해 컴퓨터 노드와 통신&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;연산 계층 (Leaf, Mixer1, Mixer0)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;디스크 없이 Colossus에서 읽은 데이터를 처리&lt;/li&gt;
&lt;li&gt;각각 위의 계층으로 데이터를 올리는 역할&lt;/li&gt;
&lt;li&gt;분산 병렬 처리를 통한 고속 연산&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="no-key-no-index"&gt;&lt;a href="#no-key-no-index" class="header-anchor"&gt;&lt;/a&gt;No Key, No Index
&lt;/h3&gt;&lt;p&gt;키와 인덱스 개념이 존재하지 않음. 풀스캔 only&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&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;/ul&gt;
&lt;hr&gt;
&lt;h3 id="no-update-delete"&gt;&lt;a href="#no-update-delete" class="header-anchor"&gt;&lt;/a&gt;No Update, Delete
&lt;/h3&gt;&lt;p&gt;성능을 위해 추가만 가능하며 한번 입력된 데이터는 수정되거나 삭제될 수 없음.&lt;/p&gt;
&lt;p&gt;데이터가 잘못 입력된 경우 테이블을 삭제하고 다시 만들어야 함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;제약사항&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;INSERT만 지원&lt;/li&gt;
&lt;li&gt;UPDATE/DELETE 미지원&lt;/li&gt;
&lt;li&gt;데이터 수정 시 재생성 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="eventual-consistency"&gt;&lt;a href="#eventual-consistency" class="header-anchor"&gt;&lt;/a&gt;Eventual Consistency
&lt;/h3&gt;&lt;p&gt;데이터를 3개의 데이터 센터에 복제를 하기에 데이터 쓰기 후 바로 조회가 안 될 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Eventual Consistency" class="gallery-image" data-flex-basis="555px" data-flex-grow="231" height="553" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231218_bigquery/img2.png" srcset="https://0andwild.com/posts/231218_bigquery/img2_hu_7243d5dea1f40b1f.png 800w, https://0andwild.com/posts/231218_bigquery/img2.png 1280w" width="1280"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3중 복제를 통한 고가용성&lt;/li&gt;
&lt;li&gt;최종 일관성 보장&lt;/li&gt;
&lt;li&gt;쓰기 후 즉시 읽기 불가능할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://burning-dba.tistory.com/137" target="_blank" rel="noopener"
 &gt;BigQuery 성능/비용 팁&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://zzsza.github.io/gcp/2020/04/12/bigquery-unnest-array-struct/#bigquery-unnest" target="_blank" rel="noopener"
 &gt;BigQuery UNNEST, ARRAY, STRUCT 사용 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://bcho.tistory.com/1117" target="_blank" rel="noopener"
 &gt;구글 빅데이터 플랫폼 빅쿼리 아키텍처 소개&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Springdoc과 OpenAPI (어노테이션 활용법)</title><link>https://0andwild.com/posts/231017_swagger/</link><pubDate>Tue, 17 Oct 2023 17:12:52 +0900</pubDate><guid>https://0andwild.com/posts/231017_swagger/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Springdoc과 OpenAPI (어노테이션 활용법)" /&gt;&lt;h2 id="querystring-처리-방식-비교"&gt;&lt;a href="#querystring-%ec%b2%98%eb%a6%ac-%eb%b0%a9%ec%8b%9d-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;QueryString 처리 방식 비교
&lt;/h2&gt;&lt;p&gt;Spring에서 QueryString을 처리하는 두 가지 방식을 비교해보겠습니다.&lt;/p&gt;
&lt;h3 id="방식-1-object로-받기-parameterobject"&gt;&lt;a href="#%eb%b0%a9%ec%8b%9d-1-object%eb%a1%9c-%eb%b0%9b%ea%b8%b0-parameterobject" class="header-anchor"&gt;&lt;/a&gt;방식 1: Object로 받기 (ParameterObject)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;swagger&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/hello/parameters1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ResponseTest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parameterObjectTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ParameterObjectReq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseTest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;occupation&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="방식-2-개별-파라미터로-받기-requestparam"&gt;&lt;a href="#%eb%b0%a9%ec%8b%9d-2-%ea%b0%9c%eb%b3%84-%ed%8c%8c%eb%9d%bc%eb%af%b8%ed%84%b0%eb%a1%9c-%eb%b0%9b%ea%b8%b0-requestparam" class="header-anchor"&gt;&lt;/a&gt;방식 2: 개별 파라미터로 받기 (@RequestParam)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;swagger&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/hello/parameters2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ResponseTest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parameterObjectTest2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pw&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;oq&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OccupationStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseTest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResponseEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="모델-정의"&gt;&lt;a href="#%eb%aa%a8%eb%8d%b8-%ec%a0%95%ec%9d%98" class="header-anchor"&gt;&lt;/a&gt;모델 정의
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;ParameterObjectReq (Request DTO)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ParameterObjectReq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OccupationStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;occupation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;OccupationStatus (Enum)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OccupationStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STUDENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EMPLOYEE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UNEMPLOYED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="두-방식의-차이점"&gt;&lt;a href="#%eb%91%90-%eb%b0%a9%ec%8b%9d%ec%9d%98-%ec%b0%a8%ec%9d%b4%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;두 방식의 차이점
&lt;/h3&gt;&lt;p&gt;일반적으로 request를 QueryString으로 받을 경우 &lt;code&gt;@RequestParam&lt;/code&gt;을 사용하지만, 받는 인자가 많을 경우 첫 번째 방식처럼 QueryString을 Object 형태로 받을 수 있습니다.&lt;/p&gt;
&lt;h4 id="requestparam-vs-parameterobject"&gt;&lt;a href="#requestparam-vs-parameterobject" class="header-anchor"&gt;&lt;/a&gt;@RequestParam vs ParameterObject
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@RequestParam&lt;/strong&gt;: 기본적으로 &lt;code&gt;required = true&lt;/code&gt;로 설정되어 있어 request value를 필수로 받습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ParameterObject&lt;/strong&gt;: Spring에서 별도의 어노테이션 없이도 QueryString을 객체의 필드값에 자동으로 바인딩합니다. 하지만 &lt;code&gt;required&lt;/code&gt;가 기본 설정되지 않아 &lt;code&gt;null&lt;/code&gt; 값이 들어올 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="springdoc에서의-parameterobject-vs-requestparam-변환-비교"&gt;&lt;a href="#springdoc%ec%97%90%ec%84%9c%ec%9d%98-parameterobject-vs-requestparam-%eb%b3%80%ed%99%98-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;Springdoc에서의 ParameterObject vs @RequestParam 변환 비교
&lt;/h2&gt;&lt;h3 id="parameterobject-사용-시"&gt;&lt;a href="#parameterobject-%ec%82%ac%ec%9a%a9-%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;ParameterObject 사용 시
&lt;/h3&gt;&lt;p&gt;&lt;img alt="ParameterObject 변환 결과" class="gallery-image" data-flex-basis="273px" data-flex-grow="114" height="817" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image1.png" srcset="https://0andwild.com/posts/231017_swagger/image1_hu_cf1157316278237d.png 800w, https://0andwild.com/posts/231017_swagger/image1.png 932w" width="932"&gt;&lt;/p&gt;
&lt;h3 id="requestparam-사용-시"&gt;&lt;a href="#requestparam-%ec%82%ac%ec%9a%a9-%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;@RequestParam 사용 시
&lt;/h3&gt;&lt;p&gt;&lt;img alt="RequestParam 변환 결과" class="gallery-image" data-flex-basis="286px" data-flex-grow="119" height="776" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image2.png" srcset="https://0andwild.com/posts/231017_swagger/image2_hu_885e9d03410a6c49.png 800w, https://0andwild.com/posts/231017_swagger/image2.png 927w" width="927"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="parameterobject-어노테이션-활용"&gt;&lt;a href="#parameterobject-%ec%96%b4%eb%85%b8%ed%85%8c%ec%9d%b4%ec%85%98-%ed%99%9c%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;@ParameterObject 어노테이션 활용
&lt;/h2&gt;&lt;p&gt;ParameterObject를 Springdoc이 &lt;code&gt;@RequestParam&lt;/code&gt;을 사용했을 때처럼 변환해주고 Required 여부를 표시하려면 다음과 같이 설정합니다.&lt;/p&gt;
&lt;h3 id="코드-예제"&gt;&lt;a href="#%ec%bd%94%eb%93%9c-%ec%98%88%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;코드 예제
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@ParameterObject&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ParameterObjectReq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@NotNull&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@NotNull&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OccupationStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;occupation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;@ParameterObject&lt;/code&gt;는 Springdoc 어노테이션으로, 여러 개의 QueryString을 Object 형태로 받을 경우 해당 클래스 위에 명시하면 &lt;code&gt;@RequestParam&lt;/code&gt;처럼 인식하고 변환해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Swagger UI에서의 변환 결과" class="gallery-image" data-flex-basis="306px" data-flex-grow="127" height="356" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image3.png" width="454"&gt;&lt;/p&gt;
&lt;h3 id="jsr-303-지원"&gt;&lt;a href="#jsr-303-%ec%a7%80%ec%9b%90" class="header-anchor"&gt;&lt;/a&gt;JSR-303 지원
&lt;/h3&gt;&lt;p&gt;Springdoc은 JSR-303을 지원하며, 다음과 같은 validation 어노테이션을 사용할 수 있습니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@NotNull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Min&lt;/code&gt;, &lt;code&gt;@Max&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;기타 validation 어노테이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Springdoc 공식 문서에 따르면&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;This library supports&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenAPI 3&lt;/li&gt;
&lt;li&gt;Spring-boot (v1, v2 and v3)&lt;/li&gt;
&lt;li&gt;JSR-303, specifically for @NotNull, @Min, @Max, and @Size&lt;/li&gt;
&lt;li&gt;Swagger-ui&lt;/li&gt;
&lt;li&gt;OAuth 2&lt;/li&gt;
&lt;li&gt;GraalVM native images&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;
&lt;h3 id="변환-결과"&gt;&lt;a href="#%eb%b3%80%ed%99%98-%ea%b2%b0%ea%b3%bc" class="header-anchor"&gt;&lt;/a&gt;변환 결과
&lt;/h3&gt;&lt;p&gt;ParameterObject도 &lt;code&gt;@RequestParam&lt;/code&gt;으로 인식되도록 spec 파일이 작성되었고, &lt;code&gt;@NotNull&lt;/code&gt;을 붙이지 않은 &lt;code&gt;occupation&lt;/code&gt;에는 Required가 optional 형태로 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Spec 파일 변환 결과" class="gallery-image" data-flex-basis="276px" data-flex-grow="115" height="796" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image4.png" srcset="https://0andwild.com/posts/231017_swagger/image4_hu_88db034e3bd78cc8.png 800w, https://0andwild.com/posts/231017_swagger/image4.png 918w" width="918"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Swagger UI 표시 결과" class="gallery-image" data-flex-basis="563px" data-flex-grow="234" height="701" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image5.png" srcset="https://0andwild.com/posts/231017_swagger/image5_hu_9ca7d7b267dcf395.png 800w, https://0andwild.com/posts/231017_swagger/image5_hu_f4b70326621470ab.png 1600w, https://0andwild.com/posts/231017_swagger/image5.png 1647w" width="1647"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;좌측 이미지&lt;/strong&gt;: &lt;code&gt;@ParameterObject&lt;/code&gt;를 명시했을 경우 Springdoc이 인식하고 정상적인 spec으로 변환&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="swagger2--swagger3-annotations"&gt;&lt;a href="#swagger2--swagger3-annotations" class="header-anchor"&gt;&lt;/a&gt;Swagger2 → Swagger3 Annotations
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Swagger2&lt;/th&gt;
 &lt;th&gt;Swagger3&lt;/th&gt;
 &lt;th&gt;설명&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;@Api&lt;/td&gt;
 &lt;td&gt;@Tag&lt;/td&gt;
 &lt;td&gt;클래스단에 swagger 리소스 표시(그룹화 시켜줌)&lt;br&gt;&lt;br&gt;&lt;code&gt;name&lt;/code&gt; : 태그의 이름&lt;br&gt;&lt;code&gt;description&lt;/code&gt; : 태그에 대한 설명&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiIgnore&lt;/td&gt;
 &lt;td&gt;@Parameter(hidden = true)&lt;br&gt;@Operation(hidden = true)&lt;br&gt;@Hidden&lt;/td&gt;
 &lt;td&gt;해당 어노테이션을 통해 파라미터를 swagger-ui 에서 숨길 수 있음.&lt;br&gt;&lt;br&gt;requestBody 나 ResponseBody 의 경우는&lt;br&gt;@JsonProperty(access = JsonProperty.Access.READ_ONLY) 를 사용&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiImplicitParam&lt;/td&gt;
 &lt;td&gt;@Parameter&lt;/td&gt;
 &lt;td&gt;단일 RequestParam 에 대한 설정 및 리소스 표시&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiImplicitParams&lt;/td&gt;
 &lt;td&gt;@Parameters&lt;/td&gt;
 &lt;td&gt;여러개의 RequestParam 을 설정&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiModel&lt;/td&gt;
 &lt;td&gt;@Schema&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;description&lt;/code&gt; : 한글명&lt;br&gt;&lt;code&gt;defaultValue&lt;/code&gt; : 기본값&lt;br&gt;&lt;code&gt;allowableValues&lt;/code&gt; : 허용가능한 값(열거형으로 정의가능할 경우 설정합니다)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiModelProperty(hidden = true)&lt;/td&gt;
 &lt;td&gt;@Schema(accessMode = READ_ONLY)&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiOperation(value = &amp;ldquo;foo&amp;rdquo;, notes = &amp;ldquo;bar&amp;rdquo;)&lt;/td&gt;
 &lt;td&gt;@Operation(summary = &amp;ldquo;foo&amp;rdquo;, description = &amp;ldquo;bar&amp;rdquo;)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;summary&lt;/code&gt; : api에 대한 간략 설명&lt;br&gt;&lt;code&gt;description&lt;/code&gt; : api에 대한 상세 설명&lt;br&gt;&lt;code&gt;responses&lt;/code&gt; : api Response 리스트&lt;br&gt;&lt;code&gt;parameters&lt;/code&gt; : api 파라미터 리스트&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiParam&lt;/td&gt;
 &lt;td&gt;@Parameter&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;name&lt;/code&gt; : 파라미터 이름&lt;br&gt;&lt;code&gt;description&lt;/code&gt; : 파라미터 설명&lt;br&gt;&lt;code&gt;in&lt;/code&gt; : 파라미터 위치 (query, header, path, cookie)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@ApiResponse(code = 404, message = &amp;ldquo;foo&amp;rdquo;)&lt;/td&gt;
 &lt;td&gt;@ApiResponse(responseCode = &amp;ldquo;404&amp;rdquo;, description = &amp;ldquo;foo&amp;rdquo;)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;responseCode&lt;/code&gt; : http 상태코드&lt;br&gt;&lt;code&gt;description&lt;/code&gt; : response에 대한 설명&lt;br&gt;&lt;code&gt;content&lt;/code&gt; : Response payload 구조&lt;br&gt;&lt;code&gt;schema&lt;/code&gt; : payload에서 이용하는 Schema&lt;br&gt;&lt;br&gt;&lt;code&gt;hidden&lt;/code&gt; : Schema 숨김여부&lt;br&gt;&lt;code&gt;implementation&lt;/code&gt; : Schema 대상 클래스&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;여러 개의 request query params를 캡처하기 위해 객체를 사용하는 경우, 해당 메서드 인자에 &lt;code&gt;@ParameterObject&lt;/code&gt; 어노테이션을 사용하세요&lt;/li&gt;
&lt;li&gt;이 단계는 선택사항입니다: &lt;strong&gt;여러 개의&lt;/strong&gt; &lt;code&gt;Docket&lt;/code&gt; 빈이 있는 경우에만 &lt;code&gt;GroupedOpenApi&lt;/code&gt; 빈으로 교체하세요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="tag-어노테이션-활용"&gt;&lt;a href="#tag-%ec%96%b4%eb%85%b8%ed%85%8c%ec%9d%b4%ec%85%98-%ed%99%9c%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;@Tag 어노테이션 활용
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;@Tag&lt;/code&gt; 어노테이션을 사용하면 다음과 같은 그룹핑이 가능합니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Controller 단위로 그룹핑&lt;/li&gt;
&lt;li&gt;Controller 내부의 메서드 단위로 그룹핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Tag&lt;/code&gt;에 명명한 이름에 따라 spec 파일로 전환 시 그룹핑 수행&lt;/li&gt;
&lt;li&gt;OpenAPI Generator를 이용한 client code 생성 시 해당 이름으로 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tag-중복-사용-시-주의사항"&gt;&lt;a href="#tag-%ec%a4%91%eb%b3%b5-%ec%82%ac%ec%9a%a9-%ec%8b%9c-%ec%a3%bc%ec%9d%98%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;@Tag 중복 사용 시 주의사항
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;질문&lt;/strong&gt;: 최상위 레벨에 &lt;code&gt;@Tag&lt;/code&gt;로 그룹핑하고, 하위 메서드의 &lt;code&gt;@Operation&lt;/code&gt;에서 다른 이름으로 tag를 설정하면 어떻게 될까요?&lt;/p&gt;
&lt;h4 id="테스트-결과"&gt;&lt;a href="#%ed%85%8c%ec%8a%a4%ed%8a%b8-%ea%b2%b0%ea%b3%bc" class="header-anchor"&gt;&lt;/a&gt;테스트 결과
&lt;/h4&gt;&lt;p&gt;&lt;img alt="중복 그룹 생성 결과" class="gallery-image" data-flex-basis="442px" data-flex-grow="184" height="573" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image6.png" srcset="https://0andwild.com/posts/231017_swagger/image6_hu_39ef28f5749fa3a7.png 800w, https://0andwild.com/posts/231017_swagger/image6.png 1056w" width="1056"&gt;&lt;/p&gt;
&lt;p&gt;최상위에 &lt;code&gt;@Tag(name = &amp;quot;swagger&amp;quot;)&lt;/code&gt;를 설정하고, &lt;code&gt;postHello&lt;/code&gt; 메서드의 &lt;code&gt;@Operation&lt;/code&gt;에서 &lt;code&gt;tags = {&amp;quot;swagger123&amp;quot;}&lt;/code&gt;을 추가한 경우, 같은 엔드포인트가 다른 그룹으로 중복 생성됩니다.&lt;/p&gt;
&lt;h4 id="문제점"&gt;&lt;a href="#%eb%ac%b8%ec%a0%9c%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;문제점
&lt;/h4&gt;&lt;p&gt;이 상태에서 OpenAPI Generator를 사용하면 아래와 같이 중복된 client 코드가 생성되는 문제가 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="중복 생성된 API 파일 1" class="gallery-image" data-flex-basis="728px" data-flex-grow="303" height="510" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image7.png" srcset="https://0andwild.com/posts/231017_swagger/image7_hu_bc30ff9f9efe9f74.png 800w, https://0andwild.com/posts/231017_swagger/image7.png 1548w" width="1548"&gt;
&lt;img alt="중복 생성된 API 파일 2" class="gallery-image" data-flex-basis="269px" data-flex-grow="112" height="673" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image8.png" width="757"&gt;
&lt;img alt="중복 생성된 API 파일 3" class="gallery-image" data-flex-basis="428px" data-flex-grow="178" height="174" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/231017_swagger/image9.png" width="311"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;권장사항&lt;/strong&gt;: 특별한 경우가 아니라면 &lt;code&gt;@Tag&lt;/code&gt;를 이용한 그룹핑은 Controller의 최상위에서만 사용하는 것을 권장합니다.&lt;/p&gt;
&lt;h3 id="openapi-generator-client-code-생성-시-파일명"&gt;&lt;a href="#openapi-generator-client-code-%ec%83%9d%ec%84%b1-%ec%8b%9c-%ed%8c%8c%ec%9d%bc%eb%aa%85" class="header-anchor"&gt;&lt;/a&gt;OpenAPI Generator Client Code 생성 시 파일명
&lt;/h3&gt;&lt;p&gt;Client 코드 생성 시 &lt;code&gt;@Tag&lt;/code&gt;에서 명명한 이름 + &lt;code&gt;-api&lt;/code&gt;가 postfix로 붙습니다. 이 부분을 커스터마이징하려면 Mustache 파일을 수정해야 합니다.&lt;/p&gt;
&lt;h3 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://openapi-generator.tech/docs/templating" target="_blank" rel="noopener"
 &gt;Using Templates | OpenAPI Generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/janl/mustache.js" target="_blank" rel="noopener"
 &gt;Mustache.js GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://hmos.dev/how-to-use-oas-generator" target="_blank" rel="noopener"
 &gt;OpenAPI Generator 사용법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://velog.io/@kdeun1/OpenAPI-Generator%eb%a5%bc-%ec%82%ac%ec%9a%a9%ed%95%98%ec%97%ac-API%ec%99%80-%eb%8f%99%ec%9d%bc%ed%95%9c-Model%ea%b3%bc-%ec%a0%95%ed%98%95%ed%99%94%eb%90%9c-API%ec%bd%94%eb%93%9c-%ec%9e%90%eb%8f%99%ec%83%9d%ec%84%b1%ed%95%98%ea%b8%b0" target="_blank" rel="noopener"
 &gt;OpenAPI Generator로 API의 안전한 Model과 정형화된 구현코드 자동생성하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="인증-관련-openapi-스펙"&gt;&lt;a href="#%ec%9d%b8%ec%a6%9d-%ea%b4%80%eb%a0%a8-openapi-%ec%8a%a4%ed%8e%99" class="header-anchor"&gt;&lt;/a&gt;인증 관련 OpenAPI 스펙
&lt;/h2&gt;&lt;p&gt;OpenAPI는 다양한 인증 방식을 지원합니다. 주요 설정 항목은 다음과 같습니다.&lt;/p&gt;
&lt;h3 id="type-인증-형식"&gt;&lt;a href="#type-%ec%9d%b8%ec%a6%9d-%ed%98%95%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;type (인증 형식)
&lt;/h3&gt;&lt;p&gt;현재 API Key, HTTP, OAuth2, OpenID Connect 방식을 지원합니다.
&lt;strong&gt;참고&lt;/strong&gt;: OpenAPI v2 스펙에서는 OpenID Connect 방식을 지원하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지원되는 타입&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http&lt;/code&gt;: Basic, Bearer 및 기타 HTTP 인증 체계&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apiKey&lt;/code&gt;: API 키 및 쿠키 인증&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth2&lt;/code&gt;: OAuth2 인증&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openIdConnect&lt;/code&gt;: OpenID Connect 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="주요-설정-항목"&gt;&lt;a href="#%ec%a3%bc%ec%9a%94-%ec%84%a4%ec%a0%95-%ed%95%ad%eb%aa%a9" class="header-anchor"&gt;&lt;/a&gt;주요 설정 항목
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;name&lt;/code&gt;&lt;/strong&gt;: 인증 키 이름 (API Key 방식 사용 시 필요)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;in&lt;/code&gt;&lt;/strong&gt;: 인증 키의 위치 지정 (&lt;code&gt;query&lt;/code&gt;, &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;cookie&lt;/code&gt; 중 선택, API Key 방식 사용 시 필요)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scheme&lt;/code&gt;&lt;/strong&gt;: 인증 방식 지정 (&lt;code&gt;Basic&lt;/code&gt; 또는 &lt;code&gt;Bearer&lt;/code&gt;, HTTP 인증 방식 사용 시 필요)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;bearerFormat&lt;/code&gt;&lt;/strong&gt;: Bearer 토큰 형식 (일반적으로 &lt;code&gt;JWT&lt;/code&gt; 사용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flows&lt;/code&gt;&lt;/strong&gt;: OAuth2 플로우 타입 (&lt;code&gt;implicit&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;clientCredentials&lt;/code&gt;, &lt;code&gt;authorizationCode&lt;/code&gt; 중 선택)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;openIdConnectUrl&lt;/code&gt;&lt;/strong&gt;: OpenID Connect URL (OpenAPI v2 스펙에서는 OAuth2나 Bearer 토큰 방식으로 대체 권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="deprecated-전략"&gt;&lt;a href="#deprecated-%ec%a0%84%eb%9e%b5" class="header-anchor"&gt;&lt;/a&gt;@Deprecated 전략
&lt;/h2&gt;&lt;p&gt;API 버전 업데이트로 DTO 스펙에 변경이 있을 경우, 다음과 같은 단계적 전략을 사용합니다.&lt;/p&gt;
&lt;h3 id="1단계-deprecated-표시"&gt;&lt;a href="#1%eb%8b%a8%ea%b3%84-deprecated-%ed%91%9c%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;1단계: @Deprecated 표시
&lt;/h3&gt;&lt;p&gt;먼저 변경될 필드에 &lt;code&gt;@Deprecated&lt;/code&gt; 어노테이션을 붙입니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Deprecated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oldField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;OpenAPI spec 상에도 해당 스키마의 필드에 &lt;code&gt;deprecated&lt;/code&gt;가 표시되고, 프론트엔드에서 코드 생성 시 해당 필드에 deprecated 표시가 나타납니다. 이를 통해 프론트엔드 팀에게 곧 해당 필드가 제거될 것임을 미리 알립니다.&lt;/p&gt;
&lt;h3 id="2단계-schemahidden--true-적용"&gt;&lt;a href="#2%eb%8b%a8%ea%b3%84-schemahidden--true-%ec%a0%81%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;2단계: @Schema(hidden = true) 적용
&lt;/h3&gt;&lt;p&gt;프론트엔드에서 새로운 스펙으로 마이그레이션이 완료되면, 서버에서 해당 &lt;code&gt;@Deprecated&lt;/code&gt; 필드에 &lt;code&gt;@Schema(hidden = true)&lt;/code&gt;를 추가하여 더 이상 OpenAPI spec에 해당 필드가 생성되지 않도록 합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Deprecated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oldField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// spec에서 제외됨&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3단계-필드-제거"&gt;&lt;a href="#3%eb%8b%a8%ea%b3%84-%ed%95%84%eb%93%9c-%ec%a0%9c%ea%b1%b0" class="header-anchor"&gt;&lt;/a&gt;3단계: 필드 제거
&lt;/h3&gt;&lt;p&gt;충분한 시간이 지난 후 해당 필드를 완전히 제거합니다.&lt;/p&gt;
&lt;p&gt;이러한 단계적 접근 방식을 통해 프론트엔드와 백엔드 간의 안전한 API 버전 관리가 가능합니다.&lt;/p&gt;</description></item><item><title>OpenAPI Generator 정복하기</title><link>https://0andwild.com/posts/231016_swagger/</link><pubDate>Mon, 16 Oct 2023 16:56:35 +0900</pubDate><guid>https://0andwild.com/posts/231016_swagger/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post OpenAPI Generator 정복하기" /&gt;&lt;h2 id="swagger란"&gt;&lt;a href="#swagger%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;Swagger란?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Swagger&lt;/strong&gt;는 OAS(OpenAPI Specification)를 위한 프레임워크로, API들이 가지고 있는 스펙을 명세하고 관리할 수 있는 프로젝트입니다. Swagger를 통해 REST API 서비스를 설계, 빌드, 문서화할 수 있습니다.&lt;/p&gt;
&lt;h3 id="swagger-tools"&gt;&lt;a href="#swagger-tools" class="header-anchor"&gt;&lt;/a&gt;Swagger Tools
&lt;/h3&gt;&lt;p&gt;Swagger는 다음과 같은 주요 도구들을 제공합니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Swagger UI&lt;/strong&gt;: Swagger API 명세서를 HTML 형식으로 시각화하여 확인할 수 있도록 해주는 도구&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swagger Codegen&lt;/strong&gt;: Swagger에 정의된 스펙에 따라 클라이언트 및 서버 코드를 자동으로 생성해주는 CLI 툴&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swagger Editor&lt;/strong&gt;: Swagger 표준에 따른 API 설계서 및 명세서를 작성하기 위한 에디터&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="springfox-vs-springdoc"&gt;&lt;a href="#springfox-vs-springdoc" class="header-anchor"&gt;&lt;/a&gt;Springfox vs Springdoc
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Springfox Swagger&lt;/strong&gt;는 Spring 또는 Spring Boot를 사용하는 프로젝트에서 Swagger를 이용해 API 문서를 쉽게 작성할 수 있도록 도와주는 라이브러리입니다.&lt;/p&gt;
&lt;p&gt;Springfox가 업데이트를 중단하는 시점에 &lt;strong&gt;Springdoc&lt;/strong&gt;이 등장했으며, 활발한 업데이트가 이루어지면서 급부상하게 되었습니다. Springdoc 역시 Swagger 문서 작성을 지원하는 라이브러리로, 현재는 Springfox보다 더 권장되는 선택입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="swagger-codegen-vs-openapi-generator"&gt;&lt;a href="#swagger-codegen-vs-openapi-generator" class="header-anchor"&gt;&lt;/a&gt;Swagger Codegen vs OpenAPI Generator
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Swagger&lt;/strong&gt;는 SmartBear사의 트레이드마크이며, &lt;strong&gt;Swagger Codegen&lt;/strong&gt;은 그 안에 포함되어 있는 프로젝트입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenAPI Generator&lt;/strong&gt;는 Swagger Codegen 프로젝트를 포크(fork)하여 시작된 커뮤니티 주도 오픈소스 프로젝트입니다. 현재 40명 이상의 상위 프로젝트 기여자와 Swagger Codegen의 창립 멤버들이 함께 참여하고 있습니다.&lt;/p&gt;
&lt;h3 id="openapi-generator-license"&gt;&lt;a href="#openapi-generator-license" class="header-anchor"&gt;&lt;/a&gt;OpenAPI Generator License
&lt;/h3&gt;&lt;p&gt;OpenAPI Generator는 &lt;strong&gt;Apache License 2.0&lt;/strong&gt;을 따르고 있습니다.&lt;/p&gt;
&lt;h4 id="apache-license-20이란"&gt;&lt;a href="#apache-license-20%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;Apache License 2.0이란?
&lt;/h4&gt;&lt;p&gt;Apache License 2.0에 따라 다음과 같은 권리가 부여됩니다&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;strong&gt;단, Apache License 버전 및 표기는 반드시 포함해야 함&lt;/strong&gt; (Apache License로 개발된 소프트웨어임을 명확히 표시)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="openapi-generator의-탄생-배경"&gt;&lt;a href="#openapi-generator%ec%9d%98-%ed%83%84%ec%83%9d-%eb%b0%b0%ea%b2%bd" class="header-anchor"&gt;&lt;/a&gt;OpenAPI Generator의 탄생 배경
&lt;/h3&gt;&lt;p&gt;OpenAPI Generator의 공식 Q&amp;amp;A를 보면 프로젝트의 탄생 배경을 확인할 수 있습니다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;버전 철학의 차이&lt;/strong&gt;
Swagger Codegen의 창립 멤버들은 Swagger Codegen 3.0.0이 2.x의 철학과 너무 많이 다르다고 느꼈습니다. 두 개의 개별 브랜치(2.x, 3.x)를 유지 관리하는 오버헤드가 Python 커뮤니티에서 겪었던 것과 유사한 문제를 야기할 수 있다고 우려했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;빠른 배포 주기&lt;/strong&gt;
창립 멤버들은 사용자가 원하는 안정적인 배포 버전을 사용하기 위해 몇 달을 기다리지 않도록 더 빠른 배포 주기를 원했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주간 패치 배포 (Weekly patch releases)&lt;/li&gt;
&lt;li&gt;월간 마이너 배포 (Monthly minor releases)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;커뮤니티 주도 개발&lt;/strong&gt;
커뮤니티가 주도하는 오픈소스 프로젝트 형식으로 진행하면 혁신과 신뢰성, 그리고 커뮤니티가 소유하는 로드맵을 확보할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 이유들로 OpenAPI Generator 프로젝트가 탄생했습니다. OpenAPI Generator는 MySQL과 MariaDB의 관계와 유사한 느낌입니다.&lt;/p&gt;
&lt;h3 id="swagger-codegen에서-마이그레이션"&gt;&lt;a href="#swagger-codegen%ec%97%90%ec%84%9c-%eb%a7%88%ec%9d%b4%ea%b7%b8%eb%a0%88%ec%9d%b4%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;Swagger Codegen에서 마이그레이션
&lt;/h3&gt;&lt;p&gt;공식 문서에 따르면, 기존에 Swagger Codegen 2.x 버전을 사용하고 있을 경우 OpenAPI Generator로 편리하게 마이그레이션할 수 있습니다. OpenAPI Generator는 Swagger Codegen 2.4.0-SNAPSHOT 버전을 기반으로 하고 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;자세한 마이그레이션 방법은 공식 가이드를 참조하세요&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://openapi-generator.tech/docs/swagger-codegen-migration" target="_blank" rel="noopener"
 &gt;Migrating from Swagger Codegen | OpenAPI Generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://openapi-generator.tech/" target="_blank" rel="noopener"
 &gt;OpenAPI Generator 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://openapi-generator.tech/docs/swagger-codegen-migration" target="_blank" rel="noopener"
 &gt;Migrating from Swagger Codegen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Jenkins &amp; Springboot CI/CD 정리 마지막 (4)</title><link>https://0andwild.com/posts/221112_jenkins_springboot/</link><pubDate>Sat, 12 Nov 2022 20:23:37 +0900</pubDate><guid>https://0andwild.com/posts/221112_jenkins_springboot/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Jenkins &amp; Springboot CI/CD 정리 마지막 (4)" /&gt;</description></item><item><title>Jenkins &amp; Springboot CI/CD 정리 (3)</title><link>https://0andwild.com/posts/221111_jenkins_springboot2/</link><pubDate>Fri, 11 Nov 2022 20:23:37 +0900</pubDate><guid>https://0andwild.com/posts/221111_jenkins_springboot2/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Jenkins &amp; Springboot CI/CD 정리 (3)" /&gt;</description></item><item><title>Jenkins &amp; Springboot CI/CD 정리 (2)</title><link>https://0andwild.com/posts/221111_jenkins_springboot/</link><pubDate>Fri, 11 Nov 2022 20:23:30 +0900</pubDate><guid>https://0andwild.com/posts/221111_jenkins_springboot/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Jenkins &amp; Springboot CI/CD 정리 (2)" /&gt;</description></item><item><title>Jenkins &amp; Springboot CI/CD 정리 (1)</title><link>https://0andwild.com/posts/221105_jenkins_springboot/</link><pubDate>Sat, 05 Nov 2022 20:23:37 +0900</pubDate><guid>https://0andwild.com/posts/221105_jenkins_springboot/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Jenkins &amp; Springboot CI/CD 정리 (1)" /&gt;</description></item><item><title>Jenkins&amp;Sonaqube&amp;Checkstyle 을 이용한 코드컨벤션 적용기(Naver Code Convention)</title><link>https://0andwild.com/posts/221103_jenkins_sornaqube/</link><pubDate>Thu, 03 Nov 2022 20:16:58 +0900</pubDate><guid>https://0andwild.com/posts/221103_jenkins_sornaqube/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Jenkins&amp;Sonaqube&amp;Checkstyle 을 이용한 코드컨벤션 적용기(Naver Code Convention)" /&gt;</description></item><item><title>Jenkins &amp; Slack Notification 연동</title><link>https://0andwild.com/posts/221027_jenkins_slack/</link><pubDate>Thu, 27 Oct 2022 00:00:00 +0900</pubDate><guid>https://0andwild.com/posts/221027_jenkins_slack/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Jenkins &amp; Slack Notification 연동" /&gt;</description></item><item><title>Nginx란 무엇일까? 웹 서버의 진화와 구조</title><link>https://0andwild.com/posts/221025_about_nginx/</link><pubDate>Tue, 25 Oct 2022 19:17:04 +0900</pubDate><guid>https://0andwild.com/posts/221025_about_nginx/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Nginx란 무엇일까? 웹 서버의 진화와 구조" /&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/featured.png"
 alt="Nginx 로고"&gt;
&lt;/figure&gt;

&lt;h2 id="nginx란"&gt;&lt;a href="#nginx%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;Nginx란?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Nginx&lt;/strong&gt;(엔진엑스)는 &lt;strong&gt;경량 고성능 웹 서버 소프트웨어&lt;/strong&gt;입니다. 웹 서버로 동작할 뿐만 아니라 리버스 프록시, 로드 밸런서, HTTP 캐시로도 사용됩니다.&lt;/p&gt;
&lt;p&gt;Nginx는 높은 동시 접속 처리를 위해 설계되었으며, 현재 전 세계 수많은 대규모 웹사이트에서 사용되고 있습니다.&lt;/p&gt;
&lt;h3 id="왜-nginx가-필요했을까"&gt;&lt;a href="#%ec%99%9c-nginx%ea%b0%80-%ed%95%84%ec%9a%94%ed%96%88%ec%9d%84%ea%b9%8c" class="header-anchor"&gt;&lt;/a&gt;왜 Nginx가 필요했을까?
&lt;/h3&gt;&lt;p&gt;과거에는 &lt;strong&gt;Apache 웹서버&lt;/strong&gt;가 업계 표준이었습니다. 하지만 2000년대 초반, 인터넷 사용자가 폭발적으로 증가하면서 &lt;strong&gt;C10k 문제&lt;/strong&gt;라는 병목 현상이 발생했습니다.&lt;/p&gt;
&lt;h2 id="c10k-문제"&gt;&lt;a href="#c10k-%eb%ac%b8%ec%a0%9c" class="header-anchor"&gt;&lt;/a&gt;C10k 문제
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;C10k 문제&lt;/strong&gt;는 &amp;ldquo;Connection 10,000&amp;quot;의 약자로, &lt;strong&gt;하나의 서버에서 동시에 10,000개의 클라이언트 연결을 처리하는 것&lt;/strong&gt;을 의미합니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;중요한 개념 구분&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;동시 처리 (Concurrent)&lt;/strong&gt;: 많은 연결을 동시에 유지하고 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;처리 속도 (Throughput)&lt;/strong&gt;: 초당 처리할 수 있는 요청의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;동시 접속 처리는 빠른 속도보다는 &lt;strong&gt;효율적인 자원 관리와 스케줄링&lt;/strong&gt;이 핵심입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure1.png"
 alt="Apache의 스레드 기반 구조 - 하나의 커넥션 당 하나의 스레드를 점유"&gt;
&lt;/figure&gt;

&lt;h3 id="apache의-구조적-한계"&gt;&lt;a href="#apache%ec%9d%98-%ea%b5%ac%ec%a1%b0%ec%a0%81-%ed%95%9c%ea%b3%84" class="header-anchor"&gt;&lt;/a&gt;Apache의 구조적 한계
&lt;/h3&gt;&lt;p&gt;기존 Apache는 다음과 같은 구조적 문제를 가지고 있었습니다:&lt;/p&gt;
&lt;h4 id="1-프로세스-기반-처리"&gt;&lt;a href="#1-%ed%94%84%eb%a1%9c%ec%84%b8%ec%8a%a4-%ea%b8%b0%eb%b0%98-%ec%b2%98%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;1. 프로세스 기반 처리
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;요청이 들어올 때마다 &lt;strong&gt;새로운 프로세스 또는 스레드를 생성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용자가 많아질수록 프로세스 수가 비례 증가&lt;/li&gt;
&lt;li&gt;결과적으로 &lt;strong&gt;메모리 부족&lt;/strong&gt; 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-높은-리소스-소비"&gt;&lt;a href="#2-%eb%86%92%ec%9d%80-%eb%a6%ac%ec%86%8c%ec%8a%a4-%ec%86%8c%eb%b9%84" class="header-anchor"&gt;&lt;/a&gt;2. 높은 리소스 소비
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Apache의 강력한 확장성 덕분에 다양한 모듈 추가 가능&lt;/li&gt;
&lt;li&gt;하지만 각 프로세스가 &lt;strong&gt;모든 모듈을 메모리에 로드&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;프로세스당 메모리 사용량 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-context-switching-오버헤드"&gt;&lt;a href="#3-context-switching-%ec%98%a4%eb%b2%84%ed%97%a4%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;3. Context-Switching 오버헤드
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;CPU 코어가 여러 프로세스를 번갈아 실행&lt;/li&gt;
&lt;li&gt;프로세스 전환 시 &lt;strong&gt;Context-Switching 비용&lt;/strong&gt; 발생&lt;/li&gt;
&lt;li&gt;요청이 많을수록 CPU 오버헤드 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 문제로 인해 Apache는 &lt;strong&gt;대규모 동시 접속 환경에 부적합&lt;/strong&gt;했습니다.&lt;/p&gt;
&lt;h2 id="nginx의-탄생"&gt;&lt;a href="#nginx%ec%9d%98-%ed%83%84%ec%83%9d" class="header-anchor"&gt;&lt;/a&gt;Nginx의 탄생
&lt;/h2&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure2.png"
 alt="Igor Sysoev - Nginx 개발자"&gt;
&lt;/figure&gt;

&lt;p&gt;2002년, 러시아의 개발자 **이고르 시쇼브(Igor Sysoev)**가 이 문제를 해결하기 위해 Nginx 개발을 시작했고, 2004년 첫 릴리즈를 공개했습니다.&lt;/p&gt;
&lt;h3 id="nginx의-핵심-목표"&gt;&lt;a href="#nginx%ec%9d%98-%ed%95%b5%ec%8b%ac-%eb%aa%a9%ed%91%9c" class="header-anchor"&gt;&lt;/a&gt;Nginx의 핵심 목표
&lt;/h3&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;h3 id="nginx의-주요-역할"&gt;&lt;a href="#nginx%ec%9d%98-%ec%a3%bc%ec%9a%94-%ec%97%ad%ed%95%a0" class="header-anchor"&gt;&lt;/a&gt;Nginx의 주요 역할
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP Server&lt;/strong&gt;: 정적 파일(HTML, CSS, JS, 이미지)을 빠르게 제공&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reverse Proxy Server&lt;/strong&gt;: 백엔드 애플리케이션 서버 앞단에서 요청 중계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load Balancer&lt;/strong&gt;: 여러 서버로 트래픽 분산&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mail Proxy Server&lt;/strong&gt;: 메일 서버 프록시 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="nginx의-내부-구조"&gt;&lt;a href="#nginx%ec%9d%98-%eb%82%b4%eb%b6%80-%ea%b5%ac%ec%a1%b0" class="header-anchor"&gt;&lt;/a&gt;Nginx의 내부 구조
&lt;/h2&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure3.png"
 alt="Event-Driven 방식의 Nginx 구조"&gt;
&lt;/figure&gt;

&lt;p&gt;Nginx는 &lt;strong&gt;1개의 Master Process&lt;/strong&gt;와 &lt;strong&gt;여러 개의 Worker Process&lt;/strong&gt;로 구성됩니다.&lt;/p&gt;
&lt;h3 id="master-process의-역할"&gt;&lt;a href="#master-process%ec%9d%98-%ec%97%ad%ed%95%a0" class="header-anchor"&gt;&lt;/a&gt;Master Process의 역할
&lt;/h3&gt;&lt;p&gt;Master Process는 다음 작업을 담당합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설정 파일 읽기 및 유효성 검증&lt;/li&gt;
&lt;li&gt;Worker Process 생성 및 관리&lt;/li&gt;
&lt;li&gt;설정 변경 시 Worker Process 재시작&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Master Process 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ps aux &lt;span class="p"&gt;|&lt;/span&gt; grep nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="worker-process의-역할"&gt;&lt;a href="#worker-process%ec%9d%98-%ec%97%ad%ed%95%a0" class="header-anchor"&gt;&lt;/a&gt;Worker Process의 역할
&lt;/h3&gt;&lt;p&gt;Worker Process가 실제 클라이언트 요청을 처리합니다:&lt;/p&gt;
&lt;h4 id="1-커넥션-관리"&gt;&lt;a href="#1-%ec%bb%a4%eb%84%a5%ec%85%98-%ea%b4%80%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;1. 커넥션 관리
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Master Process로부터 &lt;strong&gt;listen socket&lt;/strong&gt; 할당받음&lt;/li&gt;
&lt;li&gt;클라이언트와 커넥션 형성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep-Alive&lt;/strong&gt; 시간 동안 커넥션 유지&lt;/li&gt;
&lt;li&gt;하나의 Worker가 &lt;strong&gt;수천 개의 커넥션&lt;/strong&gt; 동시 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-non-blocking-io"&gt;&lt;a href="#2-non-blocking-io" class="header-anchor"&gt;&lt;/a&gt;2. Non-blocking I/O
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;커넥션에 요청이 없으면 다른 작업 처리&lt;/li&gt;
&lt;li&gt;요청이 들어오면 즉시 응답&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비동기 Event-Driven 방식&lt;/strong&gt;으로 효율적 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-thread-pool"&gt;&lt;a href="#3-thread-pool" class="header-anchor"&gt;&lt;/a&gt;3. Thread Pool
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;시간이 오래 걸리는 작업(파일 I/O, DB 쿼리)은 &lt;strong&gt;Thread Pool&lt;/strong&gt;에 위임&lt;/li&gt;
&lt;li&gt;Worker Process는 다른 요청 계속 처리&lt;/li&gt;
&lt;li&gt;Blocking 작업의 영향 최소화&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4-cpu-코어-최적화"&gt;&lt;a href="#4-cpu-%ec%bd%94%ec%96%b4-%ec%b5%9c%ec%a0%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;4. CPU 코어 최적화
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Worker Process는 &lt;strong&gt;CPU 코어 개수만큼 생성&lt;/strong&gt; 권장&lt;/li&gt;
&lt;li&gt;각 Worker를 특정 CPU 코어에 고정 (CPU Affinity)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context-Switching 최소화&lt;/strong&gt;로 성능 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# nginx.conf 설정 예시
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;worker_processes&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# CPU 코어 수만큼 자동 생성
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;worker_cpu_affinity&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# CPU 친화성 자동 설정
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="event-driven-아키텍처"&gt;&lt;a href="#event-driven-%ec%95%84%ed%82%a4%ed%85%8d%ec%b2%98" class="header-anchor"&gt;&lt;/a&gt;Event-Driven 아키텍처
&lt;/h3&gt;&lt;p&gt;Nginx는 &lt;strong&gt;멀티프로세스 + 싱글스레드 + Event-Driven&lt;/strong&gt; 방식으로 동작합니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;여러 커넥션을 &lt;strong&gt;Event Handler&lt;/strong&gt;가 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비동기 Non-blocking&lt;/strong&gt; 방식으로 처리&lt;/li&gt;
&lt;li&gt;먼저 준비된 이벤트부터 순차 처리&lt;/li&gt;
&lt;li&gt;대기 중인 프로세스 없이 &lt;strong&gt;자원 효율성 극대화&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이는 Apache처럼 요청을 기다리며 방치되는 프로세스가 없어 &lt;strong&gt;메모리와 CPU를 효율적으로 사용&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="nginx의-장단점"&gt;&lt;a href="#nginx%ec%9d%98-%ec%9e%a5%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;Nginx의 장단점
&lt;/h2&gt;&lt;h3 id="장점"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;장점
&lt;/h3&gt;&lt;h4 id="1-높은-동시-접속-처리-능력"&gt;&lt;a href="#1-%eb%86%92%ec%9d%80-%eb%8f%99%ec%8b%9c-%ec%a0%91%ec%86%8d-%ec%b2%98%eb%a6%ac-%eb%8a%a5%eb%a0%a5" class="header-anchor"&gt;&lt;/a&gt;1. 높은 동시 접속 처리 능력
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Apache 대비 &lt;strong&gt;동시 커넥션 수 10배 이상&lt;/strong&gt; 증가&lt;/li&gt;
&lt;li&gt;동일 커넥션에서 &lt;strong&gt;처리 속도 2배&lt;/strong&gt; 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-낮은-리소스-사용"&gt;&lt;a href="#2-%eb%82%ae%ec%9d%80-%eb%a6%ac%ec%86%8c%ec%8a%a4-%ec%82%ac%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;2. 낮은 리소스 사용
&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;/ul&gt;
&lt;h4 id="3-무중단-설정-리로드"&gt;&lt;a href="#3-%eb%ac%b4%ec%a4%91%eb%8b%a8-%ec%84%a4%ec%a0%95-%eb%a6%ac%eb%a1%9c%eb%93%9c" class="header-anchor"&gt;&lt;/a&gt;3. 무중단 설정 리로드
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nginx -s reload &lt;span class="c1"&gt;# 서비스 중단 없이 설정 적용&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Master Process가 새 설정 읽기&lt;/li&gt;
&lt;li&gt;기존 Worker는 현재 요청 완료 후 종료&lt;/li&gt;
&lt;li&gt;새 Worker가 새 설정으로 요청 처리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서비스 중단 없이 설정 변경 가능&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4-우수한-정적-파일-처리"&gt;&lt;a href="#4-%ec%9a%b0%ec%88%98%ed%95%9c-%ec%a0%95%ec%a0%81-%ed%8c%8c%ec%9d%bc-%ec%b2%98%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;4. 우수한 정적 파일 처리
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;이미지, CSS, JS 등 정적 콘텐츠를 빠르게 제공&lt;/li&gt;
&lt;li&gt;Apache보다 정적 파일 처리 성능 우수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="단점"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;h4 id="1-동적-모듈-개발의-어려움"&gt;&lt;a href="#1-%eb%8f%99%ec%a0%81-%eb%aa%a8%eb%93%88-%ea%b0%9c%eb%b0%9c%ec%9d%98-%ec%96%b4%eb%a0%a4%ec%9b%80" class="header-anchor"&gt;&lt;/a&gt;1. 동적 모듈 개발의 어려움
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;모듈 추가 시 Worker Process 재시작 필요&lt;/li&gt;
&lt;li&gt;Apache처럼 손쉬운 모듈 개발 어려움&lt;/li&gt;
&lt;li&gt;대신 Lua 스크립팅으로 어느 정도 보완 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-windows-환경-제한"&gt;&lt;a href="#2-windows-%ed%99%98%ea%b2%bd-%ec%a0%9c%ed%95%9c" class="header-anchor"&gt;&lt;/a&gt;2. Windows 환경 제한
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Linux/Unix 환경에 최적화&lt;/li&gt;
&lt;li&gt;Windows에서는 성능과 안정성 저하&lt;/li&gt;
&lt;li&gt;프로덕션 환경에서는 Linux 사용 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-htaccess-미지원"&gt;&lt;a href="#3-htaccess-%eb%af%b8%ec%a7%80%ec%9b%90" class="header-anchor"&gt;&lt;/a&gt;3. .htaccess 미지원
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Apache의 &lt;code&gt;.htaccess&lt;/code&gt; 파일 사용 불가&lt;/li&gt;
&lt;li&gt;모든 설정을 중앙 설정 파일에서 관리&lt;/li&gt;
&lt;li&gt;호스팅 환경에서 유연성 떨어질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="nginx의-주요-기능"&gt;&lt;a href="#nginx%ec%9d%98-%ec%a3%bc%ec%9a%94-%ea%b8%b0%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;Nginx의 주요 기능
&lt;/h2&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure4.png"
 alt="Nginx 주요 기능 다이어그램"&gt;
&lt;/figure&gt;

&lt;h3 id="1-리버스-프록시-reverse-proxy"&gt;&lt;a href="#1-%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9c-reverse-proxy" class="header-anchor"&gt;&lt;/a&gt;1. 리버스 프록시 (Reverse Proxy)
&lt;/h3&gt;&lt;p&gt;리버스 프록시는 클라이언트와 백엔드 서버 사이에서 &lt;strong&gt;중계자&lt;/strong&gt; 역할을 합니다.&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure5.png"
 alt="리버스 프록시 구조"&gt;
&lt;/figure&gt;

&lt;h4 id="주요-이점"&gt;&lt;a href="#%ec%a3%bc%ec%9a%94-%ec%9d%b4%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;주요 이점
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;보안 강화&lt;/strong&gt;: 실제 서버 IP 숨김&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;SSL 처리&lt;/strong&gt;: HTTPS 암호화/복호화 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 리버스 프록시 설정 예시
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="실무-활용-패턴"&gt;&lt;a href="#%ec%8b%a4%eb%ac%b4-%ed%99%9c%ec%9a%a9-%ed%8c%a8%ed%84%b4" class="header-anchor"&gt;&lt;/a&gt;실무 활용 패턴
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nginx + Apache&lt;/strong&gt;: Nginx가 정적 파일 처리, Apache가 동적 처리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx + Node.js/Python/Java&lt;/strong&gt;: Nginx가 프론트엔드, 백엔드 애플리케이션 보호&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx + Nginx&lt;/strong&gt;: 여러 Nginx 서버를 계층적으로 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-로드-밸런싱-load-balancing"&gt;&lt;a href="#2-%eb%a1%9c%eb%93%9c-%eb%b0%b8%eb%9f%b0%ec%8b%b1-load-balancing" class="header-anchor"&gt;&lt;/a&gt;2. 로드 밸런싱 (Load Balancing)
&lt;/h3&gt;&lt;p&gt;여러 백엔드 서버로 &lt;strong&gt;트래픽을 분산&lt;/strong&gt;하여 부하를 균등하게 배분합니다.&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure6.png"
 alt="로드 밸런싱 구조"&gt;
&lt;/figure&gt;

&lt;h4 id="로드-밸런싱-알고리즘"&gt;&lt;a href="#%eb%a1%9c%eb%93%9c-%eb%b0%b8%eb%9f%b0%ec%8b%b1-%ec%95%8c%ea%b3%a0%eb%a6%ac%ec%a6%98" class="header-anchor"&gt;&lt;/a&gt;로드 밸런싱 알고리즘
&lt;/h4&gt;&lt;h5 id="round-robin-기본값"&gt;&lt;a href="#round-robin-%ea%b8%b0%eb%b3%b8%ea%b0%92" class="header-anchor"&gt;&lt;/a&gt;Round Robin (기본값)
&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;순차적으로 요청을 각 서버에 분배&lt;/li&gt;
&lt;li&gt;가장 간단하고 공평한 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend1.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend2.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend3.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="least-connections"&gt;&lt;a href="#least-connections" class="header-anchor"&gt;&lt;/a&gt;Least Connections
&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;현재 연결 수가 가장 적은 서버로 전송&lt;/li&gt;
&lt;li&gt;처리 시간이 다른 요청에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;least_conn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend1.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend2.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="ip-hash"&gt;&lt;a href="#ip-hash" class="header-anchor"&gt;&lt;/a&gt;IP Hash
&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;클라이언트 IP 해시값으로 서버 결정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션 유지&lt;/strong&gt;(Session Persistence)에 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ip_hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend1.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend2.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="weight-가중치"&gt;&lt;a href="#weight-%ea%b0%80%ec%a4%91%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;Weight (가중치)
&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;서버 성능에 따라 가중치 부여&lt;/li&gt;
&lt;li&gt;고성능 서버에 더 많은 요청 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend1.example.com&lt;/span&gt; &lt;span class="s"&gt;weight=3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend2.example.com&lt;/span&gt; &lt;span class="s"&gt;weight=2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend3.example.com&lt;/span&gt; &lt;span class="s"&gt;weight=1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="health-check-헬스-체크"&gt;&lt;a href="#health-check-%ed%97%ac%ec%8a%a4-%ec%b2%b4%ed%81%ac" class="header-anchor"&gt;&lt;/a&gt;Health Check (헬스 체크)
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend1.example.com&lt;/span&gt; &lt;span class="s"&gt;max_fails=3&lt;/span&gt; &lt;span class="s"&gt;fail_timeout=30s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;backend2.example.com&lt;/span&gt; &lt;span class="s"&gt;max_fails=3&lt;/span&gt; &lt;span class="s"&gt;fail_timeout=30s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_fails&lt;/code&gt;: 실패 허용 횟수&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fail_timeout&lt;/code&gt;: 서버를 다운으로 간주할 시간&lt;/li&gt;
&lt;li&gt;장애 서버 자동 제외로 &lt;strong&gt;가용성 향상&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-ssltls-터미네이션"&gt;&lt;a href="#3-ssltls-%ed%84%b0%eb%af%b8%eb%84%a4%ec%9d%b4%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;3. SSL/TLS 터미네이션
&lt;/h3&gt;&lt;p&gt;Nginx가 &lt;strong&gt;클라이언트와 HTTPS 통신&lt;/strong&gt;, &lt;strong&gt;백엔드와 HTTP 통신&lt;/strong&gt;을 담당합니다.&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_about_nginx/figure7.png"
 alt="SSL 터미네이션 구조"&gt;
&lt;/figure&gt;

&lt;h4 id="주요-이점-1"&gt;&lt;a href="#%ec%a3%bc%ec%9a%94-%ec%9d%b4%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&gt;주요 이점
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;백엔드 서버의 &lt;strong&gt;SSL 처리 부담 제거&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;중앙화된 인증서 관리&lt;/li&gt;
&lt;li&gt;백엔드는 &lt;strong&gt;비즈니스 로직에 집중&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Nginx와 백엔드는 같은 내부 네트워크에서 HTTP 통신 (보안상 안전)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/path/to/cert.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/path/to/key.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;HIGH:!aNULL:!MD5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="http2-지원"&gt;&lt;a href="#http2-%ec%a7%80%ec%9b%90" class="header-anchor"&gt;&lt;/a&gt;HTTP/2 지원
&lt;/h4&gt;&lt;p&gt;Nginx는 HTTP/2를 지원하여:&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;/li&gt;
&lt;li&gt;&lt;strong&gt;Server Push&lt;/strong&gt;: 클라이언트 요청 전 리소스 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-캐싱-caching"&gt;&lt;a href="#4-%ec%ba%90%ec%8b%b1-caching" class="header-anchor"&gt;&lt;/a&gt;4. 캐싱 (Caching)
&lt;/h3&gt;&lt;p&gt;서버 응답을 &lt;strong&gt;메모리나 디스크에 저장&lt;/strong&gt;하여 반복 요청 시 빠르게 응답합니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 캐시 경로 설정
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;proxy_cache_path&lt;/span&gt; &lt;span class="s"&gt;/var/cache/nginx&lt;/span&gt; &lt;span class="s"&gt;levels=1:2&lt;/span&gt; &lt;span class="s"&gt;keys_zone=my_cache:10m&lt;/span&gt; &lt;span class="s"&gt;max_size=1g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_cache&lt;/span&gt; &lt;span class="s"&gt;my_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="mi"&gt;60m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# 200 응답은 60분 캐싱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="mi"&gt;10m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# 404 응답은 10분 캐싱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="캐싱-전략"&gt;&lt;a href="#%ec%ba%90%ec%8b%b1-%ec%a0%84%eb%9e%b5" class="header-anchor"&gt;&lt;/a&gt;캐싱 전략
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;프록시 캐싱&lt;/strong&gt;: 백엔드 응답 캐싱&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FastCGI 캐싱&lt;/strong&gt;: PHP-FPM 등 동적 콘텐츠 캐싱&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정적 파일 캐싱&lt;/strong&gt;: 브라우저 캐시 헤더 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 정적 파일 캐시 헤더 설정
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="s"&gt;\.(jpg|jpeg|png|gif|ico|css|js)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;1y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;public,&lt;/span&gt; &lt;span class="s"&gt;immutable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="5-압축-gzip"&gt;&lt;a href="#5-%ec%95%95%ec%b6%95-gzip" class="header-anchor"&gt;&lt;/a&gt;5. 압축 (Gzip)
&lt;/h3&gt;&lt;p&gt;응답 데이터를 압축하여 &lt;strong&gt;네트워크 대역폭 절약&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;gzip&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;gzip_vary&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;gzip_min_length&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;gzip_types&lt;/span&gt; &lt;span class="s"&gt;text/plain&lt;/span&gt; &lt;span class="s"&gt;text/css&lt;/span&gt; &lt;span class="s"&gt;text/xml&lt;/span&gt; &lt;span class="s"&gt;text/javascript&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;application/x-javascript&lt;/span&gt; &lt;span class="s"&gt;application/xml+rss&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;application/json&lt;/span&gt; &lt;span class="s"&gt;application/javascript&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;텍스트 기반 콘텐츠 60-80% 압축&lt;/li&gt;
&lt;li&gt;전송 시간 단축으로 사용자 경험 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="6-rate-limiting-속도-제한"&gt;&lt;a href="#6-rate-limiting-%ec%86%8d%eb%8f%84-%ec%a0%9c%ed%95%9c" class="header-anchor"&gt;&lt;/a&gt;6. Rate Limiting (속도 제한)
&lt;/h3&gt;&lt;p&gt;DDoS 공격 방어 및 서버 보호&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Zone 정의
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;limit_req_zone&lt;/span&gt; &lt;span class="nv"&gt;$binary_remote_addr&lt;/span&gt; &lt;span class="s"&gt;zone=mylimit:10m&lt;/span&gt; &lt;span class="s"&gt;rate=10r/s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/api/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;limit_req&lt;/span&gt; &lt;span class="s"&gt;zone=mylimit&lt;/span&gt; &lt;span class="s"&gt;burst=20&lt;/span&gt; &lt;span class="s"&gt;nodelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;IP당 초당 요청 수 제한&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;burst&lt;/strong&gt;: 순간적인 트래픽 증가 허용&lt;/li&gt;
&lt;li&gt;API 서버 보호에 필수적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="nginx-vs-apache-어떤-것을-선택할까"&gt;&lt;a href="#nginx-vs-apache-%ec%96%b4%eb%96%a4-%ea%b2%83%ec%9d%84-%ec%84%a0%ed%83%9d%ed%95%a0%ea%b9%8c" class="header-anchor"&gt;&lt;/a&gt;Nginx vs Apache: 어떤 것을 선택할까?
&lt;/h2&gt;&lt;h3 id="nginx를-선택해야-하는-경우"&gt;&lt;a href="#nginx%eb%a5%bc-%ec%84%a0%ed%83%9d%ed%95%b4%ec%95%bc-%ed%95%98%eb%8a%94-%ea%b2%bd%ec%9a%b0" class="header-anchor"&gt;&lt;/a&gt;Nginx를 선택해야 하는 경우
&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;li&gt;최신 프로토콜(HTTP/2, HTTP/3) 지원 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="apache를-선택해야-하는-경우"&gt;&lt;a href="#apache%eb%a5%bc-%ec%84%a0%ed%83%9d%ed%95%b4%ec%95%bc-%ed%95%98%eb%8a%94-%ea%b2%bd%ec%9a%b0" class="header-anchor"&gt;&lt;/a&gt;Apache를 선택해야 하는 경우
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.htaccess&lt;/code&gt; 파일 기반 설정이 필요한 경우&lt;/li&gt;
&lt;li&gt;다양한 써드파티 모듈이 필요한 경우&lt;/li&gt;
&lt;li&gt;Windows 환경에서 사용해야 하는 경우&lt;/li&gt;
&lt;li&gt;레거시 애플리케이션 호환성이 중요한 경우&lt;/li&gt;
&lt;li&gt;동적 모듈 개발이 빈번한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="최적의-조합-nginx--apache"&gt;&lt;a href="#%ec%b5%9c%ec%a0%81%ec%9d%98-%ec%a1%b0%ed%95%a9-nginx--apache" class="header-anchor"&gt;&lt;/a&gt;최적의 조합: Nginx + Apache
&lt;/h3&gt;&lt;p&gt;많은 기업이 &lt;strong&gt;Nginx를 프론트엔드&lt;/strong&gt;, &lt;strong&gt;Apache를 백엔드&lt;/strong&gt;로 사용합니다:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[클라이언트] → [Nginx] → [Apache] → [애플리케이션]
 정적 파일 동적 처리
 SSL 처리 PHP/Python
 캐싱 모듈 활용
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="실무-팁"&gt;&lt;a href="#%ec%8b%a4%eb%ac%b4-%ed%8c%81" class="header-anchor"&gt;&lt;/a&gt;실무 팁
&lt;/h2&gt;&lt;h3 id="1-worker-connections-설정"&gt;&lt;a href="#1-worker-connections-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;1. Worker Connections 설정
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Worker당 처리 가능한 커넥션 수
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="s"&gt;epoll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Linux에서 최적의 이벤트 모델
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-keepalive-최적화"&gt;&lt;a href="#2-keepalive-%ec%b5%9c%ec%a0%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;2. Keepalive 최적화
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;keepalive_timeout&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;keepalive_requests&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-버퍼-크기-조정"&gt;&lt;a href="#3-%eb%b2%84%ed%8d%bc-%ed%81%ac%ea%b8%b0-%ec%a1%b0%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;3. 버퍼 크기 조정
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;client_body_buffer_size&lt;/span&gt; &lt;span class="s"&gt;16K&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;client_header_buffer_size&lt;/span&gt; &lt;span class="mi"&gt;1k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;8m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;large_client_header_buffers&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;8k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-로그-최적화"&gt;&lt;a href="#4-%eb%a1%9c%ea%b7%b8-%ec%b5%9c%ec%a0%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;4. 로그 최적화
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="s"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt; &lt;span class="s"&gt;buffer=32k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;error_log&lt;/span&gt; &lt;span class="s"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="5-보안-강화"&gt;&lt;a href="#5-%eb%b3%b4%ec%95%88-%ea%b0%95%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;5. 보안 강화
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 버전 정보 숨기기
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server_tokens&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 보안 헤더 추가
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;SAMEORIGIN&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;nosniff&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-XSS-Protection&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;mode=block&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="마무리"&gt;&lt;a href="#%eb%a7%88%eb%ac%b4%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;마무리
&lt;/h2&gt;&lt;p&gt;Nginx는 &lt;strong&gt;현대 웹 인프라의 핵심 구성 요소&lt;/strong&gt;로 자리잡았습니다. Event-Driven 아키텍처를 통한 높은 성능과 효율성으로 Netflix, Airbnb, GitHub 등 대규모 서비스에서 사용되고 있습니다.&lt;/p&gt;
&lt;p&gt;Apache의 안정성과 확장성도 여전히 가치가 있지만, 대규모 트래픽 처리와 리소스 효율성이 중요한 현대 웹 환경에서는 &lt;strong&gt;Nginx가 더 적합한 선택&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;추천 학습 경로&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;로컬 환경에서 Nginx 설치 및 기본 설정 실습&lt;/li&gt;
&lt;li&gt;리버스 프록시 구성해보기&lt;/li&gt;
&lt;li&gt;로드 밸런싱 설정 및 테스트&lt;/li&gt;
&lt;li&gt;SSL 인증서 적용 (Let&amp;rsquo;s Encrypt)&lt;/li&gt;
&lt;li&gt;성능 모니터링 및 최적화&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="참고-자료"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;참고 자료
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://nginx.org/en/docs/" target="_blank" rel="noopener"
 &gt;Nginx 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.digitalocean.com/community/tools/nginx" target="_blank" rel="noopener"
 &gt;Nginx 설정 생성기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.nginx.com/blog/tuning-nginx/" target="_blank" rel="noopener"
 &gt;Nginx Performance Tuning Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;다만 주의할 점&lt;/strong&gt;: Nginx는 Windows 환경에서 제한적인 성능과 호환성을 보이므로, 프로덕션 환경에서는 반드시 &lt;strong&gt;Linux/Unix 시스템&lt;/strong&gt;을 사용하는 것을 권장합니다!&lt;/p&gt;</description></item><item><title>도커(Docker) 설치 &amp; 명령어 사용방법 총정리</title><link>https://0andwild.com/posts/221025_docker_command/</link><pubDate>Tue, 25 Oct 2022 17:59:31 +0900</pubDate><guid>https://0andwild.com/posts/221025_docker_command/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post 도커(Docker) 설치 &amp; 명령어 사용방법 총정리" /&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/featured.png"
 alt="docker"&gt;
&lt;/figure&gt;

&lt;p&gt;지난 글에 이어 도커의 명령어와 사용방법을 정리해볼까 합니다 =)&lt;/p&gt;
&lt;h2 id="docker-설치하기"&gt;&lt;a href="#docker-%ec%84%a4%ec%b9%98%ed%95%98%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;Docker 설치하기
&lt;/h2&gt;&lt;p&gt;우선 도커를 사용하려면 설치를 해주어야 겠죠?&lt;/p&gt;
&lt;p&gt;필자의 경우 AWS EC2 인스턴스로 Ubuntu 환경에서 Docker를 설치하였습니다.&lt;/p&gt;
&lt;p&gt;Ubuntu 및 다른 환경에서 설치가 필요하신 경우 아래 공식 문서를 참고해주세요!&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.docker.com/engine/install/ubuntu/" target="_blank" rel="noopener"
 &gt;Install Docker Engine on Ubuntu - docs.docker.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="docker-구버전-제거-및-신버전-설치"&gt;&lt;a href="#docker-%ea%b5%ac%eb%b2%84%ec%a0%84-%ec%a0%9c%ea%b1%b0-%eb%b0%8f-%ec%8b%a0%eb%b2%84%ec%a0%84-%ec%84%a4%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;Docker 구버전 제거 및 신버전 설치
&lt;/h3&gt;&lt;p&gt;만약 Docker 구버전을 삭제 후 신버전을 설치하려 한다면 아래 명령어를 통해 구버전을 삭제해주도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu 기준&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get remove docker docker-engine docker.io containerd runc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;저장소 업데이트&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;apt가 https를 통해 repository를 사용할 수 있도록 패키지 설치&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Docker 저장소 키를 apt에 등록&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsSL https://download.docker.com/linux/ubuntu/gpg &lt;span class="p"&gt;|&lt;/span&gt; sudo apt-key add -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Docker 저장소 등록&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo add-apt-repository &lt;span class="s2"&gt;&amp;#34;deb [arch=amd64] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;lsb_release -cs&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;지금까지 작업 내용 반영을 위해 apt update&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Docker 설치&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;위의 명령어를 통해 Docker 설치 작업을 끝냈다면 확인을 해봐야 겠죠?!&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure1.png"
 alt="docker version 명령어 실행 화면"&gt;
&lt;/figure&gt;

&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;또는&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker run hello-world
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure2.png"
 alt="docker run hello-world 실행 화면"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure3.png"
 alt="hello-world 컨테이너 실행 결과"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위와 같이 run 명령어를 사용하면 hello-world 이미지를 local에서 찾고 만약 없으면 docker hub에서 이미지를 다운받고 실행시켜 컨테이너로 띄워질 것입니다 =)&lt;/p&gt;
&lt;h3 id="docker-권한-설정"&gt;&lt;a href="#docker-%ea%b6%8c%ed%95%9c-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;Docker 권한 설정
&lt;/h3&gt;&lt;p&gt;만약 도커 명령어 실행시 다음과 같은 문구가 나온다면 걱정하지 마세요 ㅎㅎ&lt;/p&gt;
&lt;p&gt;이 메세지는 Docker를 root 외의 사용자가 사용할 수 있는 권한이 없어 그런 것 입니다 =)&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure4.png"
 alt="권한 에러 메시지"&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;우선 docker의 권한을 확인합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /etc/group &lt;span class="p"&gt;|&lt;/span&gt; grep docker
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure5.png"
 alt="docker 그룹 확인"&gt;
&lt;/figure&gt;

&lt;p&gt;필자의 경우 이미 사용자 권한을 추가하여 뒤에 ubuntu 라는 사용자 이름으로 권한이 추가되어 있습니다.&lt;/p&gt;
&lt;p&gt;만약 추가가 되어 있지 않다면 &lt;code&gt;docker:x:999:&lt;/code&gt; 와 같은 문구를 확인할 수 있을 겁니다!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker 그룹에 사용할 사용자 아이디를 추가해줍니다&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo usermod -aG docker &lt;span class="o"&gt;[&lt;/span&gt;사용자이름&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure6.png"
 alt="사용자 추가 명령어 실행"&gt;
&lt;/figure&gt;

&lt;p&gt;사용자이름은 예시로 필자는 위의 사진과 같이 ubuntu라는 사용자 이름으로 사용중이기에 ubuntu를 넣어 주었습니다.&lt;/p&gt;
&lt;p&gt;Linux의 경우 기본유저이름이 ec2-user로 잡혀 있을 겁니다 =)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;시스템 재시작을 해줍니다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo reboot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;이제 docker 명령어 앞에 sudo 를 빼고 다시 버전확인을 해보도록 하겠습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure7.png"
 alt="권한 설정 후 docker version 실행"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure8.png"
 alt="Client와 Server 정보 확인"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;다음과 같이 Client와 Server 정보가 나오면 정상적으로 권한 부여가 이루어진겁니다. =)&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;Got permission denied ...&lt;/code&gt; 메세지가 다시 나온다면 권한 설정이 잘 이루어지지 않을 것이니 확인을 다시 해보신 후 추가적인 에러메세지를 함께 검색해보시길 바랍니다!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;에러메세지를 댓글에 공유 해주시면 저도 함께 찾아보도록 하겠습니다 ㅎㅎ&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="windows--mac-os-docker-desktop"&gt;&lt;a href="#windows--mac-os-docker-desktop" class="header-anchor"&gt;&lt;/a&gt;Windows &amp;amp; Mac OS Docker Desktop
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;+ 추가적으로 Windows 와 Mac OS의 경우 docker desktop을 지원하여 간편한 설치를 통해 GUI를 사용할 수 있습니다!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개인 사용자나 250인 이하 또는 $1000만 달러 미만 매출의 회사에서만 무료로 사용할 수 있다니 참고 바랍니다!&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure9.png"
 alt="Docker Desktop"&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;설치방법은 아래 공식문서 링크를 참고하세요 =)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.docker.com/desktop/install/windows-install/" target="_blank" rel="noopener"
 &gt;Install Docker Desktop on Windows - docs.docker.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.docker.com/desktop/install/mac-install/" target="_blank" rel="noopener"
 &gt;Install Docker Desktop on Mac - docs.docker.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="docker-명령어"&gt;&lt;a href="#docker-%eb%aa%85%eb%a0%b9%ec%96%b4" class="header-anchor"&gt;&lt;/a&gt;Docker 명령어
&lt;/h2&gt;&lt;h3 id="docker-image-검색"&gt;&lt;a href="#docker-image-%ea%b2%80%ec%83%89" class="header-anchor"&gt;&lt;/a&gt;Docker Image 검색
&lt;/h3&gt;&lt;p&gt;Docker 공식 registry인 Docker hub에서 이미지를 검색합니다.&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure10.png"
 alt="docker search 실행 화면"&gt;
&lt;/figure&gt;

&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker search &lt;span class="o"&gt;[&lt;/span&gt;검색 할 Image이름&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="이미지-다운받기"&gt;&lt;a href="#%ec%9d%b4%eb%af%b8%ec%a7%80-%eb%8b%a4%ec%9a%b4%eb%b0%9b%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;이미지 다운받기
&lt;/h3&gt;&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure11.png"
 alt="docker pull 명령어"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure12.png"
 alt="이미지 다운로드 진행 화면"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker pull &lt;span class="o"&gt;[&lt;/span&gt;이미지이름&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="o"&gt;[&lt;/span&gt;태그&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;일반적으로 이미지 생성시 태그명을 따로 지정하지 않으면 default 값으로 latest가 붙습니다 =)&lt;/p&gt;
&lt;h3 id="이미지를-docker-hub-계정에-push-하기"&gt;&lt;a href="#%ec%9d%b4%eb%af%b8%ec%a7%80%eb%a5%bc-docker-hub-%ea%b3%84%ec%a0%95%ec%97%90-push-%ed%95%98%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;이미지를 docker hub 계정에 push 하기
&lt;/h3&gt;&lt;p&gt;필자의 경우 좀전에 받은 hello-world 이미지를 필자의 docker-hub 계정에 push 해보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure13.png"
 alt="Docker Hub repository 생성"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure14.png"
 alt="repository 생성 완료"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;먼저 push를 하기에 앞서 docker-hub에 hello-world 라는 repository를 만들어두도록 하겠습니다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker push &lt;span class="o"&gt;[&lt;/span&gt;docker-hub ID&lt;span class="o"&gt;]&lt;/span&gt;/&lt;span class="o"&gt;[&lt;/span&gt;이미지 이름&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="o"&gt;[&lt;/span&gt;태그&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure15.png"
 alt="push 실패 에러 메시지"&gt;
&lt;/figure&gt;

&lt;p&gt;음&amp;hellip; 다음과 같은 메세지와 함께 이미지 push가 실패하였네요. =(&lt;/p&gt;
&lt;p&gt;이러한 이유는 docker hub의 repository 이름과 로컬의 도커 이미지 repository 이름을 똑같게 해줘야 한다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결방법&lt;/strong&gt;&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure16.png"
 alt="docker image tag 명령어 실행"&gt;
&lt;/figure&gt;

&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker image tag &lt;span class="o"&gt;[&lt;/span&gt;이미지 repo 이름&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="o"&gt;[&lt;/span&gt;태그&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;변경할 이미지 repo이름 지정&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="o"&gt;[&lt;/span&gt;태그&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 방식은 해당 이미지의 이름을 바꾸는 것이 아닌 이미지를 복사하여 새로운 이름의 이미지를 생성해줍니다. =)&lt;/p&gt;
&lt;p&gt;필자의 경우 tag 부분은 변경할 repo 도 default인 latest를 사용할거기 때문에 따로 지정을 해주지 않았습니다.&lt;/p&gt;
&lt;p&gt;다시 push를 해보도록 하겠습니다 ㅎㅎ.&lt;/p&gt;
&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure17.png"
 alt="push 재시도"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure18.png"
 alt="push 성공"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이제 정상적으로 이미지가 push 된 것을 확인할 수 있습니다! =)&lt;/p&gt;
&lt;h3 id="다운받은-이미지-확인"&gt;&lt;a href="#%eb%8b%a4%ec%9a%b4%eb%b0%9b%ec%9d%80-%ec%9d%b4%eb%af%b8%ec%a7%80-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;다운받은 이미지 확인
&lt;/h3&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure19.png"
 alt="docker images 명령어 실행 화면"&gt;
&lt;/figure&gt;

&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker images
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="docker-image를-실행하여-container로-띄우기"&gt;&lt;a href="#docker-image%eb%a5%bc-%ec%8b%a4%ed%96%89%ed%95%98%ec%97%ac-container%eb%a1%9c-%eb%9d%84%ec%9a%b0%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;Docker image를 실행하여 container로 띄우기
&lt;/h3&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure20.png"
 alt="docker run 명령어 구조"&gt;
&lt;/figure&gt;

&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d -i -t --name &lt;span class="o"&gt;[&lt;/span&gt;생성할 컨테이너 name 설정&lt;span class="o"&gt;]&lt;/span&gt; -p &lt;span class="o"&gt;[&lt;/span&gt;host port:container port&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;image name or ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;필자의 경우 이미 springboot project를 docker image로 빌드 해둔 것이 있어 해당 image를 실행시켜 컨테이너로 띄워보도록 하겠습니다. =)&lt;/p&gt;
&lt;p&gt;일반적으로는 &lt;code&gt;-i&lt;/code&gt; &lt;code&gt;-t&lt;/code&gt; 옵션을 함께 사용하여 &lt;code&gt;-it&lt;/code&gt; 이렇게 옵션을 주기도 합니다 =)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;host port&lt;/strong&gt;는 컨테이너가 띄워진 후 사용자가 접근할 외부 port이고 &lt;strong&gt;container port&lt;/strong&gt;는 다음과 같이 docker file을 이용하여 docker image를 빌드할때 지정해준 port라 생각하시면 될 것 같습니다. =)&lt;/p&gt;
&lt;p&gt;필자의 경우 이미지를 생성할 때 .yml 파일에 존재하는 local, dev, prod 환경 중 dev환경으로 지정해주었고 해당 dev 환경의 server port는 8081로 지정해주었기 때문에 containerport를 8081로 지정해주었습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure21.png"
 alt="Dockerfile 설정"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure22.png"
 alt="yml 파일 설정"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;image name or ID 는 실행시킬 이미지의 이름 또는 해당 이미지의 아이디 값을 넣어주면 됩니다. =)&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure23.png"
 alt="컨테이너 실행"&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Docker의 다양한 옵션에 대한 내용은 아래에 정리를 해두었으니 참고 바랍니다 !&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이제 이미지를 실행 시켰으니 컨테이너가 잘 띄워 졌는지 확인을 해봐야 겠죠?&lt;/p&gt;
&lt;h3 id="실행중인-컨테이너-확인"&gt;&lt;a href="#%ec%8b%a4%ed%96%89%ec%a4%91%ec%9d%b8-%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88-%ed%99%95%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;실행중인 컨테이너 확인
&lt;/h3&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure24.png"
 alt="docker ps 명령어"&gt;
&lt;/figure&gt;

&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo docker ps
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure25.png"
 alt="브라우저 접속 화면"&gt;
&lt;/figure&gt;

&lt;p&gt;컨테이너가 실행된 후 &lt;code&gt;http://[public ip]:8080&lt;/code&gt; 으로 접속을 하니 서버가 잘 띄워진 것을 확인 할 수 있습니다! =)&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure26.png"
 alt="API 응답 확인"&gt;
&lt;/figure&gt;

&lt;p&gt;또한 현재 띄워진 서버의 운영 환경이 dockerfile에서 지정해준 dev1 환경이라는 것을 만들어둔 api를 통해 확인할 수 있었습니다. =)&lt;/p&gt;
&lt;h3 id="포트-매핑-실험"&gt;&lt;a href="#%ed%8f%ac%ed%8a%b8-%eb%a7%a4%ed%95%91-%ec%8b%a4%ed%97%98" class="header-anchor"&gt;&lt;/a&gt;포트 매핑 실험
&lt;/h3&gt;&lt;p&gt;그럼 여기서 추가적으로 실험을 하나더 해보도록 하겠습니다. 필자는 아까 말했듯이 docker file에서 dev1으로 운영환경을 지정해주었고 해당 dockerfile을 통해 빌드된 이미지는 내부적으로 server port가 8081 인 이미지 입니다.&lt;/p&gt;
&lt;p&gt;그럼 이 이미지를 실행시킬 때 &lt;code&gt;-p 8080:8080&lt;/code&gt; 으로 container port를 8081이 아닌 8080으로 주게되면 어떻게 될까요??&lt;/p&gt;
&lt;p&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure27.png"
 alt="잘못된 포트 매핑 실행"&gt;
&lt;/figure&gt;

&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure28.png"
 alt="컨테이너는 실행됨"&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;우선 컨테이너는 정상적으로 띄워졌군요!&lt;/p&gt;
&lt;p&gt;그럼 해당 서버의 ip와 외부접근 port인 8080으로 접근을 시도해보도록 하겠습니다!&lt;/p&gt;
&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221025_docker_command/figure29.png"
 alt="서버 접속 실패"&gt;
&lt;/figure&gt;

&lt;p&gt;이번에는 컨테이너는 정상적으로 띄워 졌지만 서버는 제대로 작동을 하지 않는 것 같군요 =)&lt;/p&gt;
&lt;p&gt;이렇게 dockerfile에서 설정한 운영환경 지정이 제대로 동작하는 것을 확인 할 수 있습니다!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="기본적인-docker-명령어"&gt;&lt;a href="#%ea%b8%b0%eb%b3%b8%ec%a0%81%ec%9d%b8-docker-%eb%aa%85%eb%a0%b9%ec%96%b4" class="header-anchor"&gt;&lt;/a&gt;기본적인 Docker 명령어
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker pull &lt;span class="o"&gt;[&lt;/span&gt;다운받을 이미지 이름&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="o"&gt;[&lt;/span&gt;태그&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker push &lt;span class="o"&gt;[&lt;/span&gt;docker-hub ID&lt;span class="o"&gt;]&lt;/span&gt;/&lt;span class="o"&gt;[&lt;/span&gt;이미지 이름&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="o"&gt;[&lt;/span&gt;태그&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker images
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# pull 또는 run을 통해 다운받아 local에 존재하는 image들을 확인할 수 있음&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker run -d -i -t --name &lt;span class="o"&gt;[&lt;/span&gt;생성할 컨테이너 name 설정&lt;span class="o"&gt;]&lt;/span&gt; -p &lt;span class="o"&gt;[&lt;/span&gt;host port:container port&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;image name or ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Docker image를 실행하여 컨테이너로 띄움. 만약 docker hub에 존재하는 공식이미지의 경우&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# pull을 미리 하지 않고도 local에 없으면 자동으로 다운받아 실행시켜줌.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker ps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 이미지를 실행시켜 컨테이너로 띄워지고 실행중인 컨테이너 항목들을 보여줌&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker ps -a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 실행중인 컨테이너 외에 종료된 컨테이너 항목들을 모두 보여줌&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker stop &lt;span class="o"&gt;[&lt;/span&gt;컨테이너이름 or 컨테이너 ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 현재 실행중인 컨테이너를 중지시킴&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker start &lt;span class="o"&gt;[&lt;/span&gt;컨테이너 이름 or 컨테이너 아이디&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 종료된 컨테이너를 실행시킴&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker restart &lt;span class="o"&gt;[&lt;/span&gt;컨테이너이름 or 컨테이너 ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 실행중인 컨테이너를 재시작함&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker rm &lt;span class="o"&gt;[&lt;/span&gt;컨테이너 이름 or 컨테이너 ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 컨테이너를 삭제시킴&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 컨테이너를 삭제시키기 위해선 먼저 컨테이너를 stop 해주어야 합니다 =)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# +tip 컨테이너 ID를 입력할 경우 모두 적지 않고 2~3글자만 적어도 됩니다.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker rmi &lt;span class="o"&gt;[&lt;/span&gt;이미지이름 or 이미지 ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 이미지를 삭제합니다.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 이또한 마찬가지로 ID를 이용해 삭제할경우 2~3글자만 입력하여도 됩니다 =)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker logs &lt;span class="o"&gt;[&lt;/span&gt;컨테이너이름 or 컨테이너 ID&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 실행한 컨테이너의 로그를 확인할 수 있습니다&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo docker &lt;span class="nb"&gt;exec&lt;/span&gt; -it &lt;span class="o"&gt;[&lt;/span&gt;컨테이너ID&lt;span class="o"&gt;]&lt;/span&gt; /bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 컨테이너 내부 접근&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 종료시에는 $ exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="docker-명령어-옵션"&gt;&lt;a href="#docker-%eb%aa%85%eb%a0%b9%ec%96%b4-%ec%98%b5%ec%85%98" class="header-anchor"&gt;&lt;/a&gt;Docker 명령어 옵션
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;-i&lt;/strong&gt; : &lt;code&gt;--interactive&lt;/code&gt; : 표준입력을 활성화하며, 컨테이너와 연결(attach)되어 있지 않더라도 표준입력을 유지함. 이 옵션을 통해 Bash 명령어를 입력함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-t&lt;/strong&gt; : &lt;code&gt;--tty&lt;/code&gt; : &lt;strong&gt;TTY(pseudo-TTY)&lt;/strong&gt;를 사용함. Bash를 사용하려면 이 옵션을 설정해야하고 설정하지 않으면 명령어를 입력할 순 있지만 셸이 표시되지 않음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-d&lt;/strong&gt; : &lt;code&gt;--detach&lt;/code&gt; : Detached 모드로 데몬 모드라고 부릅니다. 컨테이너가 백그라운드로 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-p&lt;/strong&gt; : &lt;code&gt;--publish&lt;/code&gt; : 호스트와 컨테이너 포트를 연결합니다. &lt;strong&gt;(포트포워딩)&lt;/strong&gt; ex) &lt;code&gt;-p 80:80&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ndash;privileged&lt;/strong&gt; : 컨테이너 안에서 호스트의 리눅스 커널 기능(Capability)을 모두 사용합니다. 이를통해 호스트의 주요 자원에 접근할 수 있음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ndash;rm&lt;/strong&gt; : 프로세스 종료시 컨테이너 자동 제거&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ndash;restart&lt;/strong&gt; : 컨테이너 종료 시, 재시작 정책을 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-v&lt;/strong&gt; : &lt;code&gt;--volume&lt;/code&gt; : 데이터 볼륨설정으로 호스트와 컨테이너의 디렉토리를 연결하여, 파일 설정등을 호스트에서 변경하면 컨테이너 내부도 동일하게 변경사항이 적용됩니다. 싱크의 개념.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-u&lt;/strong&gt; : &lt;code&gt;--user&lt;/code&gt; : 컨테이너가 실행될 리눅스 사용자 계정 이름 또는 UID를 설정합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;--user ubuntu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;-e&lt;/strong&gt; : &lt;code&gt;--env&lt;/code&gt; : 컨테이너 내부에서 사용할 환경변수를 설정합니다. 일반적으로 설정값이나 비밀번호를 전달할 때 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ndash;link&lt;/strong&gt; : 컨테이너끼리 연결합니다. &lt;code&gt;[컨테이너명:별칭]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;--link &amp;quot;mysql:mysql&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;-h&lt;/strong&gt; : &lt;code&gt;--hostname&lt;/code&gt; : 컨테이너의 호스트 이름을 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-w&lt;/strong&gt; : &lt;code&gt;--workdir&lt;/code&gt; : 컨테이너 안의 프로세스가 실행될 디렉토리를 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-a&lt;/strong&gt; : &lt;code&gt;--attach&lt;/code&gt; : 컨테이너에 표준입력(stdin), 표준출력(stdout), 표준에러(stderr)를 연결합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-c&lt;/strong&gt; : &lt;code&gt;--cpu-shares&lt;/code&gt; : CPU 자원 분배 설정입니다. 기본값은 1024이고 각 값은 상대적으로 적용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-m&lt;/strong&gt; : &lt;code&gt;--memory&lt;/code&gt; : 메모리 한계를 설정합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ex) &lt;code&gt;--memory=&amp;quot;100m&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;ndash;gpus&lt;/strong&gt; : 컨테이너에서 호스트의 NVIDIA GPU 를 사용할 수 있도록 설정합니다. 이 방식을 사용하기 위해선 호스트는 NVIDIA GPU가 장착된 Linux 서버 + NVIDIA driver 설치 완료 + docker 19.03.5 버전 이상이여야 합니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--gpus all&lt;/code&gt; : GPU 모두 사용하기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--gpus &amp;quot;device=0.1&amp;quot;&lt;/code&gt; : GPU 지정하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;ndash;security-opt&lt;/strong&gt; : SELinux, AppArmor 옵션을 설정합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--security-opt=&amp;quot;label:level:TopSecret&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>도커(Docker)란? &amp; Docker Container 그리고 가상화 방식의 종류</title><link>https://0andwild.com/posts/221024_about_docker/</link><pubDate>Mon, 24 Oct 2022 00:00:00 +0900</pubDate><guid>https://0andwild.com/posts/221024_about_docker/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post 도커(Docker)란? &amp; Docker Container 그리고 가상화 방식의 종류" /&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221024_about_docker/figure1.png"
 alt="docker"&gt;
&lt;/figure&gt;

&lt;div class="stack-lead"&gt;
 Docker는 &lt;strong&gt;오픈소스 컨테이너화 플랫폼&lt;/strong&gt;으로, 코드와 의존성을 패키징하여 다양한 컴퓨팅 환경에서 애플리케이션을 빠르고 안정적으로 실행할 수 있게 해줍니다.
&lt;/div&gt;

&lt;h2 id="-docker란"&gt;&lt;a href="#-docker%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;🐳 Docker란?
&lt;/h2&gt;&lt;p&gt;Docker의 핵심 개념은 크게 두 가지입니다: &lt;strong&gt;컨테이너(Container)&lt;strong/&gt; 와 &lt;strong&gt;이미지(Image)&lt;strong/&gt;&lt;/p&gt;
&lt;h3 id="docker-image-도커-이미지"&gt;&lt;a href="#docker-image-%eb%8f%84%ec%bb%a4-%ec%9d%b4%eb%af%b8%ec%a7%80" class="header-anchor"&gt;&lt;/a&gt;Docker Image (도커 이미지)
&lt;/h3&gt;&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;strong&gt;Docker Image&lt;/strong&gt;는 애플리케이션 실행에 필요한 코드, 런타임, 시스템 도구, 시스템 라이브러리, 설정 등을 포함하는 &lt;strong&gt;경량의 독립적인 소프트웨어 패키지&lt;/strong&gt;입니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="실제-사용-예시"&gt;&lt;a href="#%ec%8b%a4%ec%a0%9c-%ec%82%ac%ec%9a%a9-%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;실제 사용 예시
&lt;/h3&gt;&lt;p&gt;기존 방식으로 Linux에 Jenkins를 설치한다면:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo apt-get install jenkins
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;위 명령어를 실행하면 여러 의존성 패키지들을 함께 다운로드해야 합니다.&lt;/p&gt;
&lt;p&gt;반면 Docker를 사용하면:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker pull jenkins/jenkins:lts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;필요한 모든 구성 요소가 포함된 사전 구성된 이미지를 한 번에 다운로드할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-docker-registry--docker-hub"&gt;&lt;a href="#-docker-registry--docker-hub" class="header-anchor"&gt;&lt;/a&gt;📦 Docker Registry &amp;amp; Docker Hub
&lt;/h2&gt;&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;strong&gt;Docker Registry&lt;/strong&gt;는 Docker 이미지를 공유하는 저장소 역할을 합니다. &amp;ldquo;Docker용 GitHub&amp;quot;라고 생각하면 쉽습니다.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Docker Hub&lt;/strong&gt;는 공식 Docker 레지스트리로, 벤더가 제공하는 공식 이미지들을 제공합니다.&lt;/p&gt;
&lt;h3 id="동작-흐름"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%ed%9d%90%eb%a6%84" class="header-anchor"&gt;&lt;/a&gt;동작 흐름
&lt;/h3&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;hr&gt;
&lt;h2 id="-container-virtualization-컨테이너-가상화"&gt;&lt;a href="#-container-virtualization-%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88-%ea%b0%80%ec%83%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;🔄 Container Virtualization (컨테이너 가상화)
&lt;/h2&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221024_about_docker/figure2.png"
 alt="Containerized"&gt;
&lt;/figure&gt;

&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;컨테이너 기술은 &amp;ldquo;하나의 시스템 내에서 여러 개의 격리된 인스턴스를 실행할 수 있게 하는 서버 가상화 방식&amp;quot;으로, 각 컨테이너는 사용자에게 개별 서버처럼 보입니다.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;중요한 점:&lt;/strong&gt;
컨테이너는 Docker만의 전유물이 아닙니다. OpenVZ, Libvirt, LXC 등 다양한 컨테이너 기술이 존재합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="-가상화-방식의-종류"&gt;&lt;a href="#-%ea%b0%80%ec%83%81%ed%99%94-%eb%b0%a9%ec%8b%9d%ec%9d%98-%ec%a2%85%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;🖥️ 가상화 방식의 종류
&lt;/h2&gt;&lt;h3 id="1-host-virtualization-호스트-가상화"&gt;&lt;a href="#1-host-virtualization-%ed%98%b8%ec%8a%a4%ed%8a%b8-%ea%b0%80%ec%83%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;1. Host Virtualization (호스트 가상화)
&lt;/h3&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221024_about_docker/figure3.png"
 alt="Hosted Vitualization Architecture"&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;구조:&lt;/strong&gt;
Guest OS가 Host OS 위에서 가상화 소프트웨어를 통해 실행됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예시: VM Workstation, VMware Player, VirtualBox 등&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;📝&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설치 및 구성이 간단함&lt;/li&gt;
&lt;li&gt;하드웨어 에뮬레이션으로 최소한의 호스트 요구사항&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OS 위에 OS를 실행하므로 리소스 집약적&lt;/li&gt;
&lt;li&gt;성능 오버헤드가 큼&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;h3 id="2-hypervisor-virtualization-하이퍼바이저-가상화"&gt;&lt;a href="#2-hypervisor-virtualization-%ed%95%98%ec%9d%b4%ed%8d%bc%eb%b0%94%ec%9d%b4%ec%a0%80-%ea%b0%80%ec%83%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;2. Hypervisor Virtualization (하이퍼바이저 가상화)
&lt;/h3&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221024_about_docker/figure4.png"
 alt="HypervisorVirtualization"&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;구조:&lt;/strong&gt;
Host OS 없이 하드웨어에 직접 소프트웨어를 설치하여 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하이퍼바이저 가상화의 두 가지 접근 방식:&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="1-full-virtualization-전가상화"&gt;&lt;a href="#1-full-virtualization-%ec%a0%84%ea%b0%80%ec%83%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;1) Full Virtualization (전가상화)
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Guest OS가 하드웨어에 직접 접근하지 않고 하이퍼바이저를 통해 접근&lt;/li&gt;
&lt;li&gt;더 안정적이지만 성능 오버헤드 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-paravirtualization-반가상화"&gt;&lt;a href="#2-paravirtualization-%eb%b0%98%ea%b0%80%ec%83%81%ed%99%94" class="header-anchor"&gt;&lt;/a&gt;2) Paravirtualization (반가상화)
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Guest OS가 하이퍼바이저를 통해 하드웨어에 직접 접근&lt;/li&gt;
&lt;li&gt;더 빠르지만 OS 수정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;📝&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Host OS가 없어 더 효율적&lt;/li&gt;
&lt;li&gt;리소스를 더 효과적으로 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;시작 시간이 느림&lt;/li&gt;
&lt;li&gt;각 VM이 독립적인 OS를 실행하므로 여전히 리소스 소모가 큼&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;h3 id="3-container-virtualization-컨테이너-가상화-"&gt;&lt;a href="#3-container-virtualization-%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88-%ea%b0%80%ec%83%81%ed%99%94-" class="header-anchor"&gt;&lt;/a&gt;3. Container Virtualization (컨테이너 가상화) ⭐
&lt;/h3&gt;&lt;figure class="mx-auto"&gt;&lt;img src="https://0andwild.com/posts/221024_about_docker/figure5.png"
 alt="ContainerVirtualization"&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;구조:&lt;/strong&gt;
애플리케이션들이 호스트 OS 커널을 공유하면서도 격리된 환경을 유지합니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;📝&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;경량&lt;/strong&gt;: 일반적으로 수십 MB (VM은 수십 GB)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빠른 시작 속도&lt;/strong&gt;: 별도의 OS 부팅이 필요 없음&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;단점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;호스트 시스템과 동일한 OS 환경이 필요함&lt;/li&gt;
&lt;li&gt;크로스 플랫폼 배포가 어려울 수 있음 (예: Linux 컨테이너는 Linux 호스트 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;h2 id="-가상화-방식-비교"&gt;&lt;a href="#-%ea%b0%80%ec%83%81%ed%99%94-%eb%b0%a9%ec%8b%9d-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&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;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;용량&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;수십 GB&lt;/td&gt;
 &lt;td&gt;수십 GB&lt;/td&gt;
 &lt;td&gt;수십 MB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;시작 속도&lt;/strong&gt;&lt;/td&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;strong&gt;리소스 사용&lt;/strong&gt;&lt;/td&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;strong&gt;격리 수준&lt;/strong&gt;&lt;/td&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;strong&gt;이식성&lt;/strong&gt;&lt;/td&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;strong&gt;설정 난이도&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;쉬움&lt;/td&gt;
 &lt;td&gt;어려움&lt;/td&gt;
 &lt;td&gt;중간&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-정리"&gt;&lt;a href="#-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;💡 정리
&lt;/h2&gt;&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;Docker 컨테이너 가상화의 핵심 가치:&lt;/strong&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;: 애플리케이션을 몇 초 안에 시작하고 중지 가능&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;/div&gt;
&lt;/div&gt;

&lt;p&gt;Docker는 현대적인 애플리케이션 개발과 배포의 핵심 도구로, DevOps와 마이크로서비스 아키텍처의 기반이 되고 있습니다.&lt;/p&gt;</description></item><item><title>사설IP/공인IP? 사설망/공중망? VPN?</title><link>https://0andwild.com/posts/221005_about_ip/</link><pubDate>Wed, 05 Oct 2022 17:34:36 +0900</pubDate><guid>https://0andwild.com/posts/221005_about_ip/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post 사설IP/공인IP? 사설망/공중망? VPN?" /&gt;&lt;div class="stack-lead"&gt;
 사설망과 공중망, 어디선가 들어는 보았지만 개념은 잘 몰랐기에 정리를 해보고자 합니다.
&lt;/div&gt;

&lt;figure&gt;&lt;img src="https://0andwild.com/posts/221005_about_ip/featured.jpg"
 alt="public ip vs private ip"&gt;&lt;figcaption&gt;
 &lt;p&gt;사설 IP와 공인 IP의 관계&lt;/p&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;위 그림을 처음 보면 잉? 하겠지만 글을 모두 읽고 나면 아! 하면서 어느 정도 이해를 하실 수 있을 겁니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id="-2011년-ipv4-주소-고갈-선언"&gt;&lt;a href="#-2011%eb%85%84-ipv4-%ec%a3%bc%ec%86%8c-%ea%b3%a0%ea%b0%88-%ec%84%a0%ec%96%b8" class="header-anchor"&gt;&lt;/a&gt;📅 2011년, IPv4 주소 고갈 선언
&lt;/h2&gt;&lt;p&gt;인터넷 주소 관리기구인 &lt;code&gt;IANA&lt;/code&gt;(Internet Assigned Numbers Authority)에서는 더 이상의 IPv4의 할당은 없을 것이라고 선언을 하였습니다. IPv4는 대략 &lt;strong&gt;43억 개&lt;/strong&gt;의 한정된 주소를 사용할 수 있는데 반해 인터넷의 수요가 빠르게 증가하여 각 대륙에 할당한 IPv4가 동이 나버린 거죠.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;strong&gt;IANA(Internet Assigned Numbers Authority)&lt;/strong&gt;는 인터넷 할당 번호 관리기관의 약자로 IP 주소, 최상위 도메인 등을 관리하는 단체입니다. 현재 ICANN이 관리하고 있습니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="그런데-어떻게-아직도-ipv4를-사용할까"&gt;&lt;a href="#%ea%b7%b8%eb%9f%b0%eb%8d%b0-%ec%96%b4%eb%96%bb%ea%b2%8c-%ec%95%84%ec%a7%81%eb%8f%84-ipv4%eb%a5%bc-%ec%82%ac%ec%9a%a9%ed%95%a0%ea%b9%8c" class="header-anchor"&gt;&lt;/a&gt;그런데 어떻게 아직도 IPv4를 사용할까?
&lt;/h3&gt;&lt;p&gt;그럼 현재 2022년, 무려 11년 전에 동이 나버린 IPv4임에도 불구하고 현재에도 우리는 IPv4를 잘 사용하고 있습니다. 이게 어떻게 된 걸까요?&lt;/p&gt;
&lt;p&gt;이 때문에 IPv6가 오래전 개발되어 조금씩 상용화되고 있습니다. 그럼에도 IPv4의 사용이 훨씬 더 많을 텐데 어떻게 11년이 지난 지금까지 잘 유지를 하고 있는 걸까요?&lt;/p&gt;
&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;이러한 이유는 &lt;strong&gt;사설망(Private Network)&lt;/strong&gt; 덕분입니다.&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id="-사설망private-network이란"&gt;&lt;a href="#-%ec%82%ac%ec%84%a4%eb%a7%9dprivate-network%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;🔌 사설망(Private Network)이란?
&lt;/h2&gt;&lt;p&gt;사설망은 IPv4 중 특정 대역을 공인 인터넷이 아닌 가정, 기업 등의 한정된 공간에 사용한 네트워크를 의미합니다. 사설망에 소속된 IP인 &lt;strong&gt;사설 IP 대역&lt;/strong&gt;은 오로지 &lt;strong&gt;사설망(내부망)&lt;/strong&gt;에서만 사용이 가능하기 때문에 &lt;strong&gt;공인망(외부망, 인터넷)&lt;/strong&gt;에선 사용을 할 수 없습니다.&lt;/p&gt;
&lt;figure class="max-w-4xl"&gt;&lt;img src="https://0andwild.com/posts/221005_about_ip/figure2_ko.png"
 alt="private ip"&gt;&lt;figcaption&gt;
 &lt;p&gt;사설 IP 대역&lt;/p&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="hahahugoshortcode26s6hbhb-공인-ip란"&gt;&lt;a href="#hahahugoshortcode26s6hbhb-%ea%b3%b5%ec%9d%b8-ip%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;&lt;span class="stack-icon stack-icon--emoji" aria-hidden="true"&gt;🌐&lt;/span&gt; 공인 IP란?
&lt;/h2&gt;&lt;p&gt;인터넷 상에서 서로 다른 PC끼리 통신하기 위해 필요한 IP로써 다음과 같은 용도로 사용됩니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;홈페이지 서버 구축&lt;/li&gt;
&lt;li&gt;PC 인터넷 연결&lt;/li&gt;
&lt;li&gt;인터넷을 통한 통신&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;공인 IP는 각 나라마다 관리하는 기관이 있는데, 우리나라는 &lt;strong&gt;한국 인터넷진흥원(KISA)&lt;/strong&gt;에서 관리하고 있습니다.&lt;/div&gt;
&lt;/div&gt;

&lt;figure&gt;&lt;img src="https://0andwild.com/posts/221005_about_ip/figure3.jpeg"
 alt="public ip"&gt;&lt;figcaption&gt;
 &lt;p&gt;공인 IP 주소 체계&lt;/p&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr&gt;
&lt;h3 id="-개념-정리"&gt;&lt;a href="#-%ea%b0%9c%eb%85%90-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;💡 개념 정리
&lt;/h3&gt;&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;사설망은 가정 또는 기업 등의 &lt;strong&gt;한정된 공간에서만 사용이 가능&lt;/strong&gt;합니다.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;그럼 우리가 같은 사설망을 사용하지 않는 다른 PC들과 통신하기 위해선 어떻게 해야 할까요?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;공인 IP&lt;/code&gt; 가 필요합니다!&lt;/p&gt;
&lt;p&gt;즉, 사설망에서 공인 인터넷 통신을 하기 위해선 &lt;strong&gt;특별한 조치&lt;/strong&gt;가 필요합니다. 사설 IP는 사설망에서만 사용하도록 규정이 되어 공인 인터넷에서는 사설 IP를 사용할 수 없는 거죠.&lt;/p&gt;
&lt;h2 id="-nat-network-address-translation"&gt;&lt;a href="#-nat-network-address-translation" class="header-anchor"&gt;&lt;/a&gt;🔄 NAT (Network Address Translation)
&lt;/h2&gt;&lt;p&gt;이에 IP를 변환하기 위한 방법으로 고안된 것이 &lt;strong&gt;네트워크 주소 변환(NAT: Network Address Translation)&lt;/strong&gt; 입니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;NAT란?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;IP 패킷의 TCP/UDP 포트 숫자와 소스 및 목적지의 IP 주소 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고받는 기술을 말합니다. 패킷에 변화가 생기기 때문에 IP나 &lt;strong&gt;TCP/UDP의 체크섬(checksum)&lt;/strong&gt;도 다시 계산되어 재기록해야 합니다.&lt;/p&gt;
&lt;p&gt;NAT를 이용하는 이유는 대개 &lt;strong&gt;사설 네트워크에 속한 여러 개의 호스트가 하나의 공인 IP 주소를 사용&lt;/strong&gt;하여 인터넷에 접속하기 위함입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;즉, 사설망에서 공인망으로, 공인망에서 사설망으로 통신하고자 할 때 공인망/사설망에서 사용하는 IP로 변환하는 것을 의미합니다. 위 설명에 따르면 IP 패킷의 TCP/UDP 포트 숫자를 변환한다고 하는 것은 실제로 NAT의 의미가 IP 주소뿐만 아니라 Port까지 변환시켜 사용하는 것을 포함하기 때문이라고 하네요!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PAT&lt;/code&gt; 또는 &lt;code&gt;NAPT&lt;/code&gt; &lt;strong&gt;(Port Address Translation)&lt;/strong&gt;이라고 부릅니다.&lt;/p&gt;
&lt;h2 id="-공유기의-기능"&gt;&lt;a href="#-%ea%b3%b5%ec%9c%a0%ea%b8%b0%ec%9d%98-%ea%b8%b0%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;📡 공유기의 기능
&lt;/h2&gt;&lt;p&gt;요즘 대부분의 모든 집이 공유기를 (예: iptime, olleh 등) 설치하고 사용하고 있죠.&lt;/p&gt;
&lt;p&gt;이 공유기에는 다양한 기능이 존재합니다.&lt;/p&gt;
&lt;h3 id="1-dhcp-서버-기능"&gt;&lt;a href="#1-dhcp-%ec%84%9c%eb%b2%84-%ea%b8%b0%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;1. DHCP 서버 기능
&lt;/h3&gt;&lt;p&gt;일단 하나의 공유기를 통해 연결된 다양한 기기에 IP를 할당해주는 &lt;code&gt;DHCP&lt;/code&gt;(Dynamic Host Configuration Protocol) 서버의 기능이 존재합니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;동적 호스트 구성 프로토콜(DHCP)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;DHCP는 호스트 IP 구성 관리를 단순화하는 IP 표준입니다. DHCP 서버를 사용하여 IP 주소 및 관련된 기타 구성 세부 정보를 네트워크의 DHCP 사용 클라이언트에게 &lt;strong&gt;동적으로 할당&lt;/strong&gt;하는 방법을 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;이를 통해 공유기에 연결한 집 내부의 스마트 기기 및 PC는 각각의 사설 IP를 할당받게 됩니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--warning"&gt;
 &lt;div class="stack-alert__icon"&gt;⚠️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;왜 사설 IP를 할당받는 걸까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;맨 처음 설명으로 돌아가 보면 이해가 되겠죠&amp;hellip;?!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;IP 할당 개수는 한정되어 있기 때문에 집집마다, 아니 모든 기기마다 공인 IP를 할당할 수 없으니 &lt;strong&gt;사설 IP를 할당하여 사설망을 구축&lt;/strong&gt;하는 거죠! 이렇게 사설망을 구축하여 내부적으로는 통신이 가능하지만 아직 외부 인터넷과는 통신을 할 수 없습니다.&lt;/p&gt;
&lt;h3 id="2-nat-기능"&gt;&lt;a href="#2-nat-%ea%b8%b0%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;2. NAT 기능
&lt;/h3&gt;&lt;p&gt;그래서 공유기는 &lt;strong&gt;NAT 기능&lt;/strong&gt;을 갖추고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사설 IP를 공인 IP로 변환하는 기능&lt;/li&gt;
&lt;li&gt;매핑 테이블을 자체 구축하여 NAT 테이블로 변환 전과 변환 후 값을 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;물론 공유기가 자체적으로 공인 IP를 갖고 있는 것은 아닙니다! 공유기는 &lt;strong&gt;인터넷 업체(KT, SKT, LG 등)&lt;/strong&gt;에서 제공받은 &lt;strong&gt;공인 IP 대역을 사용&lt;/strong&gt;하는 것이죠!&lt;/div&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;h2 id="hahahugoshortcode26s14hbhb-vpnvirtual-private-network이란"&gt;&lt;a href="#hahahugoshortcode26s14hbhb-vpnvirtual-private-network%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;&lt;span class="stack-icon stack-icon--emoji" aria-hidden="true"&gt;🛡️&lt;/span&gt; VPN(Virtual Private Network)이란?
&lt;/h2&gt;&lt;p&gt;그럼 더 나아가 우리가 사용은 해봤을 수 있지만 정확히 어떤 역할을 하는지 잘 모르는 VPN에 대해 알아봅시다!&lt;/p&gt;
&lt;div class="stack-lead"&gt;
 VPN은 &lt;strong&gt;가상 사설망&lt;/strong&gt;으로 이름 그대로 사설망이지만 가상인 사설망을 말합니다.
&lt;/div&gt;

&lt;div class="stack-alert stack-alert--important"&gt;
 &lt;div class="stack-alert__icon"&gt;🔥&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;원래 알고 있던 VPN은 뭔가 IP를 변경하거나 IP를 속여 불법&amp;hellip; 🤔&lt;/p&gt;
&lt;p&gt;그런 건 줄 알고 있었지만 &lt;strong&gt;반은 맞고 반은 틀린&lt;/strong&gt; 내용입니다!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="vpn의-진짜-의미"&gt;&lt;a href="#vpn%ec%9d%98-%ec%a7%84%ec%a7%9c-%ec%9d%98%eb%af%b8" class="header-anchor"&gt;&lt;/a&gt;VPN의 진짜 의미
&lt;/h3&gt;&lt;p&gt;VPN은 &lt;strong&gt;외부에 있는 컴퓨터에서 내부 네트워크(사설망)에 접속해 있는 것처럼&lt;/strong&gt; 사용할 수 있는 것을 말합니다.&lt;/p&gt;
&lt;p&gt;VPN을 사용하였을 때 IP가 변경되는 이유도 위의 사설망/공중망을 잘 생각해 보면 이해할 수 있습니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;VPN을 통해 &lt;strong&gt;내부 네트워크(사설망) 안에 접속&lt;/strong&gt;을 하였기 때문에 IP가 변경되는 것이죠!&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="-vpn-활용-사례"&gt;&lt;a href="#-vpn-%ed%99%9c%ec%9a%a9-%ec%82%ac%eb%a1%80" class="header-anchor"&gt;&lt;/a&gt;💼 VPN 활용 사례
&lt;/h3&gt;&lt;h4 id="1-재택근무원격-근무"&gt;&lt;a href="#1-%ec%9e%ac%ed%83%9d%ea%b7%bc%eb%ac%b4%ec%9b%90%ea%b2%a9-%ea%b7%bc%eb%ac%b4" class="header-anchor"&gt;&lt;/a&gt;1. 재택근무/원격 근무
&lt;/h4&gt;&lt;p&gt;이를 통해 사설 네트워크가 구축되어 있는 회사에서 VPN 서버 설정을 해주고 외부 공인 IP 주소와 설정된 아이디/비밀번호를 통해 &lt;strong&gt;어디에서든 회사의 사설망에 접근&lt;/strong&gt;을 할 수 있는 거죠.&lt;/p&gt;
&lt;h4 id="2-원격-컴퓨터-접속"&gt;&lt;a href="#2-%ec%9b%90%ea%b2%a9-%ec%bb%b4%ed%93%a8%ed%84%b0-%ec%a0%91%ec%86%8d" class="header-anchor"&gt;&lt;/a&gt;2. 원격 컴퓨터 접속
&lt;/h4&gt;&lt;p&gt;또한 개인 컴퓨터도 마찬가지로 VPN 설정을 통해 외부 공인 IP 주소만 알아놓고 있으면 어디에서든 VPN을 통해 &lt;strong&gt;서울에 있는 내 컴퓨터를 제주도에서도 접근&lt;/strong&gt;할 수 있습니다.&lt;/p&gt;
&lt;h4 id="3-지역-제한-우회"&gt;&lt;a href="#3-%ec%a7%80%ec%97%ad-%ec%a0%9c%ed%95%9c-%ec%9a%b0%ed%9a%8c" class="header-anchor"&gt;&lt;/a&gt;3. 지역 제한 우회
&lt;/h4&gt;&lt;p&gt;어떤 한 나라의 사이트에서 우리나라 IP로 접속하는 것을 차단했다고 하였을 때 우리는 그 사이트에 접속을 할 수 없습니다. 이 사이트에 접속을 하기 위해서 우리나라가 아닌 다른 나라의 IP 주소로 접근을 해주어야 하는데 이때 VPN을 통해 &lt;strong&gt;다른 나라의 내부 네트워크에 접속한 것처럼&lt;/strong&gt; 하여 차단된 방화벽을 통과할 수 있습니다.&lt;/p&gt;
&lt;h4 id="4-방화벽-우회-메커니즘"&gt;&lt;a href="#4-%eb%b0%a9%ed%99%94%eb%b2%bd-%ec%9a%b0%ed%9a%8c-%eb%a9%94%ec%bb%a4%eb%8b%88%ec%a6%98" class="header-anchor"&gt;&lt;/a&gt;4. 방화벽 우회 메커니즘
&lt;/h4&gt;&lt;div class="stack-alert stack-alert--warning"&gt;
 &lt;div class="stack-alert__icon"&gt;⚠️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;가상 시나리오&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;회사에서 내부 규칙으로 회사 근무 중에 SNS에 접속하지 못하도록 차단하였다고 하였을 때 우리는 집에 구축해둔 VPN 또는 해외 VPN을 통해 접속을 해줍니다. 그럼 SNS에 접속을 할 수 있게 됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;왜 이러는 걸까요?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;VPN을 연결하는 순간 &lt;strong&gt;가상의 터널&lt;/strong&gt;이 형성되고 터널 간의 통신을 위해 보내는 패킷들을 잘게 나누고 &lt;strong&gt;암호화&lt;/strong&gt;와 &lt;strong&gt;캡슐화&lt;/strong&gt;를 합니다. 이때 회사의 방화벽을 거치게 되지만 암호화/캡슐화된 패킷이기 때문에 내가 VPN을 통해 접속하려는 곳이 SNS인지 알아차리지 못하기 때문에 패킷을 그대로 통과시키게 됩니다.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://0andwild.com/posts/221005_about_ip/figure4.png"
 alt="vpn"&gt;&lt;figcaption&gt;
 &lt;p&gt;VPN 터널링 구조&lt;/p&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr&gt;
&lt;h2 id="-vpn-정리"&gt;&lt;a href="#-vpn-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;📋 VPN 정리
&lt;/h2&gt;&lt;h3 id="-장점"&gt;&lt;a href="#-%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;👍 장점
&lt;/h3&gt;&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;ul&gt;
&lt;li&gt;&lt;span class="stack-icon stack-icon--emoji" aria-hidden="true"&gt;🔒&lt;/span&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;IP 주소 변경&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;/div&gt;
&lt;/div&gt;

&lt;h3 id="-단점"&gt;&lt;a href="#-%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;👎 단점
&lt;/h3&gt;&lt;p&gt;VPN은 위와 같이 여러 장점도 존재하지만 단점도 존재합니다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--warning"&gt;
 &lt;div class="stack-alert__icon"&gt;⚠️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;ul&gt;
&lt;li&gt;🐢 VPN에 접속한 장비는 VPN 서버와 암호화 통신을 해야 하기 때문에 &lt;strong&gt;네트워크 속도가 매우 느립니다&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;⚠️ 신뢰성이 낮은 일부 VPN도 존재합니다&lt;/li&gt;
&lt;li&gt;💰 보안성이 높은 VPN 사용 시에는 &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;/div&gt;
&lt;/div&gt;
</description></item><item><title>Spring Dispatcher Servlet의 이해</title><link>https://0andwild.com/posts/220927_dispatcher/</link><pubDate>Tue, 27 Sep 2022 23:10:15 +0900</pubDate><guid>https://0andwild.com/posts/220927_dispatcher/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post Spring Dispatcher Servlet의 이해" /&gt;&lt;h2 id="dispatcher-servlet이란"&gt;&lt;a href="#dispatcher-servlet%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;Dispatcher Servlet이란?
&lt;/h2&gt;&lt;p&gt;디스패처 서블릿의 &lt;strong&gt;dispatch&lt;/strong&gt;는 &amp;ldquo;보내다&amp;quot;라는 뜻을 가지고 있다. 이러한 단어의 뜻을 내포하는 디스패처 서블릿은 **HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(Front Controller)**라고 정의할 수 있다.&lt;/p&gt;
&lt;h3 id="동작-개요"&gt;&lt;a href="#%eb%8f%99%ec%9e%91-%ea%b0%9c%ec%9a%94" class="header-anchor"&gt;&lt;/a&gt;동작 개요
&lt;/h3&gt;&lt;p&gt;좀 더 자세한 절차는 다음과 같다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트로부터 어떠한 요청이 들어오면 &lt;strong&gt;Tomcat&lt;/strong&gt;과 같은 서블릿 컨테이너가 요청을 받게 된다&lt;/li&gt;
&lt;li&gt;이 모든 요청을 프론트 컨트롤러인 &lt;strong&gt;디스패처 서블릿&lt;/strong&gt;이 가장 먼저 받게 된다&lt;/li&gt;
&lt;li&gt;디스패처 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야 하는 컨트롤러를 찾아서 작업을 위임한다&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="front-controller-패턴"&gt;&lt;a href="#front-controller-%ed%8c%a8%ed%84%b4" class="header-anchor"&gt;&lt;/a&gt;Front Controller 패턴
&lt;/h3&gt;&lt;p&gt;여기서 &lt;strong&gt;Front Controller&lt;/strong&gt;라는 용어는 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해주는 컨트롤러로써, &lt;strong&gt;MVC 구조에서 함께 사용되는 디자인 패턴&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dispatcher-servlet의-동작-방식"&gt;&lt;a href="#dispatcher-servlet%ec%9d%98-%eb%8f%99%ec%9e%91-%eb%b0%a9%ec%8b%9d" class="header-anchor"&gt;&lt;/a&gt;Dispatcher Servlet의 동작 방식
&lt;/h2&gt;&lt;p&gt;디스패처 서블릿은 가장 먼저 요청을 받는 &lt;strong&gt;Front-Controller&lt;/strong&gt;이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**서블릿 컨텍스트(Web Context)**에서 필터들을 지나&lt;/li&gt;
&lt;li&gt;**스프링 컨텍스트(Spring Context)**에서 디스패처 서블릿이 가장 먼저 요청을 받게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="그림1: Servlet Context와 Spring Context" class="gallery-image" data-flex-basis="601px" data-flex-grow="250" height="505" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/220927_dispatcher/figure1.png" srcset="https://0andwild.com/posts/220927_dispatcher/figure1_hu_4668e2def7db33f8.png 800w, https://0andwild.com/posts/220927_dispatcher/figure1.png 1265w" width="1265"&gt;&lt;/p&gt;
&lt;p&gt;디스패처 서블릿은 적합한 컨트롤러와 메소드를 찾아 요청을 위임해야 하며 동작 방식은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="그림2: Dispatcher Servlet 동작 흐름" class="gallery-image" data-flex-basis="511px" data-flex-grow="213" height="560" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/220927_dispatcher/featured.png" srcset="https://0andwild.com/posts/220927_dispatcher/featured_hu_4d8ad4388727bae1.png 800w, https://0andwild.com/posts/220927_dispatcher/featured.png 1193w" width="1193"&gt;&lt;/p&gt;
&lt;h3 id="상세-동작-과정"&gt;&lt;a href="#%ec%83%81%ec%84%b8-%eb%8f%99%ec%9e%91-%ea%b3%bc%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;상세 동작 과정
&lt;/h3&gt;&lt;h4 id="1-http-request가-filter를-거쳐-dispatcher-servlet이-받는다"&gt;&lt;a href="#1-http-request%ea%b0%80-filter%eb%a5%bc-%ea%b1%b0%ec%b3%90-dispatcher-servlet%ec%9d%b4-%eb%b0%9b%eb%8a%94%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;1. HTTP Request가 Filter를 거쳐 Dispatcher Servlet이 받는다
&lt;/h4&gt;&lt;h4 id="2-요청-정보를-확인하고-위임할-controller를-찾는다"&gt;&lt;a href="#2-%ec%9a%94%ec%b2%ad-%ec%a0%95%eb%b3%b4%eb%a5%bc-%ed%99%95%ec%9d%b8%ed%95%98%ea%b3%a0-%ec%9c%84%ec%9e%84%ed%95%a0-controller%eb%a5%bc-%ec%b0%be%eb%8a%94%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;2. 요청 정보를 확인하고 위임할 Controller를 찾는다
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;HandlerMapping&lt;/code&gt;의 구현체 중 하나인 &lt;code&gt;RequestMappingHandlerMapping&lt;/code&gt;은 &lt;code&gt;@Controller&lt;/code&gt;로 작성된 모든 컨트롤러 빈을 파싱하여 &lt;strong&gt;HashMap으로 (요청정보, 처리대상)을 관리&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;요청에 매핑되는 컨트롤러와 해당 메소드 등을 갖는 &lt;code&gt;HandlerMethod&lt;/code&gt; 객체를 찾는다. 그렇기 때문에 &lt;code&gt;HandlerMapping&lt;/code&gt;은 요청이 오면 HTTP Method, URI 등을 사용해 Key 객체인 요청 정보를 만들고, Value인 요청을 처리할 &lt;code&gt;HandlerMethod&lt;/code&gt;를 찾아 &lt;code&gt;HandlerMethodExecutionChain&lt;/code&gt;으로 감싸서 반환한다.&lt;/p&gt;
&lt;p&gt;이렇게 감싸는 이유는 &lt;strong&gt;컨트롤러로 요청을 넘겨주기 전에 처리해야 하는 인터셉터 등을 포함하기 위함&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h4 id="3-controller로-위임해줄-handleradapter를-찾아-전달한다"&gt;&lt;a href="#3-controller%eb%a1%9c-%ec%9c%84%ec%9e%84%ed%95%b4%ec%a4%84-handleradapter%eb%a5%bc-%ec%b0%be%ec%95%84-%ec%a0%84%eb%8b%ac%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;3. Controller로 위임해줄 HandlerAdapter를 찾아 전달한다
&lt;/h4&gt;&lt;p&gt;디스패처 서블릿은 컨트롤러로 요청을 직접 위임하는 것이 아닌 &lt;code&gt;HandlerAdapter&lt;/code&gt;를 통해 컨트롤러로 요청을 위임한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HandlerAdapter&lt;/code&gt; 인터페이스를 거치는 이유는 &lt;strong&gt;컨트롤러를 구현하는 방식이 다양하기 때문&lt;/strong&gt;이다. &lt;code&gt;@Controller&lt;/code&gt;에 &lt;code&gt;@RequestMapping&lt;/code&gt; 관련 어노테이션을 사용해 컨트롤러 클래스를 주로 작성하지만, &lt;code&gt;Controller&lt;/code&gt; 인터페이스를 구현하여 컨트롤러 클래스를 작성할 수도 있다.&lt;/p&gt;
&lt;p&gt;그렇기 때문에 스프링은 &lt;code&gt;HandlerAdapter&lt;/code&gt;라는 인터페이스를 통해 &lt;strong&gt;어댑터 패턴을 적용&lt;/strong&gt;함으로써 컨트롤러의 구현 방식에 상관없이 요청을 Controller에 위임할 수 있는 것이다.&lt;/p&gt;
&lt;h4 id="4-handleradapter가-controller로-요청을-위임한다"&gt;&lt;a href="#4-handleradapter%ea%b0%80-controller%eb%a1%9c-%ec%9a%94%ec%b2%ad%ec%9d%84-%ec%9c%84%ec%9e%84%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;4. HandlerAdapter가 Controller로 요청을 위임한다
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;HandlerAdapter&lt;/code&gt;가 Controller로 요청을 넘기기 전에 공통적인 전/후 처리 과정이 필요하다.&lt;/p&gt;
&lt;p&gt;대표적으로:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인터셉터 처리&lt;/li&gt;
&lt;li&gt;요청 시 &lt;code&gt;@RequestParam&lt;/code&gt;, &lt;code&gt;@RequestBody&lt;/code&gt; 등을 처리하기 위한 &lt;strong&gt;ArgumentResolver&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;응답 시 &lt;code&gt;ResponseEntity&lt;/code&gt;의 Body를 JSON으로 직렬화하는 등의 처리를 하는 &lt;strong&gt;ReturnValueHandler&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 처리들이 어댑터에서 컨트롤러로 전달되기 전에 처리된다. 그리고 컨트롤러의 메소드를 호출하도록 요청을 위임한다.&lt;/p&gt;
&lt;h4 id="5-business-logic을-처리한다"&gt;&lt;a href="#5-business-logic%ec%9d%84-%ec%b2%98%eb%a6%ac%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;5. Business Logic을 처리한다
&lt;/h4&gt;&lt;p&gt;Controller는 서비스를 호출하고 비즈니스 로직을 진행한다.&lt;/p&gt;
&lt;h4 id="6-controller가-반환값을-return한다"&gt;&lt;a href="#6-controller%ea%b0%80-%eb%b0%98%ed%99%98%ea%b0%92%ec%9d%84-return%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;6. Controller가 반환값을 return한다
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;ResponseEntity&lt;/code&gt; 또는 View 이름을 반환한다.&lt;/p&gt;
&lt;h4 id="7-handleradapter가-return값을-처리한다"&gt;&lt;a href="#7-handleradapter%ea%b0%80-return%ea%b0%92%ec%9d%84-%ec%b2%98%eb%a6%ac%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;7. HandlerAdapter가 return값을 처리한다
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;HandlerAdapter&lt;/code&gt;는 컨트롤러로부터 받은 응답을 응답 처리기인 &lt;code&gt;ReturnValueHandler&lt;/code&gt;가 후처리한 후에 디스패처 서블릿으로 돌려준다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컨트롤러가 &lt;code&gt;ResponseEntity&lt;/code&gt;를 반환하면 → &lt;code&gt;HttpEntityMethodProcessor&lt;/code&gt;가 &lt;code&gt;MessageConverter&lt;/code&gt;를 사용해 응답 객체를 직렬화하고 응답 상태(&lt;code&gt;HttpStatus&lt;/code&gt;)를 설정&lt;/li&gt;
&lt;li&gt;View 이름을 반환하면 → &lt;code&gt;ViewResolver&lt;/code&gt;를 통해 View를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="8-서버의-응답을-클라이언트에게-전달한다"&gt;&lt;a href="#8-%ec%84%9c%eb%b2%84%ec%9d%98-%ec%9d%91%eb%8b%b5%ec%9d%84-%ed%81%b4%eb%9d%bc%ec%9d%b4%ec%96%b8%ed%8a%b8%ec%97%90%ea%b2%8c-%ec%a0%84%eb%8b%ac%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;8. 서버의 응답을 클라이언트에게 전달한다
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;DispatcherServlet&lt;/code&gt;을 통해 반환되는 응답은 다시 Filter를 거쳐 클라이언트에게 반환된다.&lt;/p&gt;</description></item><item><title>JVM(JavaVirtualMachine) 파헤치기 (2)</title><link>https://0andwild.com/posts/220923_jvm_2/</link><pubDate>Fri, 23 Sep 2022 22:27:28 +0900</pubDate><guid>https://0andwild.com/posts/220923_jvm_2/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post JVM(JavaVirtualMachine) 파헤치기 (2)" /&gt;&lt;h1 id="jvm의-구성요소"&gt;&lt;a href="#jvm%ec%9d%98-%ea%b5%ac%ec%84%b1%ec%9a%94%ec%86%8c" class="header-anchor"&gt;&lt;/a&gt;JVM의 구성요소
&lt;/h1&gt;&lt;p&gt;&lt;img alt="JVM_process" class="gallery-image" data-flex-basis="420px" data-flex-grow="175" height="325" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/220923_jvm_2/featured.png" width="569"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-클래스-로더-class-loader"&gt;&lt;a href="#1-%ed%81%b4%eb%9e%98%ec%8a%a4-%eb%a1%9c%eb%8d%94-class-loader" class="header-anchor"&gt;&lt;/a&gt;1. 클래스 로더 (Class Loader)
&lt;/h2&gt;&lt;p&gt;JVM의 &lt;strong&gt;Class Loader&lt;/strong&gt;는 &lt;code&gt;javac&lt;/code&gt;에 의해 변환된 바이트코드 파일인 &lt;code&gt;*.class&lt;/code&gt; 파일을 &lt;strong&gt;Runtime Data Areas&lt;/strong&gt;에 로딩하여 프로그램을 구동한다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;Class Loader의 로딩은 &lt;strong&gt;런타임&lt;/strong&gt;에 일어나는데, 클래스에 처음 접근될 때 일어난다. 이를 통해 &lt;strong&gt;Lazy Loading Singleton&lt;/strong&gt;이 구현되기도 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Class Loading 시간엔 &lt;strong&gt;Thread-safe&lt;/strong&gt; 하다.&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;h2 id="2-실행-엔진-execution-engine"&gt;&lt;a href="#2-%ec%8b%a4%ed%96%89-%ec%97%94%ec%a7%84-execution-engine" class="header-anchor"&gt;&lt;/a&gt;2. 실행 엔진 (Execution Engine)
&lt;/h2&gt;&lt;p&gt;Class Loader가 Runtime Data Areas에 불러온 &lt;strong&gt;바이트 코드를 실행&lt;/strong&gt;한다. 바이트 코드를 기계어로 변경해 명령어 단위로 실행하는데, 1바이트의 OpCode와 피연산자로 구성이 된다.&lt;/p&gt;
&lt;h3 id="주요-구성요소"&gt;&lt;a href="#%ec%a3%bc%ec%9a%94-%ea%b5%ac%ec%84%b1%ec%9a%94%ec%86%8c" class="header-anchor"&gt;&lt;/a&gt;주요 구성요소
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인터프리터 (Interpreter)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컴파일러 (Just-in-Time)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3-가비지-콜렉터-garbage-collector"&gt;&lt;a href="#3-%ea%b0%80%eb%b9%84%ec%a7%80-%ec%bd%9c%eb%a0%89%ed%84%b0-garbage-collector" class="header-anchor"&gt;&lt;/a&gt;3. 가비지 콜렉터 (Garbage Collector)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Heap 영역에 참조되지 않는 오브젝트를 제거&lt;/strong&gt;하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;자바 이전에는 프로그래머가 모든 프로그램의 메모리를 관리했다. 자바에서는 JVM이 &lt;strong&gt;가비지 컬렉션&lt;/strong&gt;이라는 프로세스를 통해 프로그램 메모리를 관리한다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;가비지 컬렉션은 자바 프로그램에서 사용되지 않는 메모리를 지속적으로 찾아내서 제거하는 역할을 한다.&lt;/div&gt;
&lt;/div&gt;

&lt;hr&gt;
&lt;h2 id="4-런타임-데이터-영역-runtime-data-areas"&gt;&lt;a href="#4-%eb%9f%b0%ed%83%80%ec%9e%84-%eb%8d%b0%ec%9d%b4%ed%84%b0-%ec%98%81%ec%97%ad-runtime-data-areas" class="header-anchor"&gt;&lt;/a&gt;4. 런타임 데이터 영역 (Runtime Data Areas)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;OS로부터 할당받은 JVM의 메모리 영역&lt;/strong&gt;이다. 자바 어플리케이션을 실행하는데 필요한 데이터를 담는다.&lt;/p&gt;
&lt;p&gt;Runtime Data Areas는 아래와 같이 &lt;strong&gt;5개의 영역&lt;/strong&gt;으로 나뉘어 진다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;공유 영역&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Method와 Heap 영역은 모든 Thread가 공유&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Thread별 영역&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stack, PC Register, Native Method 영역은 각 Thread 마다 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img alt="JVM_process" class="gallery-image" data-flex-basis="414px" data-flex-grow="172" height="475" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/220923_jvm_2/runtime_data_area.png" srcset="https://0andwild.com/posts/220923_jvm_2/runtime_data_area_hu_452cc5b05f4a468f.png 800w, https://0andwild.com/posts/220923_jvm_2/runtime_data_area.png 820w" width="820"&gt;&lt;/p&gt;
&lt;h3 id="1-method-area"&gt;&lt;a href="#1-method-area" class="header-anchor"&gt;&lt;/a&gt;(1) Method Area
&lt;/h3&gt;&lt;p&gt;JVM이 시작될 때 생성되고 JVM이 읽은 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드 및 메서드 코드, 정적 변수, 메서드의 바이트 코드 등을 보관한다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;Non-Heap 영역으로 Permanent 영역에 저장이된다. JVM 옵션 중 &lt;code&gt;PermSize&lt;/code&gt;(Permanent Generation의 크기)를 지정할 때 고려해야 할 요소이다.&lt;/div&gt;
&lt;/div&gt;

&lt;h4 id="1-1-type-information"&gt;&lt;a href="#1-1-type-information" class="header-anchor"&gt;&lt;/a&gt;1-1 Type Information
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Interface 여부&lt;/li&gt;
&lt;li&gt;패키지 명을 포함한 Type 이름&lt;/li&gt;
&lt;li&gt;Type의 접근 제어자&lt;/li&gt;
&lt;li&gt;연관된 Interface 리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="1-2-runtime-constant-pool"&gt;&lt;a href="#1-2-runtime-constant-pool" class="header-anchor"&gt;&lt;/a&gt;1-2 Runtime Constant Pool
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Type, Field, Method로의 모든 레퍼런스를 저장&lt;/li&gt;
&lt;li&gt;JVM은 Runtime Contant Pool을 통해 메모리 상 주소를 찾아 참조한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="1-3-field-information"&gt;&lt;a href="#1-3-field-information" class="header-anchor"&gt;&lt;/a&gt;1-3 Field Information
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Field의 타입&lt;/li&gt;
&lt;li&gt;Field의 접근 제어자&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="1-4-method-information"&gt;&lt;a href="#1-4-method-information" class="header-anchor"&gt;&lt;/a&gt;1-4 Method Information
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Constructor를 포함한 모든 Method의 메타데이터를 저장&lt;/li&gt;
&lt;li&gt;Method의 이름, 파라미터 수와 타입, 리턴 타입, 접근 제어자, 바이트코드, 지역 변수 section의 크기 등을 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="1-5-class-variable"&gt;&lt;a href="#1-5-class-variable" class="header-anchor"&gt;&lt;/a&gt;1-5 Class Variable
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;static&lt;/code&gt; 키워드로 선언된 변수를 저장&lt;/li&gt;
&lt;li&gt;기본형이 아닌 static 변수의 실제 인스턴스는 Heap 메모리에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-heap-area"&gt;&lt;a href="#2-heap-area" class="header-anchor"&gt;&lt;/a&gt;(2) Heap Area
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;new&lt;/code&gt; 연산자로 생성된 객체를 저장하는 공간이다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;참조하는 변수나 필드가 존재하지 않으면 **GC(Garbage Collector)**의 대상이 된다.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="3-stack-area"&gt;&lt;a href="#3-stack-area" class="header-anchor"&gt;&lt;/a&gt;(3) Stack Area
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Thread마다 별개의 Frame&lt;/strong&gt;으로 저장하며, 저장되는 요소는 아래와 같다.&lt;/p&gt;
&lt;h4 id="3-1-local-variable-area"&gt;&lt;a href="#3-1-local-variable-area" class="header-anchor"&gt;&lt;/a&gt;3-1 Local Variable Area
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;지역변수, 매개변수, 메소드를 호출한 주소 등 Method 수행 중 발생하는 임시데이터를 저장한다.&lt;/li&gt;
&lt;li&gt;4바이트 단위로 저장되며, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt; 등 4바이트 기본형은 1개의 셀, &lt;code&gt;double&lt;/code&gt; 등 8바이트의 기본형은 2개의 셀을 차지한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bool&lt;/code&gt;은 일반적으로 1개의 셀을 차지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-2-operand-stack"&gt;&lt;a href="#3-2-operand-stack" class="header-anchor"&gt;&lt;/a&gt;3-2 Operand Stack
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Method의 &lt;strong&gt;workspace&lt;/strong&gt; 이다.&lt;/li&gt;
&lt;li&gt;어떤 명령을 어떤 피연산자로 수행할 지 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-3-frame-data"&gt;&lt;a href="#3-3-frame-data" class="header-anchor"&gt;&lt;/a&gt;3-3 Frame Data
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Constant Pool Resolution, Method Return, Exception Dispatch 등을 포함한다.&lt;/li&gt;
&lt;li&gt;참조된 Exception의 테이블도 가지고 있다.&lt;/li&gt;
&lt;li&gt;Exception이 발생하면 JVM은 이 테이블을 참고하여 어떻게 Exception을 처리할 지 정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-pc-register"&gt;&lt;a href="#4-pc-register" class="header-anchor"&gt;&lt;/a&gt;(4) PC Register
&lt;/h3&gt;&lt;p&gt;Thread가 시작될 때 생성되며 생성될 때마다 생성되는 공간으로, &lt;strong&gt;스레드마다 하나씩 존재&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;Thread가 어떤 부분을 어떤 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 Thread가 &lt;strong&gt;현재 실행하고 있는 부분의 주소&lt;/strong&gt;를 갖는다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;OS는 &lt;strong&gt;PC(Program Counter) Register&lt;/strong&gt;를 참고하여 CPU 스케줄링 시 해당 Thread가 다음에 어떤 명령어를 수행해야 하는지 알 수 있다.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="5-native-method-stack"&gt;&lt;a href="#5-native-method-stack" class="header-anchor"&gt;&lt;/a&gt;(5) Native Method Stack
&lt;/h3&gt;&lt;p&gt;자바 프로그램이 컴파일되어 생성되는 바이트 코드가 아닌 &lt;strong&gt;실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역&lt;/strong&gt;이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java가 아닌 다른 언어로 작성된 코드&lt;/strong&gt;를 위한 공간이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Java Native Interface&lt;/strong&gt;를 통해 바이트 코드로 전환하여 저장하게 된다.&lt;/li&gt;
&lt;li&gt;일반 프로그램처럼 커널이 스택을 잡아 독자적으로 프로그램을 실행시키는 영역이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="jvm-실행-순서"&gt;&lt;a href="#jvm-%ec%8b%a4%ed%96%89-%ec%88%9c%ec%84%9c" class="header-anchor"&gt;&lt;/a&gt;JVM 실행 순서
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;메모리 할당&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로그램이 실행되면 JVM은 OS로부터 이 프로그램을 실행하는데 필요한 메모리를 할당받음&lt;/li&gt;
&lt;li&gt;JVM은 이 메모리를 여러 영역으로 나누어 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;컴파일&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Java Compiler(&lt;code&gt;javac&lt;/code&gt;)가 &lt;code&gt;*.java&lt;/code&gt; 파일을 컴파일하여 &lt;code&gt;*.class&lt;/code&gt; 인 자바 바이트코드로 변환시킨다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;클래스 로딩&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴파일된 &lt;code&gt;*.class&lt;/code&gt; 파일들을 &lt;strong&gt;Class Loader&lt;/strong&gt;를 통해 JVM 메모리 위에 로딩을 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;바이트코드 해석&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;로딩된 &lt;code&gt;*.class&lt;/code&gt; 파일들은 &lt;strong&gt;Execution Engine&lt;/strong&gt;을 통해 기계어로 해석된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;실행 및 관리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해석된 바이트코드들은 메모리 영역에 배치되어 실질적인 수행을 하게 된다&lt;/li&gt;
&lt;li&gt;실행과정 속에서 JVM은 필요에 따라 &lt;strong&gt;스레드 동기화&lt;/strong&gt;나 &lt;strong&gt;가비지 컬렉터&lt;/strong&gt;와 같은 메모리 관리 작업을 수행한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>JVM(JavaVirtualMachine) 파헤치기 (1)</title><link>https://0andwild.com/posts/220922_jvm_1/</link><pubDate>Thu, 22 Sep 2022 22:06:50 +0900</pubDate><guid>https://0andwild.com/posts/220922_jvm_1/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post JVM(JavaVirtualMachine) 파헤치기 (1)" /&gt;&lt;p&gt;문득 Java라는 언어를 공부하면서 &lt;strong&gt;JVM&lt;/strong&gt;에 대한 궁금증이 생겼다.
단순히 작성한 코드를 실행시켜주는 가상컴퓨터 이다 라고만 알고 있었기에
어떻게 동작을하고 하는 역할은 무엇인지 궁금해졌기에 파헤쳐보고자 한다.&lt;/p&gt;
&lt;h2 id="jvm이란"&gt;&lt;a href="#jvm%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;JVM이란?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Java Virtual Machine&lt;/strong&gt;의 줄임말로 Java를 실행시키기 위한 가상컴퓨터 환경을 말한다.&lt;/p&gt;
&lt;h3 id="그럼-jvm이-하는-역할의-무엇일까"&gt;&lt;a href="#%ea%b7%b8%eb%9f%bc-jvm%ec%9d%b4-%ed%95%98%eb%8a%94-%ec%97%ad%ed%95%a0%ec%9d%98-%eb%ac%b4%ec%97%87%ec%9d%bc%ea%b9%8c" class="header-anchor"&gt;&lt;/a&gt;그럼 JVM이 하는 역할의 무엇일까?
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Java는 OS에 종속적이지 않다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;위와 같은 조건을 충족 시키며 작성한 코드가 실행되기 위해선 Java와 OS사이에 무언가가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그게 바로 JVM이다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="코드-실행-과정"&gt;&lt;a href="#%ec%bd%94%eb%93%9c-%ec%8b%a4%ed%96%89-%ea%b3%bc%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;코드 실행 과정
&lt;/h2&gt;&lt;p&gt;작성한 소스코드인(원시코드) &lt;code&gt;*.java&lt;/code&gt; 를 CPU가 인식하기 위해선 &lt;strong&gt;기계어&lt;/strong&gt;(010101000101&amp;hellip;)로 변환이 이루어져야 한다.&lt;/p&gt;
&lt;h3 id="그럼-java-가-바로-기계어로-변환되어-실행이-되는건가"&gt;&lt;a href="#%ea%b7%b8%eb%9f%bc-java-%ea%b0%80-%eb%b0%94%eb%a1%9c-%ea%b8%b0%ea%b3%84%ec%96%b4%eb%a1%9c-%eb%b3%80%ed%99%98%eb%90%98%ec%96%b4-%ec%8b%a4%ed%96%89%ec%9d%b4-%eb%90%98%eb%8a%94%ea%b1%b4%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;그럼 *.java 가 바로 기계어로 변환되어 실행이 되는건가&amp;hellip;?
&lt;/h3&gt;&lt;p&gt;아니다. &lt;code&gt;*.java&lt;/code&gt; 파일은 우선 JVM이 인식을 할 수 있도록 &lt;strong&gt;java bytecode&lt;/strong&gt;(&lt;code&gt;*.class&lt;/code&gt;)로 변환이 이루어진다.&lt;/p&gt;
&lt;p&gt;이 변환과정은 &lt;strong&gt;java 컴파일러&lt;/strong&gt;에 의해 수행이 되어진다.&lt;/p&gt;
&lt;div class="stack-alert stack-alert--caution"&gt;
 &lt;div class="stack-alert__icon"&gt;✅&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;java 컴파일러는 JDK를 설치하면 bin폴더에 존재하는 &lt;code&gt;javac.exe&lt;/code&gt; 이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;javac&lt;/code&gt; 명령어를 통해 &lt;code&gt;.class&lt;/code&gt; 파일을 생성할 수 있고&lt;/li&gt;
&lt;li&gt;&lt;code&gt;java&lt;/code&gt; 명령어를 통해 이 &lt;code&gt;.class&lt;/code&gt;파일을 실행시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="이제-os에서-실행이-되는건가"&gt;&lt;a href="#%ec%9d%b4%ec%a0%9c-os%ec%97%90%ec%84%9c-%ec%8b%a4%ed%96%89%ec%9d%b4-%eb%90%98%eb%8a%94%ea%b1%b4%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;이제 OS에서 실행이 되는건가..?
&lt;/h3&gt;&lt;div class="stack-alert stack-alert--warning"&gt;
 &lt;div class="stack-alert__icon"&gt;⚠️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;아니다&amp;hellip;. bytecode는 기계어가 아니므로 OS에서 바로 실행되지 않는다&amp;hellip;!&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;이때 &lt;strong&gt;JVM이 OS가 이 bytecode를 이해할 수 있도록 해석해주는 역할&lt;/strong&gt;을 한다.&lt;/p&gt;
&lt;p&gt;이러한 JVM의 역할 덕분에 한 번 작성한 Java 코드가 OS에 상관 없이 실행이 될 수 있는 것이다.&lt;/p&gt;
&lt;h3 id="전체-프로세스"&gt;&lt;a href="#%ec%a0%84%ec%b2%b4-%ed%94%84%eb%a1%9c%ec%84%b8%ec%8a%a4" class="header-anchor"&gt;&lt;/a&gt;전체 프로세스
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;*.java&lt;/code&gt; → &lt;code&gt;*.class&lt;/code&gt; 인 bytecode 형태로 변환 → &lt;strong&gt;JIT(Just In Time) 컴파일러&lt;/strong&gt;를 통해 기계어(binary code)로 변환&lt;/p&gt;
&lt;h2 id="jvm_"&gt;&lt;a href="#jvm_" class="header-anchor"&gt;&lt;/a&gt;&lt;img alt="JVM_process" class="gallery-image" data-flex-basis="846px" data-flex-grow="352" height="152" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://0andwild.com/posts/220922_jvm_1/jvm_process.png" width="536"&gt;
&lt;/h2&gt;&lt;h2 id="jit-just-in-time-컴파일러란"&gt;&lt;a href="#jit-just-in-time-%ec%bb%b4%ed%8c%8c%ec%9d%bc%eb%9f%ac%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;JIT (Just In Time) 컴파일러란?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;JIT 컴파일&lt;/strong&gt; 또는 **동적번역(dynamic translation)**이라고 불린다.&lt;/p&gt;
&lt;p&gt;JIT는 &lt;strong&gt;인터프리터 방식의 단점을 보완&lt;/strong&gt;하기 위해 도입되었다.&lt;/p&gt;
&lt;p&gt;프로그램이 실제 실행하는 시점에 기계어로 번역을 한다.&lt;/p&gt;
&lt;h3 id="성능-특징"&gt;&lt;a href="#%ec%84%b1%eb%8a%a5-%ed%8a%b9%ec%a7%95" class="header-anchor"&gt;&lt;/a&gt;성능 특징
&lt;/h3&gt;&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;기계어는 캐시에 보관하기 때문에 &lt;strong&gt;한 번 컴파일된 코드는 빠르게 수행&lt;/strong&gt;이 된다.&lt;/div&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;JIT 컴파일러가 기계어로 컴파일 하는 과정은 바이트 코드를 인터프리팅하는 것보다 훨씬 &lt;strong&gt;느리지만&lt;/strong&gt; 한 번 수행되면 그 이후로는 &lt;strong&gt;빠르다&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;그러나 한 번만 실행되는 코드라면 컴파일을 하지 않고 바로 인터프리팅하는 것이 유리하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JIT 컴파일러를 사용하는 JVM은 해당 메서드가 얼마나 자주 수행되는지 체크를 하고 일정 정도를 넘을때에만 컴파일을 수행한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="인터프리터-방식이란"&gt;&lt;a href="#%ec%9d%b8%ed%84%b0%ed%94%84%eb%a6%ac%ed%84%b0-%eb%b0%a9%ec%8b%9d%ec%9d%b4%eb%9e%80" class="header-anchor"&gt;&lt;/a&gt;인터프리터 방식이란?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;인터프리터&lt;/strong&gt;는 실행 시마다 소스 코드를 한 줄씩 기계어로 번역하는 방식이기 떄문에 실행 속도는 정적 컴파일 언어보다 느리다.&lt;/p&gt;
&lt;h3 id="대표적인-인터프리터-언어"&gt;&lt;a href="#%eb%8c%80%ed%91%9c%ec%a0%81%ec%9d%b8-%ec%9d%b8%ed%84%b0%ed%94%84%eb%a6%ac%ed%84%b0-%ec%96%b8%ec%96%b4" class="header-anchor"&gt;&lt;/a&gt;대표적인 인터프리터 언어
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;파이썬 (Python)&lt;/li&gt;
&lt;li&gt;자바스크립트 (JavaScript)&lt;/li&gt;
&lt;li&gt;데이터베이스 언어인 SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="장단점"&gt;&lt;a href="#%ec%9e%a5%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;장단점
&lt;/h3&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;장점&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;프로그램 수정이 간단하다&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;실행 속도가 컴파일 언어보다 느리다&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;&lt;p&gt;&lt;strong&gt;컴파일러&lt;/strong&gt;는 소스코드를 번역해서 실행 파일을 만들기 때문에 프로그램에 수정 사항이 발생하면 소스 코드를 다시 컴파일해야 한다.&lt;/p&gt;
&lt;p&gt;프로그램이 작고 간단하면 문제가 없지만 프로그램 덩치가 커지면 컴파일이 시간 단위가 되는 일이 많아진다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;strong&gt;인터프리터&lt;/strong&gt;는 소스코드를 수정해서 실행시키면 끝이기에 수정이 빈번히 발생하는 용도의 프로그래밍에서 많이 사용된다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</description></item><item><title>객체지향 프로그래밍과 절차적 프로그래밍에 대해 알아보자</title><link>https://0andwild.com/posts/220831_about_oop/</link><pubDate>Wed, 31 Aug 2022 20:02:58 +0900</pubDate><guid>https://0andwild.com/posts/220831_about_oop/</guid><description>&lt;img src="https://0andwild.com/" alt="Featured image of post 객체지향 프로그래밍과 절차적 프로그래밍에 대해 알아보자" /&gt;&lt;h2 id="객체지향oop과-절차적-프로그래밍pp"&gt;&lt;a href="#%ea%b0%9d%ec%b2%b4%ec%a7%80%ed%96%a5oop%ea%b3%bc-%ec%a0%88%ec%b0%a8%ec%a0%81-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8dpp" class="header-anchor"&gt;&lt;/a&gt;객체지향(OOP)과 절차적 프로그래밍(PP)
&lt;/h2&gt;&lt;p&gt;객체지향언어와 절차지향언어는 &lt;strong&gt;절대 반대되는 개념이 아니다&lt;/strong&gt;. 그렇다면 객체지향언어와 절차지향언어는 무엇인가?&lt;/p&gt;
&lt;p&gt;우리는 보통 Java, Python, C# 등의 언어를 객체지향 언어라고 부르며 C언어는 절차지향언어라고 부른다. 하지만 어디까지나 이 언어들이 &lt;strong&gt;지향&lt;/strong&gt;하는 것이지 C언어는 절차적 프로그래밍만 가능하고 Java나 Python 등은 객체적 프로그래밍만 가능하다는 것이 아니다.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;어떤 언어를 사용하든 상관없이 절차지향적 프로그래밍을 할 수 있다. 반대로 C언어를 사용하더라도 객체지향적으로 코딩을 할 수 있는 것이다.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="절차지향이라는-용어의-오해"&gt;&lt;a href="#%ec%a0%88%ec%b0%a8%ec%a7%80%ed%96%a5%ec%9d%b4%eb%9d%bc%eb%8a%94-%ec%9a%a9%ec%96%b4%ec%9d%98-%ec%98%a4%ed%95%b4" class="header-anchor"&gt;&lt;/a&gt;&amp;lsquo;절차지향&amp;rsquo;이라는 용어의 오해
&lt;/h2&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;역도라는 스포츠는 바벨을 이용한 운동을 &lt;strong&gt;기반&lt;/strong&gt;으로 하는 것인데 바벨을 &lt;strong&gt;지향&lt;/strong&gt;하는 스포츠라고 하는 것과 같은 맥락이다.&lt;/li&gt;
&lt;li&gt;그럼 역도를 덤벨로 해야 하나&amp;hellip;?&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;div class="stack-alert stack-alert--tip"&gt;
 &lt;div class="stack-alert__icon"&gt;💡&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;객체지향 프로그래밍(OOP)와 절차적 프로그래밍(PP)는 어디까지나 프로그래밍을 하는데 있어 &lt;strong&gt;접근 방식의 차이&lt;/strong&gt;가 있을 뿐 반대 개념은 아니다!&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="핵심-차이점"&gt;&lt;a href="#%ed%95%b5%ec%8b%ac-%ec%b0%a8%ec%9d%b4%ec%a0%90" class="header-anchor"&gt;&lt;/a&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;hr&gt;
&lt;h2 id="절차적-언어와-객체지향언어를-구분하는-기준"&gt;&lt;a href="#%ec%a0%88%ec%b0%a8%ec%a0%81-%ec%96%b8%ec%96%b4%ec%99%80-%ea%b0%9d%ec%b2%b4%ec%a7%80%ed%96%a5%ec%96%b8%ec%96%b4%eb%a5%bc-%ea%b5%ac%eb%b6%84%ed%95%98%eb%8a%94-%ea%b8%b0%ec%a4%80" class="header-anchor"&gt;&lt;/a&gt;절차적 언어와 객체지향언어를 구분하는 기준
&lt;/h2&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;strong&gt;객체지향 성향이 강해진다&lt;/strong&gt;고 보면 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="절차적-프로그래밍"&gt;&lt;a href="#%ec%a0%88%ec%b0%a8%ec%a0%81-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d" class="header-anchor"&gt;&lt;/a&gt;절차적 프로그래밍
&lt;/h2&gt;&lt;p&gt;절차적 프로그래밍은 말 그대로 &lt;strong&gt;절차적&lt;/strong&gt;으로 코드를 구성한다는 것이다.&lt;/p&gt;
&lt;p&gt;데이터에 대한 순서를 파악하고 필요한 기능을 함수로 만들어 절차적(순서대로) 진행시키는 방식&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="객체지향-프로그래밍"&gt;&lt;a href="#%ea%b0%9d%ec%b2%b4%ec%a7%80%ed%96%a5-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d" class="header-anchor"&gt;&lt;/a&gt;객체지향 프로그래밍
&lt;/h2&gt;&lt;p&gt;객체지향 프로그래밍의 경우 기능들을 묶어 하나의 &lt;strong&gt;객체&lt;/strong&gt;로 만든다.&lt;/p&gt;
&lt;p&gt;다시 말하면 각각의 객체를 생성하고 그 객체마다 할 수 있는 행위(기능)들과 데이터를 하나로 묶어주는 것이다.&lt;/p&gt;
&lt;h3 id="예시"&gt;&lt;a href="#%ec%98%88%ec%8b%9c" class="header-anchor"&gt;&lt;/a&gt;예시
&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;: 기사가 할 수 있는 행위를 묶음&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="그럼-어떤-방식이-더-좋은가"&gt;&lt;a href="#%ea%b7%b8%eb%9f%bc-%ec%96%b4%eb%96%a4-%eb%b0%a9%ec%8b%9d%ec%9d%b4-%eb%8d%94-%ec%a2%8b%ec%9d%80%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;그럼 어떤 방식이 더 좋은가?
&lt;/h2&gt;&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;정답은 없다. 필요에 맞게 사용을 하고 자신이 선호하는 스타일을 사용하면 된다.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id="과거의-프로그래밍"&gt;&lt;a href="#%ea%b3%bc%ea%b1%b0%ec%9d%98-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d" class="header-anchor"&gt;&lt;/a&gt;과거의 프로그래밍
&lt;/h3&gt;&lt;p&gt;과거에는 현재처럼 큰 규모의 하드웨어와 소프트웨어가 필요치 않았다. 오래된 언어인 &lt;strong&gt;C, 포트란, 코볼&lt;/strong&gt; 같은 절차적 언어의 대표라 할 수 있는 언어들이 사용되어졌다.&lt;/p&gt;
&lt;h3 id="현대의-프로그래밍"&gt;&lt;a href="#%ed%98%84%eb%8c%80%ec%9d%98-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d" class="header-anchor"&gt;&lt;/a&gt;현대의 프로그래밍
&lt;/h3&gt;&lt;p&gt;현대에 들어서면서 점점 소프트웨어 발전이 빨라졌고 이에 따라 코드들도 복잡해져갔다.&lt;/p&gt;
&lt;p&gt;그러다 보니 복잡한 알고리즘들이 꼬이기 시작했고 작성한 코드를 사람이 읽었을 때 이해하기 힘들거나 이해할 수 없는 &lt;strong&gt;스파게티 코드&lt;/strong&gt;가 되어버린 것이다.&lt;/p&gt;
&lt;p&gt;이러한 문제의 대안으로 &lt;strong&gt;객체지향적 프로그래밍&lt;/strong&gt;이 나온 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="왜-객체지향이-우세한가"&gt;&lt;a href="#%ec%99%9c-%ea%b0%9d%ec%b2%b4%ec%a7%80%ed%96%a5%ec%9d%b4-%ec%9a%b0%ec%84%b8%ed%95%9c%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;왜 객체지향이 우세한가?
&lt;/h2&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;를 할 때 메리트가 떨어진다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="절차적-프로그래밍-장단점"&gt;&lt;a href="#%ec%a0%88%ec%b0%a8%ec%a0%81-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d-%ec%9e%a5%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;절차적 프로그래밍 장단점
&lt;/h2&gt;&lt;h3 id="장점"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;장점
&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;/ul&gt;
&lt;h3 id="단점"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;각 코드들의 끈끈한 우정 때문에 수정이 힘들다 (유기성이 높아 추가, 수정이 힘듦)&lt;/li&gt;
&lt;li&gt;디버그(오류검사)가 힘듦&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="객체지향-프로그래밍-장단점"&gt;&lt;a href="#%ea%b0%9d%ec%b2%b4%ec%a7%80%ed%96%a5-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d-%ec%9e%a5%eb%8b%a8%ec%a0%90" class="header-anchor"&gt;&lt;/a&gt;객체지향 프로그래밍 장단점
&lt;/h2&gt;&lt;h3 id="장점-1"&gt;&lt;a href="#%ec%9e%a5%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&gt;장점
&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;/ul&gt;
&lt;h3 id="단점-1"&gt;&lt;a href="#%eb%8b%a8%ec%a0%90-1" class="header-anchor"&gt;&lt;/a&gt;단점
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;대부분의 객체 지향 프로그램은 속도가 상대적으로 느려지고 많은 양의 메모리를 사용하는 경향이 있음&lt;/li&gt;
&lt;li&gt;현실세계와 유사성에 의해 코드를 이해하기 쉽게 만들기 위해 설계 과정에 있어 많은 시간이 들어간다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="정답은-없다-적재적소에-맞추어-사용하자"&gt;&lt;a href="#%ec%a0%95%eb%8b%b5%ec%9d%80-%ec%97%86%eb%8b%a4-%ec%a0%81%ec%9e%ac%ec%a0%81%ec%86%8c%ec%97%90-%eb%a7%9e%ec%b6%94%ec%96%b4-%ec%82%ac%ec%9a%a9%ed%95%98%ec%9e%90" class="header-anchor"&gt;&lt;/a&gt;정답은 없다! 적재적소에 맞추어 사용하자
&lt;/h2&gt;&lt;h3 id="절차적-프로그래밍을-사용하는-경우"&gt;&lt;a href="#%ec%a0%88%ec%b0%a8%ec%a0%81-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%98%eb%8a%94-%ea%b2%bd%ec%9a%b0" class="header-anchor"&gt;&lt;/a&gt;절차적 프로그래밍을 사용하는 경우
&lt;/h3&gt;&lt;p&gt;보통 프로젝트 규모가 크지 않고 재사용할 일이 크지 않는 경우에 많이 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로그램 자체가 가벼워짐&lt;/li&gt;
&lt;li&gt;객체지향으로 만드는 것보다 개발시간과 인력도 줄어듦&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="객체지향-프로그래밍을-사용하는-경우"&gt;&lt;a href="#%ea%b0%9d%ec%b2%b4%ec%a7%80%ed%96%a5-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%98%eb%8a%94-%ea%b2%bd%ec%9a%b0" class="header-anchor"&gt;&lt;/a&gt;객체지향 프로그래밍을 사용하는 경우
&lt;/h3&gt;&lt;p&gt;큰 규모의 프로젝트에서 코드들을 재사용해야 한다면 초기 개발비용을 제외하고 객체지향 프로그래밍이 적합하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;유지보수 측면에서 안정적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="마무리"&gt;&lt;a href="#%eb%a7%88%eb%ac%b4%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;마무리
&lt;/h2&gt;&lt;div class="stack-alert stack-alert--note"&gt;
 &lt;div class="stack-alert__icon"&gt;ℹ️&lt;/div&gt;
 &lt;div class="stack-alert__content"&gt;오늘은 이렇게 객체지향 프로그래밍과 절차적 프로그래밍에 대해 알아보았다.
아직은 깊이있는 내용에 대해서는 알지 못하지만 여러 글들을 찾아보며 객체지향과 절차적 프로그래밍에 대한 큰 틀을 이해하고 넘어가고 다음번에 좀 더 깊이있게 들어가보자 한다!&lt;/div&gt;
&lt;/div&gt;
</description></item></channel></rss>