<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hunter_joe99.log</title>
        <link>https://velog.io/</link>
        <description>Improvise, Adapt, Overcome</description>
        <lastBuildDate>Fri, 10 Apr 2026 11:55:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hunter_joe99.log</title>
            <url>https://velog.velcdn.com/images/hunter_joe99/profile/b00b749c-9006-4e25-b65f-c9499e3e392a/image.jfif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hunter_joe99.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hunter_joe99" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[비지터]]></title>
            <link>https://velog.io/@hunter_joe99/%EB%B9%84%EC%A7%80%ED%84%B0</link>
            <guid>https://velog.io/@hunter_joe99/%EB%B9%84%EC%A7%80%ED%84%B0</guid>
            <pubDate>Fri, 10 Apr 2026 11:55:15 GMT</pubDate>
            <description><![CDATA[<h2 id="정의">정의</h2>
<p>방문자 패턴은 알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴입니다.</p>
<p>알고리즘들을 그들이 작동하는 객체들로부터 분리한다라..</p>
<h2 id="문제">문제</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/321171a1-ccbe-4ac3-8125-24eee3ad894f/image.png" alt=""></p>
<ol>
<li>기존 클래스 변경 안돼</li>
<li>XML 내보기내기 메서드가 해당 클래스에 있는게 맞아? &lt;-SRP</li>
<li>다른 형식으로 내보내기 기능이 추가될꺼 같은데? &lt;- OCP</li>
</ol>
<h2 id="해결">해결</h2>
<p>새로운 메서드를 기존 클래스에 통합하는 대신에 <code>visitor</code>라는 별도의 클래스에 배치하자 </p>
<pre><code class="language-java">class ExportVisitor implements Visitor is
    method doForCity(City c) { ... }
    method doForIndustry(Industry f) { ... }
    method doForSightSeeing(SightSeeing ss) { ... }
    // …</code></pre>
<p>해당 메서드를 정확히 어떻게 호출할 수 있을까? 특히 전체 그래프(foreach)를 다룰 때,
이 메서드들은 시그니처들이 다르므로 다형성을 사용할 수 없습니다.</p>
<pre><code class="language-java">for Each {
    if (node instance of City) 
        exportVistor.doForCity((City) node)
    if (node instance of Industry)
        exportVisitor.doForIndustry((Industry) node)
}</code></pre>
<p>이 방법은 번거로운 조건문 없이 객체에 적절한 메서드를 실행하는 것을 목표로합니다.
객체들은 자신의 클래스들을 알고 있으므로 비지터가 올바른 메서드를 선택할 수 있도록 할 수 있습니다. </p>
<pre><code class="language-java">foreach (Node node in graph)
    node.accept(exportVisitor)

// City
class City is
    method accept(Visitor v) is
        v.doForCity(this)
    // …

// Industry
class Industry is
    method accept(Visitor v) is
        v.doForIndustry(this)
    // …</code></pre>
<blockquote>
<p>노드(Element) 안바꾼다면서 바꿨.... accept 추가했네..</p>
</blockquote>
<h2 id="다이어그램">다이어그램</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/b9264945-a3b8-4845-be29-3d5b89788b94/image.png" alt=""></p>
<h2 id="의사코드">의사코드</h2>
<pre><code class="language-java">// interface - element
interface Shape is 
    method move(x,y)
    method draw()
    method accept(v: Vistor)

// implement - concrete element
class Dot implements Shape is
    method accept(v: Visitor) is
        v.visitDot(this)

class Circle implements Shape is
    // …
    method accept(v: Visitor) is
        v.visitCircle(this)

class Rectangle implements Shape is
    // …
    method accept(v: Visitor) is
        v.visitRectangle(this)

class CompoundShape implements Shape is
    // …
    method accept(v: Visitor) is
        v.visitCompoundShape(this)

// interface - visitor
interface Visitor is 
    method visitDot(d: Dot)
    method visitCircle(c: Circle)
    method visitRectangle(r: Rectangle)
    method visitCompoundShape(cs: CompoundShape)

// implements - concrete visitor 
class XMLExportVisitor implements Visitor is
    method visitDot(d: Dot) is
        // 점의 아이디와 중심 좌표를 내보냅니다.

    method visitCircle(c: Circle) is
        // 원의 아이디, 중심 좌표 및 반지름을 내보냅니다.

    method visitRectangle(r: Rectangle) is
        // 사각형의 아이디, 왼쪽 상단 좌표, 너비 및 높이를 내보냅니다.

    method visitCompoundShape(cs: CompoundShape) is
        // 모양의 아이디와 그 자식들의 아이디 리스트를 내보냅니다.

// 클라이언트 코드 
class Application is
    field allShapes: array of Shapes[]

    method export() is
        exportVisitor = new XMLExportVisitor()

        foreach (shape in allShapes) do
            shape.accept(exportVisitor)

</code></pre>
<h2 id="더블-디스패치">더블 디스패치</h2>
<p><code>accept</code> 이 필요한 이유 더블 디스패치 아래서 한번 더 보자 </p>
<pre><code class="language-java">foreach (shape in allShapes) do
    shape.accept(exportVisitor)
</code></pre>
<p>allShapes에 Dot, Circle 등이 섞여 있지만 루프에서는 전부 Shape 타입으로 잡힘 
컴파일러는 구체적으로 뭔지 모름 </p>
<h3 id="1단계-디스패치--shape-타입-결정하기">1단계 디스패치 : Shape 타입 결정하기</h3>
<p><code>shape.accept(exportVisitor)</code>를 호출하면 런타임에 shape의 실제 타입에 따라 해당 클래스의 accept가 호출, 
예를 들어 실제로 Dot 객체라면</p>
<pre><code class="language-java">class Dot implements Shape is
    method accept(v: Visitor) is
        v.visitDot(this)  </code></pre>
<p>이 시점에서 Dot이 확정 이게 첫 번째 디스패치 </p>
<h3 id="2단계-디스패치-visitor-타입-결정">2단계 디스패치 :Visitor 타입 결정</h3>
<p><code>accept</code> 안에서<code>v.visitDot(this)</code>를 호출하면, <code>v</code>의 실제 타입에 따라 어떤 <code>visitDot</code>이 실행될지 결정
<code>v</code>가 <code>XMLExportVisitor</code>라면</p>
<pre><code class="language-java">class XMLExportVisitor implements Visitor is
    method visitDot(d: Dot) is
        // 점의 아이디와 중심 좌표를 내보냅니다.  </code></pre>
<p>한번의 호출로는 shape, visitor 두 축을 동시에 결정할 수 없어서 두번의 호출로 각각 다형성을 적용
그래서 더블 디스패치..</p>
<p>그러니깐 accept 쪽에서 자기 자신을 this로 넘겨줘야 
visitor에서 해당 구현된 shape의 데이터에 접근된다는건데 .. </p>
<h2 id="적용은">적용은</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/99e8dfe7-7966-43a0-979a-76f4a44535dd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SRP 원칙]]></title>
            <link>https://velog.io/@hunter_joe99/SRP-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@hunter_joe99/SRP-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Mon, 30 Mar 2026 05:01:18 GMT</pubDate>
            <description><![CDATA[<p>클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야 한다는 원칙 </p>
<p>Class는 해당 클래스만이 가지고 있는 기능 또는 목적이 있을 것임 </p>
<p>쉽게 말하면 모든 기능을 하나의 클래스에 다 때려박지 말라는 말 </p>
<p>그럼 그 기준은 어떻게 정하냐고 물을 수 있다. &lt;&lt; 이건 개발자 영역 </p>
<blockquote>
<p>기준이 모호하기 때문에 <em>변경을</em> 책임의 기준으로 삼으면 설계에 용이할 수 있다.</p>
</blockquote>
<p>객체를 쪼갤 때 기능으로 쪼개든 목적으로 쪼개든 다 좋은데 그 근본 목표가 유지보수를 향해있어야한다. 
즉 유지보수성을 극대화해라! S/W의 특징(유연함)
<img src="https://velog.velcdn.com/images/hunter_joe99/post/f7682022-db46-4a00-aba1-3f425fa1513f/image.png" alt=""></p>
<p>결국 처음에 언급한 대로 기준은 변경 가능성이며 그 판단은 개발자의 몫
SRP는 &quot;무조건 기능 하나당 클래스 하나&quot;가 아니라 &quot;변경 이유가 섞이지 않게 해라&quot;는 원칙</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 에러 정리 ]]></title>
            <link>https://velog.io/@hunter_joe99/%EB%8F%84%EC%BB%A4-%EC%97%90%EB%9F%AC-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hunter_joe99/%EB%8F%84%EC%BB%A4-%EC%97%90%EB%9F%AC-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 20 Mar 2026 16:35:51 GMT</pubDate>
            <description><![CDATA[<p>Docker 컨테이너에서 Telegram Bot API 호출 시 ETIMEDOUT 발생 원인과 해결                                     </p>
<p>  증상                                                                                                        </p>
<ul>
<li><p>NestJS 서버에서 에러 발생 시 Telegram Bot API로 알림을 보내는 기능                                        </p>
</li>
<li><p>간헐적으로 알림이 오지 않는 현상 발생                                                                   </p>
</li>
<li><p>서버 로그에는 에러가 정상적으로 찍히지만, Telegram 메시지는 도착하지 않음                                 </p>
<p>디버깅 과정</p>
<p>1단계: 코드 문제 확인                                                                                       </p>
<p>TelegramTransport에서 axios 에러를 .catch(() =&gt; {})로 무시하고 있어서, 실패해도 아무 로그가 남지 않았음.<br>catch에 로그를 추가하여 원인 파악 시작.                                                                   </p>
<p>// 기존 — 에러를 삼킴<br>axios.post(url, data).catch(() =&gt; {});                                                                      </p>
<p>// 디버깅용 — 에러 내용 출력<br>axios.post(url, data).catch((err) =&gt; {<br>console.error(&#39;[TelegramTransport]&#39;, err.code, err.message);<br>});                                                                                                         </p>
<p>결과: ETIMEDOUT 확인                                                                                        </p>
<p>2단계: 네트워크 문제 확인                                                                                   </p>
<h1 id="docker-컨테이너-내부에서-테스트">Docker 컨테이너 내부에서 테스트</h1>
<h1 id="wget-→-성공">wget → 성공</h1>
<p>docker exec server wget -q -O- <a href="https://api.telegram.org">https://api.telegram.org</a>                                                     </p>
<h1 id="nodejs-https-모듈-→-etimedout">Node.js https 모듈 → ETIMEDOUT</h1>
<p>docker exec server node -e &quot;require(&#39;https&#39;).get(&#39;<a href="https://api.telegram.org&#39;">https://api.telegram.org&#39;</a>, ...)&quot;                          </p>
<h1 id="google은-정상">Google은 정상</h1>
<p>docker exec server node -e &quot;require(&#39;https&#39;).get(&#39;<a href="https://google.com&#39;">https://google.com&#39;</a>, ...)&quot;                                </p>
<h1 id="→-status-301-성공">→ status: 301 (성공)</h1>
<p>wget은 되고 Node.js는 안 되는 상황.                                                                         </p>
<p>3단계: DNS 조회                                                                                             </p>
<p>docker exec server node -e &quot;<br>require(&#39;dns&#39;).resolve4(&#39;api.telegram.org&#39;, (e, a) =&gt; console.log(&#39;IPv4:&#39;, a));<br>require(&#39;dns&#39;).resolve6(&#39;api.telegram.org&#39;, (e, a) =&gt; console.log(&#39;IPv6:&#39;, a));<br>&quot;                                                                                                           </p>
<p>IPv4: [ &#39;149.154.166.110&#39; ]<br>IPv6: [ &#39;2001:67c:4e8:f004::9&#39; ]                                                                            </p>
<p>api.telegram.org는 IPv4, IPv6 둘 다 지원.                                                                   </p>
<p>4단계: IPv4 직접 연결 테스트                                                                                </p>
<p>IPv4 IP로 직접 연결 → 성공. IPv6 경로가 문제라는 것을 확인.                                                 </p>
<p>원인                                                                                                        </p>
</li>
<li><p>api.telegram.org는 IPv4/IPv6 듀얼 스택                                                                    </p>
</li>
<li><p>Node.js는 기본적으로 IPv6를 우선 사용 (happy eyeballs 알고리즘)</p>
</li>
<li><p>Docker 기본 네트워크(bridge)는 IPv6 외부 라우팅을 지원하지 않는 경우가 많음                               </p>
</li>
<li><p>IPv6로 연결 시도 → 라우팅 불가 → ETIMEDOUT                                                                </p>
</li>
<li><p>wget은 IPv4를 우선 사용하기 때문에 성공                                                                   </p>
</li>
<li><p>간헐적으로 알림이 온 이유: Node.js가 IPv4로 먼저 연결되는 경우도 있었기 때문                              </p>
<p>해결                                                                                                        </p>
<p>axios 인스턴스에 family: 4 옵션을 추가하여 IPv4 연결을 강제.                                                </p>
<p>// 변경 전<br>axios.post(this.apiUrl, { chat_id: this.chatId, text }).catch(() =&gt; {});                                  </p>
<p>// 변경 후
this.httpClient = axios.create({<br>timeout: 10000,<br>family: 4, // IPv4 강제<br>});<br>this.httpClient.post(this.apiUrl, { chat_id: this.chatId, text }).catch(() =&gt; {});                          </p>
<p>핵심 정리                                                                                                   </p>
<p>┌──────┬───────────────────────────────────────────────┐<br>│ 항목 │                     내용                      │<br>├──────┼───────────────────────────────────────────────┤<br>│ 환경 │ Docker (bridge network) + Node.js             │
├──────┼───────────────────────────────────────────────┤<br>│ 증상 │ 외부 API 호출 간헐적 ETIMEDOUT                │<br>├──────┼───────────────────────────────────────────────┤<br>│ 원인 │ Node.js IPv6 우선 + Docker IPv6 라우팅 미지원 │<br>├──────┼───────────────────────────────────────────────┤<br>│ 해결 │ family: 4로 IPv4 강제                         │<br>├──────┼───────────────────────────────────────────────┤<br>│ 교훈 │ .catch(() =&gt; {})로 에러를 삼키지 말 것        │<br>└──────┴───────────────────────────────────────────────┘  </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[사앙태 패터언 (상태패턴)]]></title>
            <link>https://velog.io/@hunter_joe99/%EC%82%AC%EC%95%99%ED%83%9C-%ED%8C%A8%ED%84%B0%EC%96%B8-%EC%83%81%ED%83%9C%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@hunter_joe99/%EC%82%AC%EC%95%99%ED%83%9C-%ED%8C%A8%ED%84%B0%EC%96%B8-%EC%83%81%ED%83%9C%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 19 Mar 2026 07:58:48 GMT</pubDate>
            <description><![CDATA[<h1 id="상태-패턴">상태 패턴</h1>
<p>객체의 내부 상태가 변경될 때 해당 객체가 ctx의 행동을 변경할 수 있도록 하는 패턴 
객체가 행동을 변경할 때 객체가 클래스를 변경한 것처럼 보일 수 있다. </p>
<ol>
<li>행동을 변경?? </li>
<li>클래스를 변경한 것처럼?? -&gt; 실제로는 변경이 일어나지 않았다는건가?<blockquote>
<p>궁금증은 아래서 </p>
</blockquote>
</li>
</ol>
<p>상태 패턴은 객체의 내부상태 변화에 따라 객체의 동작을 변경할 수 있는 패턴 
이 패턴은 유한 상태 기계의 개념과 유사 
상태 패턴은 패턴 인터페이스에 정의된 메서드 호출을 통해 전략을 전환할 수 있는 전략 패턴으로 해석 가능 </p>
<h1 id="fsm-유한-상태-기계">FSM 유한 상태 기계?</h1>
<p><strong>정해진 상태들 사이를 규칙에 따라 이동하는 기계(하드웨어)</strong>
신호등(초록, 노랑, 빨강), 자판기(돈 -&gt; 음료선택(유한)), 개찰구(open-close) 등 -&gt; </p>
<p><em>상태 패턴은 FSM이라는 이론적 개념을 소프트웨어에서 OOP로 구현한 패턴</em></p>
<h1 id="주요-개념">주요 개념</h1>
<ol>
<li>모든 주어진 순간에 프로그램이 속해 있을 수 있는 상태들의 수는 유한하다는 것. </li>
<li>현재 상태에 따라 프로그램은 특정 상태로 전환 or 전환되지 않을 수 있음 </li>
</ol>
<p>-&gt; 이러한 전환 규칙들을 천이(transition)이라고 하며 이 규칙들 또한 유한하며 결정적</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f065fc8e-896e-4963-b2c1-549de9a3a44d/image.png" alt=""></p>
<h1 id="문제점-feat절차지향">문제점 feat.절차지향</h1>
<pre><code class="language-js">class Document is
    field state: string
    // …
    method publish() is
        switch (state)
            &quot;draft&quot;:
                state = &quot;moderation&quot;
                break
            &quot;moderation&quot;:
                if (currentUser.role == &quot;admin&quot;)
                    state = &quot;published&quot;
                break
            &quot;published&quot;:
                // Do nothing.
                break
    // …</code></pre>
<blockquote>
<p><strong><em>아아.. 이런 코드는 OOP에서 OCP원칙을 위반한다고~ 쓰지말라고!</em></strong> 
아래서 한번 절차지향적/객체지향적 장단점에 대해 얘기해봄 </p>
</blockquote>
<p>조건문들에 기반한 상태 머신의 가장 큰 약점은 상태들과 상태에 의존하는 행동들을 추가할수록 분명해지겠죠?</p>
<ul>
<li>코드베이스 이빠이 커짐..(설계 단계에서 모든 상태 + 천이를 예측하기란 어려워)</li>
<li>유지 보수 빡셈 </li>
</ul>
<h1 id="해결how">해결(HOW)</h1>
<ol>
<li>상태 패턴은 모든 가능한 상태들에 대해 새로운 클래스를 만든다.</li>
<li>모든 상태별 행동들을 클래스들로 추출한다.</li>
</ol>
<p>조금 더 구체적으로는? 
3. context(ctx)라는 원래 객체는 모든 행동을 구현하는 대신 <strong>&quot;현재 상태&quot;</strong>를 나타내는 상태 객체 중 하나에 대한 참조를 저장 + 모든 상태와 관련된 작업을 해당 클래스에 위임</p>
<p>ㅇㅎ 상태를 만들겠군 <code>State</code>합성 합성 영어로는 ? 콤포지숀(Composition)<del>~</del> 
그리고 <code>State</code>는 합성이니깐 고수준 모듈(interface)이겠고 이거슬 구현한 ConcreteState가 있겠군!</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f68339bb-5e69-42f5-ba93-5b43e6b888c5/image.png" alt=""></p>
<h1 id="구체적인-해결-with-코드">구체적인 해결 With 코드</h1>
<h2 id="sudo">SUDO</h2>
<pre><code>Context = 리모컨 
State&lt;&lt;interface&gt;&gt; = 현재 TV 상태 인터페이스
ConcreteState = 실제 상태들 </code></pre><h3 id="ts">TS</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/8e8826d9-647f-4fcb-8cc5-dd4ecc7a2993/image.png" alt=""></p>
<p>일단은 간단하게 켜기/끄기만 구현</p>
<pre><code class="language-ts">interface TVState {
 pressButton(tv: TV): void;
}

// TV off 상태 
class TVOffState implements TVState {
    pressButton(tv: TV): void {
        console.log(&#39;티비가 켜짐!&#39;);
          tv.setState(new TVOnState());
    }
}

class TVOnState implements TVState {
    pressButton(tv: TV): void {
        console.log(&#39;티비가 꺼짐!&#39;);
          tv.setState(new TVOffState());
    }
}

class TV {
    private state: TVState = new TVOffState();

      setState(state: TVState) {
        this.state = state; 
    }

      public pressPower() {
        this.state.pressButton(this);
          // this를 호출한 객체.
    }
}

// -------CLIENT-------
const tv = new TV();

tv.pressPower(); // off -&gt; on 
tv.pressPower(); // on -&gt; off</code></pre>
<p>여기서 음량 조절 채널 버튼을 추가하면 어떻게 될까?
<em>*<em>상태패턴은 유한한 상태지만 많은 상태가 있어야 유의미한거잖아요! 
*</em></em></p>
<pre><code class="language-ts">interface TVState {
 pressButton(tv: TV): void;
 // 음량, 채널 
 pressChannel(direction: &#39;up&#39;| &#39;down&#39;): void
 pressVolume(direction: &#39;up&#39;|&#39;down&#39;): void
 // 그외 음소거 버튼 등등등 .. 
}</code></pre>
<h1 id="전략패턴-vs-상태패턴">전략패턴 vs 상태패턴</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/4516002d-2072-4360-868c-f9c9bfe0c51f/image.png" alt=""></p>
<p>전략 패턴과 비슷해 보이지만 한 가지 중요한 차이점이 있습니다. 상태 패턴에서의 특정 상태들은 서로를 인식하고, 한 상태에서 다른 상태로 천이(on -&gt; off)를 시작할 수 있지만 전략패턴들은 대부분 서로에 대해 알지 못한다는 것입니다.</p>
<p>전략 패턴은 사용자가 필요에따라 런타임에 바꿔!! 바꿔!! 바꿔!! 가능 (사용자가 갑)
상태 패턴은 사용자가 X &quot;응~ 니가 누르는거에 따라서 알아서 바뀔거야~&quot; (상황이 갑) </p>
<blockquote>
<p>솔직히 잘 모르겠음.. </p>
</blockquote>
<h1 id="궁금증-해결하기">궁금증 해결하기</h1>
<h2 id="1-행동을-변경">1. 행동을 변경??</h2>
<pre><code class="language-ts">const tv = new TV();  // TV 객체는 하나

tv.pressPower();  // &quot;티비가 켜짐!&quot; → ON 상태의 행동
tv.pressPower();  // &quot;티비가 꺼짐!&quot; → OFF 상태의 행동</code></pre>
<h2 id="2-클래스를-변경한-것처럼---실제로는-변경이-일어나지-않았다는건가">2. 클래스를 변경한 것처럼?? -&gt; 실제로는 변경이 일어나지 않았다는건가?</h2>
<p>상태 패턴을 사용하면 다음과 같은상태를 가진 클래스로 바꾼것처럼 행동한다는 뜻 
실제로는 변경없음, 상태만 변경됨</p>
<pre><code class="language-ts">tv.state = new TVOnState()   
tv.state = new TVOffState()  </code></pre>
<h1 id="절차지향-vs-객체지향">절차지향 vs 객체지향</h1>
<p>근데 꼭 절차지향이 나쁜건 아니거덩요. 
언어라는 도구를 잘 활용하면 좋거덩요 
꼭 oop에 갇혀 있을 필욘 없거덩요 
상황에 맞춰서 쓰면 되거덩요
그러하거덩요</p>
<h2 id="절차지향-c">절차지향 C</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;

// 상태 ENUM
typedef enum {
    TV_OFF,
    TV_ON
} TVState;

// 데이터 구조체 (행동 없음, 데이터만)
typedef struct {
    TVState state;
} TV;

// 함수가 구조체 밖에 존재
void press_power(TV* tv) {
    switch (tv-&gt;state) {
        case TV_OFF:
            printf(&quot;티비가 켜짐!\n&quot;);
            tv-&gt;state = TV_ON;
            break;
        case TV_ON:
            printf(&quot;티비가 꺼짐!\n&quot;);
            tv-&gt;state = TV_OFF;
            break;
    }
}

int main() {
    TV tv = { TV_OFF };  // 초기화
    press_power(&amp;tv);    // 포인터로 넘김
    press_power(&amp;tv);
    return 0;
}</code></pre>
<p>또한 상태가 많지 않다면 가독성 + 유지보수성은 절차지향이 편리함 (등가교환)</p>
<p>&quot;뭐가 더 자주 바뀌냐&quot; 에 따라 선택이 달라짐</p>
<p>기능(함수)이 자주 추가 -&gt; 절차지향
타입(클래스)이 자주 추가 -&gt; OOP</p>
<p>절차지향은 자료구조는 바꾸기 어렵지만 함수 추가는 EZPZ
OOP는 자료구조는 바꾸기 쉽지만 함수 추가는 HARD</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[axios/ forEach vs for of]]></title>
            <link>https://velog.io/@hunter_joe99/axios</link>
            <guid>https://velog.io/@hunter_joe99/axios</guid>
            <pubDate>Thu, 19 Mar 2026 04:04:21 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-ts">import { ENV } from &#39;@/configs/env&#39;;
import useAuthStore from &#39;@/store/use-auth-store&#39;;
import axios from &#39;axios&#39;;

type FailedQueue = Array&lt;{
  resolve: (token: string | null) =&gt; void;
  reject: (error: unknown) =&gt; void;
}&gt;;

export const apiCall = axios.create({
  baseURL: ENV.NEXT_PUBLIC_SERVER_URL,
  withCredentials: true,
});

apiCall.interceptors.request.use((config) =&gt; {
  const { accessToken } = useAuthStore.getState();
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});

let isRefreshing = false;
let failedQueue: FailedQueue = [];

const processQueue = (error: unknown, token: string | null = null) =&gt; {
  for (const { resolve, reject } of failedQueue) {
    if (error) {
      reject(error);
    } else {
      resolve(token);
    }
  }
  failedQueue = [];
};

apiCall.interceptors.response.use(
  (response) =&gt; response,
  async (error) =&gt; {
    const originalRequest = error.config;

    if (error.response?.status !== 401 || originalRequest._retry || originalRequest.url === &#39;/auth/refresh&#39;) {
      throw error;
    }

    if (isRefreshing) {
      return new Promise((resolve, reject) =&gt; {
        failedQueue.push({ resolve, reject });
      }).then((token) =&gt; {
        originalRequest.headers.Authorization = `Bearer ${token}`;
        return apiCall(originalRequest);
      });
    }

    originalRequest._retry = true;
    isRefreshing = true;

    try {
      const newToken = await useAuthStore.getState().refreshAccessToken();
      if (!newToken) {
        processQueue(new Error(&#39;Refresh failed&#39;));
        throw error;
      }

      processQueue(null, newToken);
      originalRequest.headers.Authorization = `Bearer ${newToken}`;
      return apiCall(originalRequest);
    } catch (refreshError) {
      processQueue(refreshError);
      throw refreshError;
    } finally {
      isRefreshing = false;
    }
  },
);
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ws]]></title>
            <link>https://velog.io/@hunter_joe99/ws</link>
            <guid>https://velog.io/@hunter_joe99/ws</guid>
            <pubDate>Wed, 18 Mar 2026 09:28:48 GMT</pubDate>
            <description><![CDATA[<p>⏺ allowRequest는 Socket.IO가 모든 연결 요청에 대해 가장 먼저 실행하는 검증 함수입니다.</p>
<p>  흐름                                                                                                                                       </p>
<p>  클라이언트가 WebSocket 연결 시도<br>    ↓<br>  allowRequest(req, cb) 실행<br>    ↓<br>  req.headers.origin 확인 (요청을 보낸 출처)<br>    ↓<br>  ├─ origin이 없음 → cb(null, true) → 연결 허용
  │   (Postman, curl, 서버 간 통신 등 브라우저가 아닌 경우)<br>  │<br>  ├─ origin이 corsOrigin 목록에 있음 → cb(null, true) → 연결 허용<br>  │   (예: &#39;<a href="http://localhost:3010&#39;%EC%9D%B4">http://localhost:3010&#39;이</a> 목록에 포함)<br>  │<br>  └─ origin이 목록에 없음 → cb(&#39;CORS_NOT_ALLOWED&#39;, false) → 연결 거부<br>      (예: &#39;<a href="http://evil.com&#39;%EC%97%90%EC%84%9C">http://evil.com&#39;에서</a> 접근 시도)                                                                                                  </p>
<p>  cb(err, success)의 의미                                                                                                                    </p>
<ul>
<li><p>cb(null, true) — 에러 없음, 연결 승인                                                                                                    </p>
</li>
<li><p>cb(&#39;CORS_NOT_ALLOWED&#39;, false) — 에러 메시지와 함께 연결 거부                                                                           </p>
<p>왜 필요한가                                                                                                                                </p>
<p>cors 옵션만으로는 HTTP의 preflight(OPTIONS) 요청에만 적용됩니다. WebSocket upgrade 요청은 preflight가 없어서 브라우저가 CORS를 강제하지<br>않습니다. 그래서 allowRequest로 서버 측에서 직접 Origin을 검사해야 합니다.                                                               </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ts, DTO ]]></title>
            <link>https://velog.io/@hunter_joe99/ts-DTO</link>
            <guid>https://velog.io/@hunter_joe99/ts-DTO</guid>
            <pubDate>Fri, 13 Mar 2026 19:58:44 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하면서 
기존 코드에 DTO가 적힌 부분을봤는데 의문점이 들어서 간단하게 정리용으로 작성함 후에 더 깊게 파야함 </p>
<h1 id="발단">발단</h1>
<pre><code class="language-ts">class UserDto {
  address: string; 
  signature: string;
}

@Post(&#39;login/verify&#39;)
async login(@Body() dto: UserDto, @Res() res:Response ): any {
      const result: { accessToken: string; refreshToken: string } = await this.authService.login(
      dto.address,
      dto.signature,
    );
}</code></pre>
<ul>
<li>사용 스택: nestjs</li>
</ul>
<p>일단 사건의 발단은 저 코드에서 UserDto가 실제로 컴파일타임, 런타임에 무슨 역할을 하는지 궁금했음 </p>
<p>내가 알고 있는 TS에대한 지식으로는 저렇게 클래스를 선언하면 변수를 할당하지 않는 이상 해당 클래스는 아무런 프로퍼티가 없는 상태다 
<code>class MetaMaskVerifyDto {}</code> &lt;- 컴파일 시점에는 빈 객체
이게 과연 런타임에 무슨 의미가 있을까, 
그리고 어떻게 <code>dto.[property]</code>에 접근할 수 있었을까?</p>
<p>조금 더 질문을 좁혀보자면 다음과 같다.</p>
<ol>
<li><p>interface/type과 비교해 class로 작성하는 이유?</p>
</li>
<li><p>컴파일시점과 런타임에는 어떻게 동작하는가?</p>
</li>
<li><p>dto를 js/ts에는 어떻게 다뤄야하는가? </p>
</li>
</ol>
<h1 id="1">1</h1>
<p>일단 ts 공식문서에서는 찾지 못했지만 내가 사용하는 프레임워크인 nestjs공식문서에서 다음과같은 글을 찾았따.
<a href="https://docs.nestjs.com/controllers">https://docs.nestjs.com/controllers</a>
Before we proceed (if you&#39;re using TypeScript), we need to define the DTO (Data Transfer Object) schema. A DTO is an object that specifies how data should be sent over the network. We could define the DTO schema using TypeScript interfaces or simple classes. <em><strong>However, we recommend using classes here. Why? Classes are part of the JavaScript ES6 standard, so they remain intact as real entities in the compiled JavaScript</strong></em>. In contrast, TypeScript interfaces are removed during transpilation, meaning Nest can&#39;t reference them at runtime. This is important because features like Pipes rely on having access to the metatype of variables at runtime, which is only possible with classes.</p>
<p>Let&#39;s create the CreateCatDto class:</p>
<pre><code>create-cat.dto.tsJS

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}</code></pre><p>DTO를정의할 때 interface/type을 상용하게 되면 트랜스파일링할때 지워져서 해당 entity에 접근할 수 없다. 
그렇기 때문에 class로 작성하는 것을 권장한다. </p>
<p>그리고후에 pipe를 활용할 만들어 놓은 dto 객체가 사용된다고 명시되어 있다.(like Pipes rely on having access to the metatype of variables at runtime, which is only possible with classes)</p>
<p>즉, interface/type을 사용해도 상관은 없지만 nest에서는 다른 feature들을 사용하기 위해서는 class로 dto를 정의한다고 보면된다.</p>
<h1 id="2">2</h1>
<p>1번에서 많은 의문점이 해소되었지만
컴파일시점 js로 변환됐을 때는 해당 class의 껍데기만 남게된다.</p>
<pre><code>class UserDto {}</code></pre><p>그리고 런타임 시점에서는 아무런 역할도 하지 않는다. 그저 객체가 body로 들어오면 아. 들어왔구나 통과~ 다음 로직에서 처리하게 된다.</p>
<p>즉 </p>
<pre><code>this.authService.login(
      dto.address,
      dto.signature,
    );</code></pre><p>이렇게 dto.address -&gt; json에 address property가 없다? 그냥 undifined로 할당 -&gt; 다음 로직 실행 </p>
<h1 id="3">3</h1>
<p>DATA TRANSFER OBJECTS</p>
<p>데이터를 전달하는 객체...</p>
<p>아마 zod를 몇번 더 써보면 정확하게 답이 나올거같아서 
현재는 더 길게는 안적을꺼같음</p>
<p>처음에 작성할 때 의문은 1,2,3 이렇게 3개였는데
1번에서 꽤 많은 의문점이 해소되었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[tcp ]]></title>
            <link>https://velog.io/@hunter_joe99/tcp</link>
            <guid>https://velog.io/@hunter_joe99/tcp</guid>
            <pubDate>Thu, 05 Mar 2026 08:03:17 GMT</pubDate>
            <description><![CDATA[<p>Transmission control protocol
데이터를 목적지까지 운송하는 역할 </p>
<p>http는 데이터를 달라고 요청만 함 그 중간에 데이터가 유실되거나 순서가 섞였는지는 확인하지 않음 이러한 복잡하고 중요한 일은 tcp가 해결함 </p>
<ul>
<li>데이터 분할 : 큰 데이터를 작은 단위의 페킷 형태로 전송 </li>
<li>순서 보장 : 페킷들을 도착지에서 순서가 바뀌어도 다시 올바른 순서로 조립 </li>
<li>재전송 : 가는 길에 데이터가 유실되면 다시 보내줘라고 요청해서 누락 없이 전달. </li>
</ul>
<p>그럼 과연 재전송하면 새로 오나? 아니면 빠진 부분만 가져오나?</p>
<p>TCP의 동작 방식 3-way-handshake
TCP는 데이터를 보내기전에 상대방과 연결이 가능한 상태인지 먼저 확인하는 절차를 거침 
이를 3-way-handshake라고 함 </p>
<ol>
<li>클라이언트 : 나 데이터 보내도 돼?</li>
<li>서버 : 나도 보내도 돼?</li>
<li>클라이언트 : 응 그럼 보낼께~ </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[면접 후기 ]]></title>
            <link>https://velog.io/@hunter_joe99/%EB%A9%B4%EC%A0%91-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hunter_joe99/%EB%A9%B4%EC%A0%91-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 05 Mar 2026 04:46:45 GMT</pubDate>
            <description><![CDATA[<p>이번 면접 후기 
생각보다 기초지식이 부족하다는 것을 느낌 </p>
<ul>
<li><p>기술스택에만 집중했던것을 후회함 
오히려 그 스택중 기본이 되는 것을 더욱 파고 드는게 중요 </p>
</li>
<li><p>왜? 라는 의문점을 항시 가져야 함 
그냥 좋아서 썼어요 &lt;- 실제로 이런 경우는 잘 없겠지만 
그 부분이 왜 좋았는지 기존의 문제는 무엇이였는지, 다른 것들과 비교해볼 때 어떤게 좋았는지, 새로운 문제를 발생시키지 않을지 
등 다양한 요소들에 대해서도 생각해보는게 좋음 </p>
</li>
<li><p>기초 결국 FE 개발자는 브라우저 , 네트워크, 이런것에 기초가 단단하고 사용하는 Js 코어, React 코어 
그리고 그 배경까지 빠삭하게 이해하는게 중요함 </p>
</li>
<li><p>개발자라면 논리적인 추론 흐름을 꼭 연습하는게 중요함</p>
</li>
<li><p>전체 프로젝트를 진행하는 것도 중요하지만, 모듈식으로 부분 부분 내가 필요한 부분을 만들어서 공부하는 것도 중요한 방식임 </p>
</li>
</ul>
<p>일단은 여기까지 작성하고 후에 이런 부분들을 고려해서 글을 작성할 때 참고해야겠음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트리쉐이킹]]></title>
            <link>https://velog.io/@hunter_joe99/%ED%8A%B8%EB%A6%AC%EC%89%90%EC%9D%B4%ED%82%B9</link>
            <guid>https://velog.io/@hunter_joe99/%ED%8A%B8%EB%A6%AC%EC%89%90%EC%9D%B4%ED%82%B9</guid>
            <pubDate>Sat, 21 Feb 2026 18:55:53 GMT</pubDate>
            <description><![CDATA[<p>전 
<img src="https://velog.velcdn.com/images/hunter_joe99/post/700b8033-cc29-4596-9df2-ccf8d497662d/image.png" alt="">
후 
<img src="https://velog.velcdn.com/images/hunter_joe99/post/0704584d-8c60-4c4a-a921-ae6ada548b3a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - CI/CD 자동화, (웹 서버 기준)]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-CICD-%EC%9E%90%EB%8F%99%ED%99%94-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EA%B8%B0%EC%A4%80</link>
            <guid>https://velog.io/@hunter_joe99/AWS-CICD-%EC%9E%90%EB%8F%99%ED%99%94-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EA%B8%B0%EC%A4%80</guid>
            <pubDate>Mon, 19 Jan 2026 18:59:35 GMT</pubDate>
            <description><![CDATA[<p>글이 중구난방일거고 읽으시는 분들한테도 그닥 도움 안될 수 있지만 
이 글의 전반적인 흐름은 다음과 같습니다. </p>
<ol>
<li>Docker Hub 가입 및 설정 + Docker Hub 필요 이유</li>
<li>git actions 작성 </li>
<li>git runners(셀프 호스팅) 설정</li>
<li>nginx + Let&#39;s Encrypt (SSL 인증서)</li>
</ol>
<h1 id="docker-hub">Docker Hub</h1>
<p>완전 자동화 CI/CD 구축을 위해서는 Docker Hub의 repository를 이용하면 개굿임 진짜 편함! </p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/651fd0ef-fee1-471b-85dd-d4c91b753581/image.png" alt="">
사진처럼 도커 이미지 생성하는 법을 알아보자 </p>
<ol>
<li>ID + 비밀번호 or Access Token 필요 
Access Token의 권한은 Read &amp; Write 정도는 필수 </li>
</ol>
<h2 id="docker-hub-무료-플랜-참고용-그닥-쓸모-x">Docker Hub 무료 플랜 (참고용 그닥 쓸모 X)</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/991d3d56-3eb4-416f-9132-1aaf673a684c/image.png" alt=""></p>
<blockquote>
<p>여담인데 배포해보니깐 그냥 대머리 될거같음 공짜 공짜 공짜.. ㄹㅇ 공짜만 찾아 댕기는 듯 ㅠ....</p>
</blockquote>
<h2 id="다시-본론-">다시 본론 ...</h2>
<p>무튼 Docker ID, PW or Access Token 이 필요한 이유는 </p>
<p>이따가 GitHub Actions를 이용한 CI/CD를 구축하기 위해 보안 정보가 필요 함 ( 아래 사진 참고 )</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/a28a2ee2-8930-4722-9440-42b596e6a994/image.png" alt=""></p>
<h2 id="docker-hub-회원가입-했지">Docker Hub 회원가입 했지?</h2>
<p>만약 보안을 위해 Private으로 유지하고 싶다면 EC2 터미널에 접속해서 아래 명령어를 딱 한 번만 실행 ㄱㄱ </p>
<pre><code># EC2 터미널에서 입력
docker login -u [도커_허브_아이디]
# Password 입력창이 뜨면 &#39;비밀번호&#39;가 아닌 발급받은 &#39;Access Token&#39;을 입력하세요.</code></pre><p>이렇게 한 번 로그인해두면 그 이후부터는 GitHub Actions가 도커 이미지를 올리고 EC2가 내려받는 과정이 막힘없이 진행됨 </p>
<h1 id="git-action">git action</h1>
<p>&quot;배포 스크립트&quot;,&quot;CI/CD 파이프라인&quot;, &quot;깃헙 Actions 설정&quot; 이 중에 뭐라고 부르는지는 잘 모르겠지만 
일단 이 코드는 잘 자기 프로젝트에 맞게 작성할 것</p>
<pre><code class="language-yml"># .github/workflows/deploy.yml

name: My server CI/CD

on:
  push:
    branches: [ main ] # 메인 브랜치에 푸시될 때 실행 </code></pre>
<blockquote>
<p>현재 <code>branches: [main]</code>으로 되어 있는데 
이건 CI/CD를 구축하기에 안전한건 아님 
왜냐하면 git Ubuntu가 내 SSH에 들어가서 직접적으로 수정하는거라서  SSH 22번 포트를 내 ip 외에도 열어줘야함 </p>
<blockquote>
<p>난 일단 초짜라 요금 폭탄 맞기 싫어서 누구한테도 안열어 줄겅미</p>
</blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/8b8d64ba-3d42-46e1-bf15-a70aee49f44b/image.png" alt=""></p>
<h1 id="git-self-hosted-runner">Git Self-Hosted Runner</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/6fe82198-a572-4d77-99c5-425fb4f497b7/image.png" alt=""></p>
<p>5분은 무슨 한 30분 걸린듯. ㅠ </p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/0948571f-eb3e-4eff-98d1-7683ba1e9afe/image.png" alt=""></p>
<p>이대로 실행하면 되긴하는데 약간의 디테일이 있음 
Download부분은 저 대로 EC2 터미널에 하나씩 입력하면되고 </p>
<p>Configure에 
<code>./config.sh --url https://~~~</code>입력한 뒤에 
./run.sh 대신에 아래 명령어 하면 좋음 </p>
<pre><code># 서비스를 설치합니다. (관리자 권한 필요)
sudo ./svc.sh install

# 서비스를 시작합니다.
sudo ./svc.sh start

# 서비스 상태를 확인합니다.
sudo ./svc.sh status</code></pre><p>이렇게하면 다음과 같다고 함 
<img src="https://velog.velcdn.com/images/hunter_joe99/post/d2587441-7633-45be-bc69-a813fd21a218/image.png" alt=""></p>
<h2 id="dependency-에러-생길경우">Dependency 에러 생길경우</h2>
<pre><code># Amazon Linux 2023 기준 수동 설치
sudo dnf install libicu -y</code></pre><h1 id="nginx--lets-encrypt">nginx + Let&#39;s Encrypt</h1>
<p><code>docker-compose.prod.yml</code>에 nginx에 대한 필요한 코드 작성해주고~ 
<code>nginx.conf</code>도 작성해주고~ </p>
<h2 id="내-폴더-구조는-이래용-nestjs">내 폴더 구조는 이래용 (NestJS)</h2>
<p>나는 nginx + certbot 폴더 생성해줬음 </p>
<pre><code>server
├── src
├── nginx/
│   └── default.conf
└── certbot/
    ├── conf/
    │   └── .gitkeep  
    └── www/
        └── .gitkeep  </code></pre><p>폴더구조는 자기 환경에 맞게끔 잘 조절해주면 GOOD!</p>
<pre><code class="language-shell"># 1. 먼저 전체 서비스를 실행합니다. (이때 Nginx는 인증서가 없어 에러가 날 수 있지만 일단 띄웁니다)
docker compose -f docker-compose.prod.yml up -d

# 2. Certbot을 이용해 인증서를 발급받습니다.
docker compose -f docker-compose.prod.yml run --rm certbot certonly --webroot --webroot-path /var/www/certbot -d &lt;url&gt;</code></pre>
<p>발급 성공하믄 
근데 아마 여기서 발급받을 때 소스코드 부분 몇번 변경을 할거임</p>
<ul>
<li>포트 열어주고 -&gt; 다시 닫아주고 이 과정 한번 거쳐야함</li>
</ul>
<p>SSL 인증서까지 받았따면!? <code>docker compose restart nginx</code> ㄱㄱ 리스타트 </p>
<h3 id="참고로-인증서는-다음과-같음">참고로 인증서는 다음과 같음</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/d84765e1-d84a-4eca-99a4-d333643eaf14/image.png" alt=""></p>
<p>여기까지 했다면 배포 굿 잘 된것임!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - Elastic IP 설정]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-Elastic-IP-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hunter_joe99/AWS-Elastic-IP-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 19 Jan 2026 18:03:29 GMT</pubDate>
            <description><![CDATA[<h1 id="elastic-ip">Elastic IP</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/4fe47913-cc41-44a8-b299-3a51a8d3bbc8/image.png" alt="">
AWS EC2의 <strong>&#39;퍼블릭 IPv4 주소&#39;</strong>는 인스턴스를 중지(Stop)했다가 다시 시작(Start)할 때마다 변경될 가능성이 있음 </p>
<ul>
<li><p>문제점: IP가 바뀌면 Route 53에 등록한 설정값도 매번 수동으로 수정해야 함 + 그동안 서비스 접속이 끊김</p>
</li>
<li><p>해결책: AWS 콘솔의 &#39;탄력적 IP&#39; 메뉴에서 고정 IP를 할당받아 인스턴스에 연결한 후, 그 고정된 IP 주소를 Route 53 값에 입력 권장</p>
</li>
</ul>
<blockquote>
<p>Elastic IP는 결국 돈나감 ㅠ </p>
</blockquote>
<blockquote>
<p>편의상 Route 53으로 설명했는데 필자는 Route 53 대신 
가비아(국내) DNS 관리 툴에서 설정 했음</p>
</blockquote>
<ul>
<li>Route53은 무료 플랜에서 사용 불가 항목 <img src="https://velog.velcdn.com/images/hunter_joe99/post/7c5955b1-0bce-4bb0-915a-9b09e1bcccfe/image.png" alt=""></li>
</ul>
<table>
<thead>
<tr>
<th align="center">타입</th>
<th align="center">호스트</th>
<th align="center">IP주소</th>
<th align="center">TTL</th>
</tr>
</thead>
<tbody><tr>
<td align="center">A</td>
<td align="center">api</td>
<td align="center">x.xx.xx.xxx</td>
<td align="center">300or 3600</td>
</tr>
</tbody></table>
<p>호스트(api) : <code>api.your-domain.com</code> &lt;- 에서 앞 <code>api.</code> 부분 설정임 </p>
<p>암튼 핵심은 도메인 연결은 기본 퍼블릭 IP는 바뀔 수 있으니 고정 IPdㅣㄴ Elastic IP(탄력적 IP)를 받아서 설정 해놓으면 수고스러움을 덜 수 있다.</p>
<p>도메인 연결은 검색하면 자료가 많아서 굳이 더 다루지 않겠음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - EC2 설정 + RDS 연결]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-EC2-%EC%84%A4%EC%A0%95-RDS-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@hunter_joe99/AWS-EC2-%EC%84%A4%EC%A0%95-RDS-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Sun, 18 Jan 2026 19:24:36 GMT</pubDate>
            <description><![CDATA[<p>혹시라도 이거 따라하시는 분 막히시면 알려주세요 
<a href="mailto:hunterjoe9999@gmail.com">hunterjoe9999@gmail.com</a> or 댓글 환영입니다</p>
<h1 id="1-ec2-인스턴스-생성">1. EC2 인스턴스 생성</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/75c1f931-b61a-45b5-8c34-2532a25ff0fc/image.png" alt=""></p>
<ul>
<li>amzaon linux - 2023 사용 </li>
<li>t3.micro</li>
</ul>
<p>무료 플랜은 이거 사용하면 됨 
우분투는 시간당 0.016이라 패스 (극강의 효율 가즈아) </p>
<ul>
<li>키를 생성하면 <code>&lt;key pair&gt;.pem</code>이런걸 다운로드 하는데 
AWS 키페어는 EC2 인스턴스에 처음 접속하기 위한 마스터 키</li>
<li><blockquote>
<p>나중에 git action에 ssh 연결할 떄 쓰임</p>
</blockquote>
</li>
</ul>
<h1 id="2-접속">2. 접속</h1>
<pre><code class="language-shell"># .pem 파일 드래그 앤 드랍해서 .pem 파일 위치 넣기
chmod 400 &lt;keypair-경로.pem&gt;

# 퍼블릭 DNS을(를) 사용하여 인스턴스에 연결
ssh -i &lt;keypair-경로.pem&gt; ec2-user@내-퍼블릭-IP주소&gt;</code></pre>
<blockquote>
<p>*<em>chmod 400 *</em>
chmod 400은 파일 소유자에게만 읽기(read) 권한을 부여하고, 그룹 및 다른 사용자에게는 어떠한 권한도 주지 않는(권한 없음) 설정</p>
</blockquote>
<h1 id="ec2-linux-shell">ec2-linux-shell</h1>
<p>일단 여기까지 왔으면 아래 사진처럼 접속이 됨
<img src="https://velog.velcdn.com/images/hunter_joe99/post/7fc1748c-b1af-439c-baa7-5e94a15a5286/image.png" alt=""></p>
<p>docker, docker-compose, git이 필요함 
설정하는건 다음과 같음 </p>
<pre><code class="language-shell">
sudo dnf update -y # 보안 패치 및 버그 수정의 이유로 패키지 업데이트 
sudo dnf install -y docker # Docker 엔진을 설치
sudo systemctl start docker # Docker 서비스 시작 
sudo systemctl enable docker # EC2 재부팅해도 Docker가 자동으로 시작되게 설정
sudo usermod -aG docker ec2-user # ec2-user를 docker 그룹에 추가
sudo dnf install -y docker-compose-plugin #  Docker Compose 플러그인 설치
docker compose version # docker-compose 버전 확인
sudo dnf install -y git # 깃 설치
git --version # 깃 버전 확인 </code></pre>
<blockquote>
<p><code>sudo usermod -aG docker ec2-user</code>
역할: ec2-user를 docker 그룹에 추가
왜 필요? Docker 명령어는 기본적으로 root 권한 필요
효과: sudo 없이도 docker 명령어 사용 가능</p>
</blockquote>
<pre><code class="language-shell"># 이 명령 전
sudo docker ps  # sudo 필요
&gt;
# 이 명령 후 (재로그인 필요)
docker ps </code></pre>
<p>⚠️ 주의: 적용하려면 로그아웃(<code>exit</code>) 후 재로그인 필요w
재로그인: <code>ssh -i &lt;keypair.pem&gt; ec-user@퍼블릭 ip주소</code></p>
<pre><code>
# git 설정 (레포지토리가 private 일때만)
```shell
# ec2 터미널에서 
ssh-keygen -t ed25519 -C &quot;내이메일@example.com&quot;</code></pre><p>엔터 3번 -&gt; <code>cat ~/.ssh/id_ed25519.pub</code>(공개 키 확인 및 복사)</p>
<pre><code class="language-shell"># 공개키는 다음과같이 생김 
`ssh-ed25519 AAAA... 내이메일@example.com`</code></pre>
<h4 id="github-저장소에-키-등록">GitHub 저장소에 키 등록</h4>
<ol>
<li><p>본인의 GitHub Private 저장소 페이지로 이동</p>
</li>
<li><p>Settings 탭 클릭 → 왼쪽 메뉴에서 Deploy keys</p>
</li>
<li><p>Add deploy key 버튼을 클릭</p>
</li>
<li><p><code>Title: EC2-Server</code> (본인이 알아보기 쉬운 이름)</p>
</li>
<li><p>Key: 방금 복사한 문자열을 붙여넣기</p>
</li>
<li><p>Add key를 눌러 저장 (Write 권한은 필요 없으므로 체크하지 않아도 됨)</p>
</li>
</ol>
<p>이렇게 설정하면 SSH로 이제 <code>git clone</code> 가능</p>
<h1 id="git-clone-레포-복사">git clone (레포 복사)</h1>
<h4 id="1-ssh-연결-테스트-가장-먼저-확인">1. SSH 연결 테스트 (가장 먼저 확인!)</h4>
<pre><code>ssh -T git@github.com</code></pre><ul>
<li>Hi 계정명/저장소명! You&#39;ve successfully authenticated... 라고 나오면 성공</li>
</ul>
<p>ec2의 루트 경로 <code>[ec-user@...]</code>에서 </p>
<ul>
<li>현재 위치한 경로 알고 싶으면 : <code>pwd</code></li>
</ul>
<pre><code class="language-shell"># SSH 주소!!!
git clone git clone git@github.com:계정명/저장소명.git</code></pre>
<h1 id="env-설정">.env 설정</h1>
<pre><code class="language-shell"># 1. env 파일 만듦 
vi .env

# 2. `i` 를 눌러 -- INSERT -- 모드로 바꾼 뒤
.env파일 복붙 

# 파일 저장 &amp; 나가기
Esc #키를 누름 (입력 모드 해제).
:wq # 입력 (Write and Quit).
Enter # 터미널로 돌아옵니다.</code></pre>
<h1 id="compose-build-requires-buildx-017-or-later">compose build requires buildx 0.17 or later</h1>
<p>이런 경고가 뜨면 
버전 업글해주면됨</p>
<pre><code class="language-shell">mkdir -p ~/.docker/cli-plugins

curl -L https://github.com/docker/buildx/releases/download/v0.19.1/buildx-v0.19.1.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx

chmod +x ~/.docker/cli-plugins/docker-buildx

docker buildx version</code></pre>
<h1 id="rds-연결까지">RDS 연결까지</h1>
<pre><code># nc(netcat) 명령어로 해당 IP와 포트 통신 확인
sudo dnf install -y nc # 설치
nc -zv &lt;프라이빗IP&gt; &lt;dbport&gt; # 확인</code></pre><p><img src="https://velog.velcdn.com/images/hunter_joe99/post/e776ab09-d47f-40e4-8792-1aff4ec68c9d/image.png" alt="">
데이터베이스(RDS)는 일종의 &#39;성&#39;이고, 보안 그룹은 그 성문을 지키는 &#39;수문장&#39;입니다. 현재 EC2가 성 안으로 들어오려고 하지만, RDS의 수문장(default)이 막고 있는 상태입니다. 따라서 default 보안 그룹의 인바운드 규칙을 수정하여 EC2(launch-wizard-3)를 통과시켜줘야 합니다.</p>
<blockquote>
<p>나는 다음과같음 
default가 rds 설정할 떄 썼던거고
launch-wizard-3가 내 ec2 인스턴스에 추가한거 </p>
</blockquote>
<h4 id="보안-그룹-규칙-추가-순서">보안 그룹 규칙 추가 순서</h4>
<ol>
<li><p><code>default</code></p>
</li>
<li><p>인바운드 규칙 편집: 하단 탭에서 <strong>인바운드 규칙(Inbound rules)</strong>을 선택하고, 오른쪽의 인바운드 규칙 편집(Edit inbound rules) 버튼을 누릅니다.</p>
</li>
<li><p>규칙 추가: 아래와 같이 새로운 규칙을 입력합니다.</p>
</li>
</ol>
<ul>
<li><p>유형: PostgreSQL (선택하면 자동으로 포트 범위가 5432로 설정됩니다.)</p>
</li>
<li><p>프로토콜: TCP (자동 설정)</p>
</li>
<li><p>포트 범위: 5432 (자동 설정)</p>
</li>
</ul>
<ol start="4">
<li><p>소스(Source): &#39;사용자 지정&#39;을 선택한 뒤, 옆의 입력창에 launch-wizard-3의 보안 그룹 ID를 입력합니다. (입력창을 클릭하면 목록에 sg-... (launch-wizard-3)가 나타날 텐데, 그걸 선택하는 것이 가장 정확하고 안전합니다.)</p>
</li>
<li><p>저장: 하단의 규칙 저장(Save rules) 버튼을 누릅니다.</p>
</li>
</ol>
<p>자 이제 다시 네트워크 확인해보자 </p>
<pre><code class="language-shell">nc -zv &lt;프라이빗IP&gt; &lt;dbport&gt; # 확인
# &gt;connected 뜨면 정상</code></pre>
<h1 id="aws-ec2-보안-그룹-확인">AWS EC2 보안 그룹 확인</h1>
<p>아까 확인하신 launch-wizard-3 보안 그룹은 현재 EC2 인스턴스의 &#39;성벽&#39; 역할을 합니다. 여기에 3000번 포트를 열어주는 규칙이 있는지 확인해야 합니다.</p>
<ol>
<li><p>AWS 콘솔 → EC2 인스턴스 목록에서 현재 실행 중인 인스턴스를 클릭합니다.</p>
</li>
<li><p>하단의 보안(Security) 탭을 누르고, 보안 그룹(Security groups) 아래에 있는 launch-wizard-3 링크를 클릭합니다.</p>
</li>
<li><p>인바운드 규칙(Inbound rules) 탭에서 <strong>인바운드 규칙 편집(Edit inbound rules)</strong>을 누릅니다.</p>
</li>
<li><p>아래 규칙을 추가하고 저장하세요.</p>
</li>
</ol>
<ul>
<li>유형: 사용자 지정 TCP</li>
<li>포트 범위: 3000</li>
<li>소스: Anywhere-IPv4 (또는 주소창에 0.0.0.0/0 입력)</li>
</ul>
<ol start="5">
<li>저장 </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - RDS postgreSQL 설정]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-RDS</link>
            <guid>https://velog.io/@hunter_joe99/AWS-RDS</guid>
            <pubDate>Sat, 17 Jan 2026 09:03:07 GMT</pubDate>
            <description><![CDATA[<p>*<em>AWS RDS DB *</em>만들고 난 뒤에 local개발 환경과 동기화 시키는 방법 작성</p>
<h1 id="1퍼블릭-액세스-설정-변경하기">1.퍼블릭 액세스 설정 변경하기</h1>
<p>RDS 콘솔로 가서 해당 → 데이터베이스를 클릭합니다.</p>
<p>상단의 → [수정(Modify)] 버튼을 누릅니다.</p>
<p>화면을 아래로 쭉 내려서 [연결(Connectivity)] 섹션을 찾습니다.</p>
<p><strong>[추가 구성(Additional configuration)]</strong>을 클릭하여 메뉴를 펼칩니다.</p>
<p>퍼블릭 액세스(Public access) 항목을 <strong>[예(Yes)]</strong>로 변경합니다.</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/caf55e49-51c2-433d-ab0c-06c541da5397/image.png" alt="">
맨 아래로 내려가 <strong>[계속(Continue)]</strong>을 누릅니다.</p>
<p>수정 예약에서 반드시 <strong>[즉시 적용(Apply immediately)]</strong>을 선택하고 <strong>[DB 인스턴스 수정]</strong>을 누르세요.</p>
<p>주의: &#39;다음 예약된 유지 관리 기간에 적용&#39;을 선택하면 당장 바뀌지 않습니다.</p>
<hr>
<h1 id="2보안-그룹security-group에서-내-ip-허용하기">2.보안 그룹(Security Group)에서 내 IP 허용하기</h1>
<p>퍼블릭 액세스를 켰더라도, 보안을 위해 &quot;특정 IP(사용자님의 컴퓨터)만 접속을 허용하겠다&quot;는 설정을 반드시 해줘야 합니다.</p>
<p>RDS 콘솔의 DB 인스턴스 상세 페이지에서 [연결 및 보안] 탭을 클릭합니다.</p>
<p>오른쪽의 [VPC 보안 그룹] 아래에 있는 파란색 링크(예: sg-0abc...)를 클릭합니다.</p>
<p>화면 하단의 [인바운드 규칙] 탭을 선택하고 [인바운드 규칙 편집] 버튼을 누릅니다.</p>
<p><strong>[규칙 추가]</strong>를 누르고 다음과 같이 설정합니다.</p>
<p>유형: PostgreSQL (자동으로 포트 5432가 입력됩니다.)</p>
<p>소스: <strong>&#39;내 IP&#39;</strong>를 선택합니다. (현재 사용자님의 인터넷 IP 주소가 자동으로 들어갑니다.)</p>
<p>설명(선택 사항): My PC Access라고 적어두면 나중에 알아보기 편합니다.</p>
<p><strong>[규칙 저장]</strong>을 누릅니다</p>
<hr>
<h1 id="3-접속-주소엔드포인트-복사하기">3. 접속 주소(엔드포인트) 복사하기</h1>
<p>실제 DB 주소를 알아야 도구에서 접속할 수 있습니다.</p>
<p>다시 RDS 상세 페이지의 [연결 및 보안] 탭으로 가서 엔드포인트(Endpoint) 주소를 복사해 두세요.</p>
<p>예: database-1.xxxx.ap-northeast-2.rds.amazonaws.com</p>
<p>3단계: DB 관리 도구(DBeaver)로 접속 테스트
이제 사용자님의 컴퓨터에 설치된 DB 클라이언트 도구(DBeaver 등)를 켭니다.</p>
<p><strong>새 연결(New Connection)</strong>을 만들고 PostgreSQL을 선택합니다.</p>
<p>Host: 복사한 &#39;엔드포인트&#39; 주소를 붙여넣습니다.</p>
<p>Port: 5432를 확인합니다.</p>
<p>Database: 기본값인 postgres를 입력합니다. (별도로 생성한 DB가 없다면 기본 DB명입니다.)</p>
<p>Username: 설정하셨던 마스터 이름(예: postgres)을 입력합니다.</p>
<p>Password: 설정하신 마스터 암호를 입력합니다.</p>
<p>[Test Connection] 버튼을 눌러 연결되는지 확인합니다.</p>
<hr>
<h1 id="4-cli">4. CLI</h1>
<pre><code>psql -h &lt;엔드포인트&gt; -p 5432 -U &lt;마스터사용자이름&gt; -d postgres</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[websocket data 테스팅]]></title>
            <link>https://velog.io/@hunter_joe99/websocket-data-%ED%85%8C%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@hunter_joe99/websocket-data-%ED%85%8C%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Mon, 12 Jan 2026 18:35:20 GMT</pubDate>
            <description><![CDATA[<h3 id="og">OG</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/41965663-5896-4809-92f4-85a507107321/image.png" alt="">
<code>ls -lh docs/sample/original-comparison.json</code> -&gt; 81KB</p>
<hr>
<h3 id="필드명-축약">필드명 축약</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/724a997c-4f53-40e9-9df0-abecc11300eb/image.png" alt="">
<code>ls -lh docs/sample/compressed-comparison.json</code> -&gt; 58KB</p>
<hr>
<h3 id="zlib-압축--필드명-축약">zlib 압축 + 필드명 축약</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/b9a4a250-d38a-473d-b44d-2f8187f44895/image.png" alt=""></p>
<hr>
<h3 id="zlib-압축--og">zlib 압축 + OG</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/d6a69b11-398e-4d24-a452-aa546cda7992/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] - Basic (Docker & Docker Compose)]]></title>
            <link>https://velog.io/@hunter_joe99/Docker-Basic</link>
            <guid>https://velog.io/@hunter_joe99/Docker-Basic</guid>
            <pubDate>Sun, 11 Jan 2026 05:47:07 GMT</pubDate>
            <description><![CDATA[<h1 id="1-docker">1. Docker</h1>
<p>도커는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼입니다. 소프트웨어를 <strong>&#39;컨테이너&#39;</strong>라는 표준화된 유닛으로 패키징하여, 개발 환경과 운영 환경의 차이 없이 어디서든 동일하게 실행되도록 돕습니다. 가상 머신보다 가볍고 빠르며 자원을 효율적으로 사용하는 것이 특징입니다.</p>
<h1 id="2-docker-image">2. Docker Image</h1>
<p>도커 이미지는 컨테이너를 생성하기 위한 <strong>읽기 전용 설계도(템플릿)</strong>입니다. 코드, 라이브러리, 설정 파일 등 애플리케이션 실행에 필요한 모든 것을 담고 있는 스냅샷과 같습니다. 한 번 만들어진 이미지는 변하지 않으며, 이 이미지를 공유하면 누구나 똑같은 환경을 복제할 수 있습니다.</p>
<h1 id="3-docker-container">3. Docker Container</h1>
<p>컨테이너는 도커 이미지를 실행한 상태로, 애플리케이션이 실제 구동되는 격리된 공간입니다. 이미지라는 설계도로 만든 &#39;실제 제품&#39;에 비유할 수 있으며, 독립된 자원을 할당받아 프로세스가 실행됩니다. 하나의 이미지로 여러 개의 컨테이너를 동시에 띄울 수 있고, 삭제와 생성이 매우 자유롭습니다.</p>
<h1 id="4-dockerfile">4. Dockerfile</h1>
<p>도커파일은 <strong>이미지를 만들기 위한 명령어들을 모아놓은 텍스트 파일</strong>입니다. 어떤 베이스 이미지를 쓸지, 어떤 파일을 복사할지, 어떤 명령어를 실행할지 적어둔 &#39;레시피&#39;와 같습니다. 이 파일을 실행(build)하면 자동으로 도커 이미지가 생성되어 환경 구축을 자동화할 수 있습니다.</p>
<h1 id="차이점-요약">차이점 요약</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f7654819-a8b0-4ca8-af27-7e468538182f/image.png" alt=""></p>
<h1 id="1-docker-compose">1. Docker Compose</h1>
<p>Docker Compose는 여러 개의 컨테이너를 하나의 서비스로 정의하고 묶어서 관리할 수 있게 도와주는 도구입니다.</p>
<p>별도의 <code>run.sh</code> 스크립트 파일을 작성하지 않아도, 하나의 <code>docker-compose.yml</code> 파일만으로 여러 컨테이너의 실행 환경과 설정을 선언적으로 정의할 수 있습니다.</p>
<pre><code class="language-bash">####### run.sh #######
# 1. 네트워크 생성
docker network create my-project-network

# 2. 볼륨 생성
docker volume create postgres_data

# 3. Redis 실행
docker run -d --name my-project-redis \
  --network my-project-network \
  redis:7-alpine

# 4. Postgres 실행
docker run -d --name my-project-postgres \
  --network my-project-network \
  -e POSTGRES_USER=myuser -e POSTGRES_PASSWORD=mypass \
  -v postgres_data:/var/lib/postgresql/data \
  postgres:15-alpine

# 5. API 서버 빌드 및 실행 (매우 복잡해짐)
docker build -t myproject-api -f Dockerfile.dev .
docker run -d --name my-project-server \
  --network crypto-express-network \
  -p 3000:3000 \
  -v $(pwd):/app -v /app/node_modules -v /app/.pnpm-store \
  -e DATABASE_URL=... (기타 환경변수 생략) \
  crypto-api sh -c &quot;pnpm install &amp;&amp; pnpm run start:dev&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NestJS] - Drizzle ORM을 사용한 DB 설정]]></title>
            <link>https://velog.io/@hunter_joe99/NestJS-Drizzle-ORM%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-DB-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hunter_joe99/NestJS-Drizzle-ORM%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-DB-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Fri, 09 Jan 2026 15:28:39 GMT</pubDate>
            <description><![CDATA[<p> NestJS에서 Drizzle ORM으로 PostgreSQL DB 설정하는 방법 
 <img src="https://velog.velcdn.com/images/hunter_joe99/post/f4ceb96e-a68d-4118-aca8-a4e423929378/image.png" alt=""></p>
<ul>
<li><a href="https://tsx.is/">https://tsx.is/</a></li>
<li><a href="https://www.npmjs.com/package/dotenv">https://www.npmjs.com/package/dotenv</a></li>
<li><a href="https://node-postgres.com/">https://node-postgres.com/</a></li>
</ul>
<h1 id="db-만들기---docker">DB 만들기 - Docker</h1>
<p><a href="https://orm.drizzle.team/docs/guides/postgresql-local-setup">https://orm.drizzle.team/docs/guides/postgresql-local-setup</a>
해당 문서 참고 </p>
<h1 id="drizzleconfigts">drizzle.config.ts</h1>
<pre><code class="language-ts">import { defineConfig } from &#39;drizzle-kit&#39;;

export default defineConfig({
  schema: &#39;./src/db/schema.ts&#39;,
  out: &#39;./drizzle&#39;,
  dialect: &#39;postgresql&#39;,
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});</code></pre>
<h1 id="database-모듈-설정">Database 모듈 설정</h1>
<pre><code class="language-ts">// db.module.ts
import &#39;dotenv/config&#39;;
import { drizzle } from &#39;drizzle-orm/node-postgres&#39;;
import { Module, Global } from &#39;@nestjs/common&#39;;
import { Pool } from &#39;pg&#39;;

import { NodePgDatabase } from &#39;drizzle-orm/node-postgres&#39;;
import * as schema from &#39;./schema&#39;; // Your Drizzle schema files

// The type for your fully configured Drizzle DB instance
export type DB = NodePgDatabase&lt;typeof schema&gt;;

const dbProvider = {
  provide: &#39;DATABASE&#39;,
  useFactory: () =&gt;
    drizzle({
      connection: {
        connectionString: process.env.DATABASE_URL,
        ssl: false, // 로컬 Docker에서는 SSL 지원 X 
      },
    }),
};

@Global()
@Module({
  providers: [dbProvider],
  exports: [&#39;DATABASE&#39;],
})
export class DbModule {}</code></pre>
<h1 id="schema---user">schema - user</h1>
<pre><code class="language-ts">import { integer, pgTable, varchar } from &#39;drizzle-orm/pg-core&#39;;

export const usersTable = pgTable(&#39;users&#39;, {
  id: integer().primaryKey().generatedAlwaysAsIdentity(),
  name: varchar({ length: 255 }).notNull(),
  age: integer().notNull(),
  email: varchar({ length: 255 }).notNull().unique(),
});</code></pre>
<h1 id="뽀나스">뽀나스</h1>
<pre><code class="language-ts">import { drizzle } from &quot;drizzle-orm/node-postgres&quot;
const db = drizzle(process.env.DATABASE_URL);
const pool = db.$client;

// above is equivalent to
import { drizzle } from &quot;drizzle-orm/node-postgres&quot;;
import { Pool } from &quot;pg&quot;;
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});
const db = drizzle({ client: pool });</code></pre>
<p>첫번째 방식은 기본설정을 그대로 쓰는 것 튜닝 X </p>
<p>두번째는 Pool을 만들어서 세부 설정(튜닝)함 
해당 튜닝 config 정보들은 <a href="https://node-postgres.com/apis/client">링크</a> 참고</p>
<ul>
<li><a href="https://node-postgres.com/apis/client">https://node-postgres.com/apis/client</a></li>
</ul>
<pre><code class="language-ts">type Config = {
  user?: string, // default process.env.PGUSER || process.env.USER
  password?: string or function, //default process.env.PGPASSWORD
  host?: string, // default process.env.PGHOST
  port?: number, // default process.env.PGPORT
  database?: string, // default process.env.PGDATABASE || user
  connectionString?: string, // e.g. postgres://user:password@host:5432/database
  ssl?: any, // passed directly to node.TLSSocket, supports all tls.connect options
  types?: any, // custom type parsers
  statement_timeout?: number, // number of milliseconds before a statement in query will time out, default is no timeout
  query_timeout?: number, // number of milliseconds before a query call will timeout, default is no timeout
  lock_timeout?: number, // number of milliseconds a query is allowed to be en lock state before it&#39;s cancelled due to lock timeout
  application_name?: string, // The name of the application that created this Client instance
  connectionTimeoutMillis?: number, // number of milliseconds to wait for connection, default is no timeout
  keepAliveInitialDelayMillis?: number, // set the initial delay before the first keepalive probe is sent on an idle socket
  idle_in_transaction_session_timeout?: number, // number of milliseconds before terminating any session with an open idle transaction, default is no timeout
  client_encoding?: string, // specifies the character set encoding that the database uses for sending data to the client
  fallback_application_name?: string, // provide an application name to use if application_name is not set
  options?: string // command-line options to be sent to the server
}</code></pre>
<h1 id="참고자료">참고자료</h1>
<ul>
<li><p><a href="https://docs.nestjs.com/fundamentals/custom-providers">https://docs.nestjs.com/fundamentals/custom-providers</a></p>
</li>
<li><p><a href="https://orm.drizzle.team/docs/get-started/postgresql-new">https://orm.drizzle.team/docs/get-started/postgresql-new</a></p>
</li>
<li><p><a href="https://orm.drizzle.team/docs/get-started-postgresql#node-postgres">https://orm.drizzle.team/docs/get-started-postgresql#node-postgres</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] - 컴파일 에러를 피하고 싶어 with archive]]></title>
            <link>https://velog.io/@hunter_joe99/TypeScript-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%97%90%EB%9F%AC%EB%A5%BC-%ED%94%BC%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4-with-archive</link>
            <guid>https://velog.io/@hunter_joe99/TypeScript-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%97%90%EB%9F%AC%EB%A5%BC-%ED%94%BC%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4-with-archive</guid>
            <pubDate>Fri, 26 Dec 2025 21:57:36 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하다보면 구조를 완전히 바꿔야 할때가 생긴다. 이때 개발 방향성을 잃지 않기 위해서 <code>archive</code> 폴더를 주로 사용한다. </p>
<p>하지만 이때 발생되는 에러는 TS가 모든 폴더 및 파일을 컴파일 하다보니 dev 환경에서 서버를 시작하면 컴파일 에러가 나와 바로 테스트하기 어려운 경우도 생긴다. </p>
<p>이때 <code>tsconfig</code>를 다음과 같이 수정해서 컴파일 문제를 해결한다. </p>
<pre><code class="language-ts">// tsconfig
{
  &quot;compilerOptions&quot;: {
    // ... 기존 설정
  },
  &quot;exclude&quot;: [
    &quot;node_modules&quot;,
    &quot;src/archive&quot; // archive 폴더를 컴파일 대상에서 제외
  ]
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] - keyof, typeof 그리고 union]]></title>
            <link>https://velog.io/@hunter_joe99/TypeScript-keyof-typeof-%EA%B7%B8%EB%A6%AC%EA%B3%A0-union</link>
            <guid>https://velog.io/@hunter_joe99/TypeScript-keyof-typeof-%EA%B7%B8%EB%A6%AC%EA%B3%A0-union</guid>
            <pubDate>Sun, 30 Nov 2025 22:43:04 GMT</pubDate>
            <description><![CDATA[<p>미리 Union타입을 만들어서 사용하는 방법과 자료구조에서 Union 타입을 추출해는 방법이 있다. </p>
<h1 id="기본적인-union-타입-선언">기본적인 Union 타입 선언</h1>
<pre><code class="language-ts">type Fruit = &quot;apple&quot; | &quot;orange&quot; | &quot;lemon&quot;| &quot;melon&quot;

const myFruit: Fruit  = &quot;kiwi&quot; // Type &#39;&quot;kiwi&quot;&#39; is not assignable to type &#39;Fruit&#39;

const yourFruit: Fruit = &quot;apple&quot;</code></pre>
<h1 id="배열에서-union-타입-추출하기">배열에서 Union 타입 추출하기</h1>
<pre><code class="language-ts">const fruits = [&quot;apple&quot;, &quot;orange&quot;, &quot;lemon&quot;] as const

type Fruits = typeof fruits[number]</code></pre>
<h1 id="객체에서-union-타입-추출하기-key-value">객체에서 Union 타입 추출하기 (key, value)</h1>
<pre><code class="language-ts">const countryCurrency = {
    KOREA : &quot;KRW&quot;,
      USA : &quot;USD&quot;,
      CANADA : &quot;CAD&quot;,
} as const;

type Country = keyof typeof countryCurrency
type Currency = (typeof countryCurrency)[keyof typeof countryCurrency]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Trouble Shooting] - 메모리 누수해결하기 -작성중]]></title>
            <link>https://velog.io/@hunter_joe99/Trouble-Shooting-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-%EC%9E%91%EC%84%B1%EC%A4%91</link>
            <guid>https://velog.io/@hunter_joe99/Trouble-Shooting-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-%EC%9E%91%EC%84%B1%EC%A4%91</guid>
            <pubDate>Sun, 23 Nov 2025 14:17:36 GMT</pubDate>
            <description><![CDATA[<p>→←↑↓
<span style="color:skyblue"></span>
<span style="color:indianred"></span>
채팅창<code>input</code> 태그에 텍스트를 작성하고 있던와중에 텍스트가 끊기듯이 작성하길래 
메모리 사용량을 확인해봤더니 900MB으로 많은 사용량을 차지하고 있음 </p>
<p>이렇게 개발중에 체감될 정도의 메모리 누수는 처음 겪어봐서 자세하게 어떻게 해결했는지를 담아볼 예정이다.</p>
<h2 id="누수-발견">누수 발견</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/93931e8f-31db-4750-8435-14fb32b422ef/image.png" alt=""></p>
<h2 id="메모리-누수-유형">메모리 누수 유형</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/c456b4bd-50df-4eb0-8727-5edf9e7911de/image.png" alt=""></p>
<blockquote>
<p><strong>NOTE</strong>
참고 : <a href="https://developer.chrome.com/docs/devtools/memory-problems">Google Developer</a>
Memory Bloat → 사이트가 점점 더 많은 메모리를 사용한다면 메모리 누수가 발생한 것임 하지만 Memory bloat는 정확한 수치가 없고 <a href="https://web.dev/articles/rail?hl=ko">RAIL</a>모델을 활용해서 사용자에게 집중해야함</p>
</blockquote>
<p>내 프로젝트의 경우 직관적으로 1번, 3번이 가장 유력하다.<br><span style="color:indianred"><strong>→ 직감을 도구를 활용해 구체화 해보자</strong></span></p>
<ul>
<li>Chorme Task Manager</li>
<li>Chrome devtools의 Memory, performance</li>
</ul>
<h2 id="크롬-task-manager">크롬 Task Manager</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/66d73d65-b180-42b7-9941-9207efc2ff5f/image.png" alt=""></p>
<h2 id="크롬-devtool로-확인하기--record">크롬 devtool로 확인하기 / Record</h2>
<blockquote>
<p><strong>TIP</strong></p>
</blockquote>
<ul>
<li>성능 기록을 <span style="color:indianred"><strong>시작하기 전과 종료할 때,</strong></span> 수동으로 가비지 컬렉션을 한 번씩 실행해주는 게 좋아요. 녹화 중에 빗자루 아이콘(Collect garbage)을 누르면 가비지 컬렉션을 강제로 수행할 수 있어요.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/b9f0b451-a4cf-41f2-afec-ded25db42eff/image.png" alt=""></p>
<p>(위 사진은 Performance 탭에서 Memory 부분을 녹화한 예시)</p>
<p>일단 분석을하고 문제가 뭔지를 알려면 그림에 보이는 <strong>JS heap, Document, Nodes, Listener</strong>가 뭔지부터 알아야겠지? 
↓↓↓↓↓ 알아보자 </p>
<h4 id="🔵-js-heap">🔵 JS heap</h4>
<ul>
<li>JavaScript 엔진(V8)이 실행될 때 사용되는 메모리 영역 중 하나로 주로 객체, 배열, 함수 등 동적으로 생성되는 데이터를 저장하는 공간 </li>
<li>JavaScript에서 원시 값은 보통 스택이라는 다른 메모리 영역에 저장되는 반면 크기가 가변적이고 참조를 통해 접근하는 참조 타입은 JS힙에 저장됨</li>
<li>파랑선이 하락하는 부분이 GC가 작용하는 부분임</li>
</ul>
<h4 id="🟢-nodes">🟢 Nodes</h4>
<ul>
<li>노드란 웹페이지를 구성하는 HTML element 하나하나를 의미</li>
<li>페이지에서 element를 제거했음에도 불구하고 해당 노드를 참조하는 JavaScript 변수나 클로저가 남아 있으면 메모리에 누적될 수 있다.<span style="color:indianred">← <strong>메모리 누수의 원인</strong></span></li>
</ul>
<h4 id="🟡-listeners">🟡 Listeners</h4>
<ul>
<li><p>리스너는 웹 페이지에서 특정 이벤트가 발생하기를 기다리고 있다가 발생하면 미리 정의된 함수(주로 콜백함수)를 실행하도록 등록된 요소</p>
</li>
<li><p>어떤 <code>&lt;div&gt;</code>에 <code>click</code>과 같은 이벤트 리스너를 등록했고 후에 해당 <code>&lt;div&gt;</code>를 제거했음에도 JavaScript 코드 어딘가에 해당 리스너에 대한 참조가 남아 있다면 리스너와 연결된 <code>&lt;div&gt;</code> 노드는 GC에 의해 제거되지 않음. <span style="color:indianred">← <strong>메모리 누수의 원인</strong> </span></p>
</li>
</ul>
<h4 id="🔴-documents">🔴 Documents</h4>
<ul>
<li>웹 페이지의 개수</li>
</ul>
<h2 id="performance-녹화--heap-snapshot">Performance 녹화 + heap snapshot</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/b2d01de2-cb2b-46ba-8eca-94062bcea953/image.png" alt="">
프로젝트의 총 5번의 측정기록</p>
<p>1-2번 그래프: JS heap, Nodes, Listeners가 증가. ← 메모리 누수 
3번 그래프: JS heap, Listeners 증가.
4-5번 그래프 : 애매하다고 판단 </p>
<p>이렇게 Performance 탭에서 메모리를 어떻게 사용중인지 녹화하는것은 애매한 부분이 있어서 좀 더 확실하게 하기 위해 heap snapshot을 찍어보기로 하였다. </p>
<h4 id="heap-snapshot">heap snapshot</h4>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f287fed1-5515-4826-bc0d-782c015f09d0/image.png" alt="">
스냅샷 또한 73.7에서 시작해 94.1까지 찍을 때마다 증가하고 있다. <span style="color:indianred"><strong>더 볼것도 없다 걍 메모리 누수다</strong></span></p>
<h2 id="어디서-메모리-누수가-날까">어디서 메모리 누수가 날까?</h2>
<p>녹화 + heap snapshot으로 내 프로젝트에서 메모리 누수가 난다는 것은 확실해졌다. 
그럼 이제 어디부분에서 메모리 누수가 난다는 걸까? 그리고 어떻게 찾아야 할까?</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/3fcd9b51-fe1b-4a27-b6c5-4fd2c2c3fced/image.png" alt="">
(Snapshot1) </p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/32c277c0-d5a6-4df8-a3ab-588aed5dc305/image.png" alt=""></p>
<p>(Snapshot Comparison: <code>snapshot5(base)</code> vs <code>snapshot1(compare)</code> )</p>
<p><strong>메모리를 가장 많이 점유하고 있는 누수 원인은 (string)과 Error 객체이다.</strong>
snapshot1 ~ snapshot5까지 진행하는 동안 새로운 문자열이 4556개나 생성되었으며 GC에 의해 해제되지 않고 메모리에 남아 있다. 
또한 Error 객체 또한 9547개나 증가했으며 이는 내가 인지하지 못하는 사이 어디선가 예외나 경고가 발생하고 있고 해당 Error 객체들이 어딘가에 참조되어 메모리에 쌓이고 있다는 뜻이다. </p>
<p><strong>문제는..</strong>
모르겠다. 정확하게 메모리 누수가 나는 곳이 어디인지 모르겠다. 그래서 다음과 같은 방법을 써서 진단해보기로 했다.
<img src="https://velog.velcdn.com/images/hunter_joe99/post/cf4717c4-5893-499a-8355-71646c816d2e/image.png" alt=""></p>
<p>많은 작업량을 처리하고 있는 Task를 보면 <code>useSocketManager</code>와 <code>setTickerStream</code>에서 대용량 트래픽이 발생하고 있다는 것을 확인 할 수 있다. </p>
<p>해당 코드에서 메모리 누수를 막을 수 있지 않을까해서 일단 수정해보겠다.</p>
<p>현재까지 <code>useSocketManager</code>와 <code>setTickerStream</code>코드는 수정을 마쳤다. </p>
<ul>
<li><code>setTickerStream</code>의 devtools 코드제거 </li>
<li><code>useSocketManager</code>모든 소켓 리스너에 cleanup 함수 </li>
</ul>
<h2 id="문제-해결-2612---추가">문제 해결 26/1/2 - 추가</h2>
<p>websocket을 활용해서 데이터를 지속적으로 공급받고 있는 과정에서 대량의 데이터를 UI에 그려야했고 그 과정에서 지속적으로 reflow, repaint 작업이 일어나고 있었다. 
그것이 문제</p>
<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://leetrue.hashnode.dev/memory-leak-chrome">https://leetrue.hashnode.dev/memory-leak-chrome</a>
<a href="https://blog.eunsukim.me/posts/debugging-javascript-memory-leak-with-chrome-devtools">https://blog.eunsukim.me/posts/debugging-javascript-memory-leak-with-chrome-devtools</a></p>
]]></description>
        </item>
    </channel>
</rss>