<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>.</title>
        <link>https://velog.io/</link>
        <description>과학과 컴퓨터를 좋아하는</description>
        <lastBuildDate>Tue, 09 Jun 2026 08:10:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>.</title>
            <url>https://velog.velcdn.com/images/halo_3735/profile/12633953-9ca6-4107-a868-48362a545f9f/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. .. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/halo_3735" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[취뿌 후 하고싶은 것]]></title>
            <link>https://velog.io/@halo_3735/%EC%B7%A8%EB%BF%8C-%ED%9B%84-%ED%95%98%EA%B3%A0%EC%8B%B6%EC%9D%80-%EA%B2%83</link>
            <guid>https://velog.io/@halo_3735/%EC%B7%A8%EB%BF%8C-%ED%9B%84-%ED%95%98%EA%B3%A0%EC%8B%B6%EC%9D%80-%EA%B2%83</guid>
            <pubDate>Tue, 09 Jun 2026 08:10:36 GMT</pubDate>
            <description><![CDATA[<h1 id="1-고양이-키우기">1. 고양이 키우기</h1>
<p>정확히는 아기 고양이 </p>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/ddbb79e1-c624-4912-81e8-c95f30dbafc2/image.png" alt=""></p>
<p>고양이도 이력서 쓰는데..<img src="https://velog.velcdn.com/images/halo_3735/post/653005f6-b2a3-45d6-8593-061b8ce81b7b/image.png" alt=""></p>
<h1 id="2-밴드-드럼으로-참가하기">2. 밴드 드럼으로 참가하기</h1>
<p>아 이건 피아노
<img src="https://velog.velcdn.com/images/halo_3735/post/75b0959a-c49e-454b-a391-436d2a13a9c5/image.png" alt=""></p>
<p>이게 드럼
<a href="https://www.youtube.com/shorts/peX3kbGJkmw">드럼 하이햇 그렇게 치는 거 아닙니다 - mastermusic</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멀티 인스턴스 환경의 데드락 상황에서 분산락 적용기]]></title>
            <link>https://velog.io/@halo_3735/%EB%8D%B0%EB%93%9C%EB%9D%BD-%EC%83%81%ED%99%A9%EC%97%90%EC%84%9C-%EB%B6%84%EC%82%B0%EB%9D%BD-%EA%B5%AC%ED%98%84%EA%B8%B0</link>
            <guid>https://velog.io/@halo_3735/%EB%8D%B0%EB%93%9C%EB%9D%BD-%EC%83%81%ED%99%A9%EC%97%90%EC%84%9C-%EB%B6%84%EC%82%B0%EB%9D%BD-%EA%B5%AC%ED%98%84%EA%B8%B0</guid>
            <pubDate>Tue, 02 Jun 2026 16:56:11 GMT</pubDate>
            <description><![CDATA[<h1 id="레디스-스트림을-도입했음에도-왜-p95가-1초이하로-내려가지-않을까">레디스 스트림을 도입했음에도 왜 p95가 1초이하로 내려가지 않을까?</h1>
<ul>
<li>원인<ul>
<li>레디스 스트림 데드락 발생
<img src="https://velog.velcdn.com/images/halo_3735/post/bc6d6e68-a8ce-4e7f-ae51-37c6f681ed97/image.png" alt=""></li>
</ul>
</li>
</ul>
<pre><code>    - PostgreSQL의 데드락 대응  : 데드락 감지한 트랜잭션 하나를 강제로 실패 (관련글 : https://velog.io/@halo_3735/데드락-victim희생-트랜잭션-선택-알고리즘</code></pre><ul>
<li><p>원인</p>
<p>  !image.png</p>
<p>  !image.png</p>
<ol>
<li><p>멀티인스턴스구조</p>
<ol>
<li>컨슈머 그룹에 의해서 각 인스턴스의 백엔드 서버는 별도의 이벤트를 가져감<ol>
<li>하지만 동일한 내용의 이벤트를 가져가는 것은 컨슈머 그룹이 막을 수 없음<ol>
<li>before_update에 들어있는 유저들만 업데이트를 하는 쿼리가 가지고 있는 유저 아이디가 1,2이고<ol>
<li>다른 쿼리가 2,1일때<ul>
<li>서로 1과 2를 가지고있는 상태에서</li>
<li>2와 1을 락 걸려고 시도하기 때문에<ul>
<li>deadlock timeout이 난 것이고<ul>
<li>이것때문에 지연 발생</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<h1 id="데드락-해결방법">데드락 해결방법</h1>
<h2 id="1-row-lock-순서-보장">1. Row Lock 순서 보장</h2>
</li>
</ol>
<ul>
<li><p>근데 트랜잭션 1이 userId 1인 행 락을 계속 잡고 있으면, 다른 트랜잭션들을 기아상태가됨</p>
<p>  가. 최대한 빨리 행락을 잡고있는 요소를 없애기</p>
<ol>
<li><p><code>카드 수 증가랑 묶여있는 알림 생성을 별도의 트랜잭션으로 분리</code></p>
<ol>
<li>하나의 트랜잭션에서 행락을 계속 잡고있기 때문에<ol>
<li><code>단점</code><ol>
<li>lock wait가 발생 <ol>
<li>동일한 업데이트 형태의 배치 3대가 동시에 돌면 2대는 항상 기다리니까<ol>
<li>지연</li>
<li>다른세션이 lock wait 때문에 db세션을 잡고있다<ol>
<li>세션 낭비<ol>
<li>cpu 증가</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>나. 알림 비동기 처리</p>
</li>
<li><p>트랜잭션 단위로 락을 잡고있기 때문에 같은 트랜잭션에있는 알림처리를 별도의 트랜잭션으로 분리하여 의존성 낮추기</p>
<p> !image.png</p>
<ul>
<li>우선순위 낮다 0.03세션 밖이 안잡아먹어서</li>
</ul>
</li>
</ol>
<h2 id="2-shedlock-분산락">2. ShedLock (분산락)</h2>
</li>
</ul>
<ol>
<li><p>하나의 인스턴스만 </p>
</li>
<li><p><code>단점</code></p>
<ol>
<li>ShedLock 잡은 서버가 죽으면 락 만료 전까지 다른 서버도 실행 못 함 <ol>
<li>락 TTL 설정</li>
<li>쿠버네티스 환경에서는 pod로 서버 재시작</li>
</ol>
</li>
</ol>
<h2 id="3-단일-컨슈머">3. 단일 컨슈머</h2>
</li>
<li><p>해당 인스턴스가 죽으면 장애생김</p>
</li>
</ol>
</li>
</ul>
<h1 id="shedlock-분산락-어떻게-구현할건데">ShedLock (분산락) 어떻게 구현할건데?</h1>
<blockquote>
<p>계봉 예정작</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[데드락 전에 행락]]></title>
            <link>https://velog.io/@halo_3735/%EB%8D%B0%EB%93%9C%EB%9D%BD-%EC%A0%84%EC%97%90-%ED%96%89%EB%9D%BD</link>
            <guid>https://velog.io/@halo_3735/%EB%8D%B0%EB%93%9C%EB%9D%BD-%EC%A0%84%EC%97%90-%ED%96%89%EB%9D%BD</guid>
            <pubDate>Tue, 02 Jun 2026 14:35:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/halo_3735/post/7a5f00f1-76c5-43ff-b5e6-f484a81cf36d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Batch 공식문서 기여]]></title>
            <link>https://velog.io/@halo_3735/Spring-Batch-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EA%B8%B0%EC%97%AC</link>
            <guid>https://velog.io/@halo_3735/Spring-Batch-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EA%B8%B0%EC%97%AC</guid>
            <pubDate>Tue, 02 Jun 2026 12:21:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/halo_3735/post/7fd65a40-f7de-40f9-853f-10e80d10a651/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Postsql 데드락 victim(희생 트랜잭션) 선택 알고리즘]]></title>
            <link>https://velog.io/@halo_3735/%EB%8D%B0%EB%93%9C%EB%9D%BD-victim%ED%9D%AC%EC%83%9D-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%84%A0%ED%83%9D-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@halo_3735/%EB%8D%B0%EB%93%9C%EB%9D%BD-victim%ED%9D%AC%EC%83%9D-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%84%A0%ED%83%9D-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Mon, 01 Jun 2026 06:40:17 GMT</pubDate>
            <description><![CDATA[<h1 id="victim이란">victim이란?</h1>
<h1 id="어떤-것을-희생양으로-삼을-것인가">어떤 것을 희생양으로 삼을 것인가?</h1>
<ul>
<li><p>vitim 희생양</p>
</li>
<li><p>데드락 victim(희생 트랜잭션) 선택 알고리즘</p>
<ul>
<li><p>죽였을 때 손해가 작은 트랜잭션</p>
<ul>
<li><p>데드락 감지한 자신이 죽음</p>
<ol>
<li><p>데드락 타임아웃 난 후 락 대기 사이클 확인</p>
<pre><code class="language-c"> if (FindLockCycle(startProc, softEdges, &amp;nSoftEdges))
 {
     if (nSoftEdges == 0)
         return -1;      /* hard deadlock detected */
     softFound = nSoftEdges;
 }</code></pre>
<ul>
<li>사이클이 존재하고 풀 수 없으면 -1 : hard deadlock 판단</li>
</ul>
</li>
<li><p>감지한 트랜잭션 에러 받기</p>
<pre><code class="language-c">state = DeadLockCheck(MyProc);

switch (state)

...

case DS_HARD_DEADLOCK:
 Assert(MyProc-&gt;waitLock != NULL);
 RemoveFromWaitQueue(MyProc, LockTagHashCode(&amp;(MyProc-&gt;waitLock-&gt;tag)));
 break;</code></pre>
</li>
</ol>
</li>
</ul>
</li>
<li><p>희생양 고르면 abort</p>
<ul>
<li>abort : 트랜잭션 중단하고 되감기</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>개봉예정작</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성과 특수 상대성 이론]]></title>
            <link>https://velog.io/@halo_3735/%EB%8F%99%EC%8B%9C%EC%84%B1%EA%B3%BC-%ED%8A%B9%EC%88%98-%EC%83%81%EB%8C%80%EC%84%B1-%EC%9D%B4%EB%A1%A0</link>
            <guid>https://velog.io/@halo_3735/%EB%8F%99%EC%8B%9C%EC%84%B1%EA%B3%BC-%ED%8A%B9%EC%88%98-%EC%83%81%EB%8C%80%EC%84%B1-%EC%9D%B4%EB%A1%A0</guid>
            <pubDate>Tue, 26 May 2026 03:41:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>참고 : <a href="https://www.microsoft.com/en-us/research/publication/time-clocks-ordering-events-distributed-system/?utm_source=chatgpt.com">https://www.microsoft.com/en-us/research/publication/time-clocks-ordering-events-distributed-system/?utm_source=chatgpt.com</a></p>
</blockquote>
<h1 id="시간은-정말-절대적일까">“시간”은 정말 절대적일까?</h1>
<p>우리는 보통 시간을 절대적인 기준이라고 생각한다.</p>
<p>오전 9시는 모두에게 같은 오전 9시이고,
먼저 일어난 일과 나중에 일어난 일은 명확하다고 느낀다.</p>
<p>그런데 아인슈타인은 말했다.</p>
<p>“그건 관찰자에 따라 달라질 수 있다.”</p>
<h1 id="동시에-일어난-사건도-사람마다-다르게-볼-수-있다고">동시에 일어난 사건도 사람마다 다르게 볼 수 있다고?</h1>
<p>특수 상대성 이론에서는
두 사건의 순서가 관찰자마다 달라질 수 있다.</p>
<p>어떤 관찰자는:</p>
<ul>
<li>A 사건이 먼저 발생했다고 보고</li>
</ul>
<p>다른 관찰자는:</p>
<ul>
<li>B 사건이 먼저 발생했다고 볼 수 있다.</li>
</ul>
<p>절대적인 “동시”라는 개념이 깨진 것이다.</p>
<p>남는 건 단 하나다.</p>
<p>“영향을 줄 수 있었는가?”</p>
<p>즉, 인과관계(causality)만이 진짜 순서를 결정한다.</p>
<h1 id="그런데-이게-왜-컴퓨터공학이랑-연결될까">그런데 이게 왜 컴퓨터공학이랑 연결될까?</h1>
<p>분산 시스템도 똑같은 문제를 겪는다.</p>
<p>서울 서버와 뉴욕 서버는
절대적으로 동일한 시간을 공유하지 못한다.</p>
<p>네트워크 지연은 항상 존재하고,
서버 시계도 미세하게 어긋난다.</p>
<p>그래서 어떤 요청이 “진짜 먼저” 도착했는지를
완벽하게 알 수 없다.</p>
<h1 id="그렇다면-컴퓨터는-순서를-어떻게-판단할까">그렇다면 컴퓨터는 순서를 어떻게 판단할까?</h1>
<p>여기서 Leslie Lamport가 등장한다.</p>
<p>:contentReference[oaicite:0]{index=0} 는
1978년 논문에서 놀라운 관점을 제시한다.</p>
<p>“실제 시간을 믿지 말자.”</p>
<p>대신:</p>
<ul>
<li>누가 누구에게 영향을 주었는가</li>
<li>어떤 이벤트가 어떤 이벤트를 발생시켰는가</li>
</ul>
<p>즉 “인과관계”만 추적하자는 것이다.</p>
<h1 id="lamport는-왜-이런-생각을-했을까">Lamport는 왜 이런 생각을 했을까?</h1>
<p>흥미롭게도 Lamport는 직접 말한다.</p>
<p>자신은 특수 상대성 이론을 직관적으로 이해하고 있었고,
그 덕분에 분산 시스템 문제의 본질을 바로 이해할 수 있었다고.</p>
<p>그가 깨달은 핵심은 이것이었다.</p>
<p>“사건에는 절대적인 전체 순서(total ordering)가 존재하지 않는다.”</p>
<p>오직:</p>
<ul>
<li>causality</li>
<li>partial ordering</li>
</ul>
<p>만 존재한다.</p>
<h1 id="그래서-탄생한-것이-무엇일까">그래서 탄생한 것이 무엇일까?</h1>
<p>이 생각은 이후 현대 분산 시스템의 뿌리가 된다.</p>
<ul>
<li>Logical Clock</li>
<li>Vector Clock</li>
<li>Distributed Consensus</li>
<li>Paxos</li>
<li>Raft</li>
</ul>
<p>같은 개념들이 모두 여기서 시작된다.</p>
<p>오늘날:</p>
<ul>
<li>Kafka</li>
<li>Cassandra</li>
<li>Google Spanner</li>
</ul>
<p>같은 거대한 시스템들도
결국은 이 문제와 싸우고 있다.</p>
<p>“시간은 정말 절대적인가?”</p>
<p>그리고 컴퓨터공학은,
생각보다 물리학과 훨씬 가까운 곳에 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5/24 고찰]]></title>
            <link>https://velog.io/@halo_3735/524-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@halo_3735/524-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Sun, 24 May 2026 10:03:55 GMT</pubDate>
            <description><![CDATA[<ul>
<li>직렬화</li>
<li>영속성 컨텍스트 확인하고 update sql 만들고 바인딩</li>
<li>PreparedStatement</li>
<li>스레드에 히카리시피 1개 (커넥션, 디비세션)</li>
<li>트랜잭션의 일관성을 플러시와 커밋 사이에있는 롤백으로 보장</li>
<li>트랜잭션 안의 여러쿼리는 보통 DB 세션 1개가 순서대로 처리</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringBoot 오픈소스 기여]]></title>
            <link>https://velog.io/@halo_3735/SpringBoot-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC</link>
            <guid>https://velog.io/@halo_3735/SpringBoot-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC</guid>
            <pubDate>Sat, 23 May 2026 02:33:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/halo_3735/post/bc631f7b-9cdd-4fd6-a328-5783be40b631/image.png" alt=""></p>
<blockquote>
<p>개봉 예정작</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[톡톡튀는 슬로우 쿼리, 위험하다고 판단할 수 있을까?]]></title>
            <link>https://velog.io/@halo_3735/%ED%86%A1%ED%86%A1%ED%8A%80%EB%8A%94-%EC%8A%AC%EB%A1%9C%EC%9A%B0-%EC%BF%BC%EB%A6%AC-%EC%9C%84%ED%97%98%ED%95%98%EB%8B%A4%EA%B3%A0-%ED%8C%90%EB%8B%A8%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@halo_3735/%ED%86%A1%ED%86%A1%ED%8A%80%EB%8A%94-%EC%8A%AC%EB%A1%9C%EC%9A%B0-%EC%BF%BC%EB%A6%AC-%EC%9C%84%ED%97%98%ED%95%98%EB%8B%A4%EA%B3%A0-%ED%8C%90%EB%8B%A8%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Wed, 20 May 2026 07:23:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/halo_3735/post/45de5ee1-cc6e-4be6-863a-e43327984210/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/3ab4fa4d-f2b2-4084-b13e-7022139991cc/image.png" alt=""></p>
<ol>
<li>해당 쿼리는 한번요청당 평균적으로 0.29ms 지연 시간을 보인다.<ol>
<li>하지만 특정구간 튀는 쿼리들 즉. 100ms를 넘어가는 쿼리가 0.1% 존재한다는거다<ol>
<li>이게 문제가 될지 어떻게 판단하지?</li>
</ol>
</li>
</ol>
</li>
</ol>
<blockquote>
<p>계종 예정작</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[레디스를 도입했더니 쿼리가 더 지연됐다고?]]></title>
            <link>https://velog.io/@halo_3735/%EB%A0%88%EB%94%94%EC%8A%A4%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%96%88%EB%8D%94%EB%8B%88-%EC%BF%BC%EB%A6%AC%EA%B0%80-%EB%8D%94-%EC%A7%80%EC%97%B0%EB%90%90%EB%8B%A4%EA%B3%A0</link>
            <guid>https://velog.io/@halo_3735/%EB%A0%88%EB%94%94%EC%8A%A4%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%96%88%EB%8D%94%EB%8B%88-%EC%BF%BC%EB%A6%AC%EA%B0%80-%EB%8D%94-%EC%A7%80%EC%97%B0%EB%90%90%EB%8B%A4%EA%B3%A0</guid>
            <pubDate>Tue, 19 May 2026 12:47:37 GMT</pubDate>
            <description><![CDATA[<h1 id="outbox-패턴-도입으로-인한-쿼리-지연">Outbox 패턴 도입으로 인한 쿼리 지연</h1>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/51ddf9c2-4d4a-422a-9e6f-224c64412b9a/image.png" alt=""></p>
<blockquote>
<p>개봉 예정작</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM도 워밍업을 한다는 것을 아시나요?]]></title>
            <link>https://velog.io/@halo_3735/JVM%EB%8F%84-%EC%9B%8C%EB%B0%8D%EC%97%85%EC%9D%84-%ED%95%9C%EB%8B%A4%EB%8A%94-%EA%B2%83%EC%9D%84-%EC%95%84%EC%8B%9C%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@halo_3735/JVM%EB%8F%84-%EC%9B%8C%EB%B0%8D%EC%97%85%EC%9D%84-%ED%95%9C%EB%8B%A4%EB%8A%94-%EA%B2%83%EC%9D%84-%EC%95%84%EC%8B%9C%EB%82%98%EC%9A%94</guid>
            <pubDate>Tue, 19 May 2026 12:47:01 GMT</pubDate>
            <description><![CDATA[<h1 id="트래픽-받는게-처음이라-당황스러운-인스턴스-달래기">트래픽 받는게 처음이라 당황스러운 인스턴스 달래기</h1>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/47fac48e-9a2f-4211-b026-64e6fef2b3b3/image.png" alt=""></p>
<blockquote>
<p>개봉예정작</p>
</blockquote>
<blockquote>
<p>비슷한 케이스  : <a href="https://tech.kakaopay.com/post/jvm-warm-up/">https://tech.kakaopay.com/post/jvm-warm-up/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성과 데이터 무결성]]></title>
            <link>https://velog.io/@halo_3735/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AC%B4%EA%B2%B0%EC%84%B1</link>
            <guid>https://velog.io/@halo_3735/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AC%B4%EA%B2%B0%EC%84%B1</guid>
            <pubDate>Thu, 14 May 2026 03:10:00 GMT</pubDate>
            <description><![CDATA[<p>동시성은 데이터 무결성을 해친다.</p>
<p>무결하다는 것은 뭘까</p>
<p>정답이 아닌것?</p>
<p>정답이 어딨나</p>
<p>약속을 지키지 않는 것 = 무결하지 않는 것</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MINE 서비스 컨테이너 기반 인프라 비용 예측 리포트]]></title>
            <link>https://velog.io/@halo_3735/MINE-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EA%B8%B0%EB%B0%98-%EC%9D%B8%ED%94%84%EB%9D%BC-%EB%B9%84%EC%9A%A9-%EC%98%88%EC%B8%A1-%EB%A6%AC%ED%8F%AC%ED%8A%B8</link>
            <guid>https://velog.io/@halo_3735/MINE-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EA%B8%B0%EB%B0%98-%EC%9D%B8%ED%94%84%EB%9D%BC-%EB%B9%84%EC%9A%A9-%EC%98%88%EC%B8%A1-%EB%A6%AC%ED%8F%AC%ED%8A%B8</guid>
            <pubDate>Sun, 12 Apr 2026 17:08:55 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개요">1. 개요</h1>
<h2 id="11-문서-목적">1.1. 문서 목적</h2>
<h2 id="12-목차">1.2. 목차</h2>
<p>1) 개요</p>
<p>2) V2 배포일</p>
<p>3) 각 서비스 별 예상 비용</p>
<p>4) 총합</p>
<h1 id="2-v2-배포일">2. V2 배포일</h1>
<p>2026.03.02~2026.03.22, 총 21일</p>
<h1 id="3-각-서비스별-예상-비용">3. 각 서비스별 예상 비용</h1>
<h2 id="31-고정-비용">3.1 고정 비용</h2>
<table>
<thead>
<tr>
<th>리소스</th>
<th>과금 기준</th>
<th>수량</th>
</tr>
</thead>
<tbody><tr>
<td>EC2 인스턴스</td>
<td>시간당</td>
<td>18개 (예정)</td>
</tr>
<tr>
<td>EBS 볼륨</td>
<td>GB / 월</td>
<td>EC2당 연결</td>
</tr>
<tr>
<td>RDS 스토리지</td>
<td>GB / 월</td>
<td>200GB × 2</td>
</tr>
</tbody></table>
<h3 id="311-인스턴스">3.1.1 인스턴스</h3>
<p>📦 EC2 인스턴스 구성</p>
<table>
<thead>
<tr>
<th>환경</th>
<th>BE</th>
<th>FE</th>
<th>AI</th>
<th>소계</th>
</tr>
</thead>
<tbody><tr>
<td>Release</td>
<td>3</td>
<td>1</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>Prod</td>
<td>3</td>
<td>1</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td><strong>6</strong></td>
<td><strong>2</strong></td>
<td><strong>2</strong></td>
<td><strong>10</strong></td>
</tr>
</tbody></table>
<p>💰 EC2 비용 (21일 기준)</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>수량</th>
<th>비용</th>
</tr>
</thead>
<tbody><tr>
<td>BE (release + prod)</td>
<td>6개</td>
<td>$62.90</td>
</tr>
<tr>
<td>FE (release + prod)</td>
<td>2개</td>
<td>$20.97</td>
</tr>
<tr>
<td>AI (release + prod)</td>
<td>2개</td>
<td>$20.97</td>
</tr>
<tr>
<td>모니터링</td>
<td>1개</td>
<td>$10.48</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td><strong>11개</strong></td>
<td><strong>$115.31</strong></td>
</tr>
</tbody></table>
<p>📊 최종 정리</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>비용</th>
</tr>
</thead>
<tbody><tr>
<td>EC2 총 비용 (21일)</td>
<td><strong>$115.31</strong></td>
</tr>
</tbody></table>
<h3 id=""></h3>
<h3 id="322-ebs-볼륨">3.2.2 EBS 볼륨</h3>
<p>EC2 인스턴스의 애플리케이션 데이터 및 로그 저장을 위해 <strong>gp3 타입 EBS 볼륨</strong>을 사용하였다.</p>
<h3 id="ebs-구성">EBS 구성</h3>
<table>
<thead>
<tr>
<th>서비스</th>
<th>release</th>
<th>prod</th>
<th>총 볼륨 수</th>
<th>용량</th>
</tr>
</thead>
<tbody><tr>
<td>Backend</td>
<td>3</td>
<td>3</td>
<td>6</td>
<td>10GB</td>
</tr>
<tr>
<td>Frontend</td>
<td>1</td>
<td>1</td>
<td>2</td>
<td>12GB</td>
</tr>
<tr>
<td>AI</td>
<td>1</td>
<td>1</td>
<td>2</td>
<td>30GB</td>
</tr>
<tr>
<td>Monitoring</td>
<td>-</td>
<td>-</td>
<td>1</td>
<td>50GB</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td></td>
<td></td>
<td><strong>11개</strong></td>
<td><strong>194GB</strong></td>
</tr>
</tbody></table>
<h3 id="비용-산정">비용 산정</h3>
<ul>
<li>단가: <strong>$0.096 / GB / month</strong></li>
<li>총 용량: <strong>194GB</strong></li>
</ul>
<pre><code>월 비용
= 194GB × $0.096
= $18.62</code></pre><pre><code>21일 비용
= $18.62 × (21 / 31)
≈ $12.62</code></pre><p><strong>총 비용(21일): $12.62</strong></p>
<h3 id="성능-설정">성능 설정</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>볼륨 타입</td>
<td>gp3</td>
</tr>
<tr>
<td>기본 IOPS</td>
<td>3000</td>
</tr>
<tr>
<td>기본 처리량</td>
<td>125 MB/s</td>
</tr>
</tbody></table>
<h3 id="323-rds-스토리지">3.2.3 RDS 스토리지</h3>
<p>Release 환경과 Prod 환경에 각각 PostgreSQL RDS 인스턴스를 구성하였다.</p>
<h3 id="rds-스토리지-구성">RDS 스토리지 구성</h3>
<table>
<thead>
<tr>
<th>DB 인스턴스</th>
<th>환경</th>
<th>엔진</th>
<th>용량</th>
</tr>
</thead>
<tbody><tr>
<td>mine-mvp1-db</td>
<td>release</td>
<td>PostgreSQL</td>
<td>200GB</td>
</tr>
<tr>
<td>mine-mvp1-db-prod</td>
<td>prod</td>
<td>PostgreSQL</td>
<td>200GB</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td></td>
<td></td>
<td><strong>400GB</strong></td>
</tr>
</tbody></table>
<h3 id="비용-산정-1">비용 산정</h3>
<ul>
<li>스토리지 타입: <strong>gp3</strong></li>
<li>단가: <strong>$0.138 / GB / month</strong></li>
</ul>
<pre><code>월 비용
= 400GB × $0.138
= $55.20</code></pre><pre><code>21일 비용
= $55.20 × (21 / 31)
≈ $37.40</code></pre><p><strong>총 비용(21일): $37.40</strong></p>
<h3 id="설정">설정</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>스토리지 타입</td>
<td>gp3</td>
</tr>
<tr>
<td>기본 IOPS</td>
<td>3000</td>
</tr>
<tr>
<td>Multi-AZ</td>
<td>미사용</td>
</tr>
</tbody></table>
<h2 id="32-가변-비용">3.2 가변 비용</h2>
<blockquote>
<p>가변비용은 실제 네트워크 전송량 및 서비스 사용량에 따라 변동되는 비용이다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>리소스</th>
<th>과금 기준</th>
</tr>
</thead>
<tbody><tr>
<td>데이터 전송 (Inter-Region)</td>
<td>APN2 → APN1 GB당</td>
</tr>
<tr>
<td>데이터 전송 (Internet Out)</td>
<td>GB당</td>
</tr>
<tr>
<td>S3 저장 및 요청</td>
<td>요청 수 + GB</td>
</tr>
</tbody></table>
<h3 id="-1"></h3>
<hr>
<h3 id="321-데이터-전송-internet-out">3.2.1 데이터 전송 (Internet Out)</h3>
<p>외부 인터넷으로 나가는 아웃바운드 트래픽에 대해 과금된다.</p>
<p>퍼블릭 서브넷의 경우 <strong>EC2 → IGW → 인터넷</strong>, 프라이빗 서브넷의 경우 <strong>EC2 → NAT Gateway → IGW → 인터넷</strong> 경로로 전송된다.</p>
<p>API 응답 전송, 외부 API 호출, 파일 다운로드 제공 등이 해당되며, 인바운드(수신)는 무료이고 아웃바운드(송신)만 과금된다.</p>
<h3 id="단가">단가</h3>
<table>
<thead>
<tr>
<th>구간</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>처음 10 TB / 월</td>
<td>$0.126 / GB</td>
</tr>
<tr>
<td>다음 40 TB / 월</td>
<td>$0.122 / GB</td>
</tr>
<tr>
<td>다음 100 TB / 월</td>
<td>$0.117 / GB</td>
</tr>
</tbody></table>
<h3 id="사용량">사용량</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>6일 사용량</td>
<td>8.32 GB</td>
</tr>
<tr>
<td>일 평균</td>
<td>1.39 GB</td>
</tr>
</tbody></table>
<h3 id="비용-산정-2">비용 산정</h3>
<pre><code>21일 예상 사용량
= 1.39 GB × 21일
= 29.19 GB</code></pre><pre><code>21일 예상 비용
= 29.19 GB × $0.126
≈ $3.68</code></pre><p><strong>예상 비용 (21일): $3.68</strong></p>
<hr>
<h3 id="322-데이터-전송-regional-az간">3.2.2 데이터 전송 (Regional, AZ간)</h3>
<p>동일 리전 내 서로 다른 가용영역(AZ) 간 데이터 전송에 대해 과금된다.</p>
<p>예를 들어 <strong>AZ-a(10.0.3.x)</strong> 와 <strong>AZ-b(10.0.4.x)</strong> 간 통신이 해당된다.</p>
<p>ALB → EC2(다른 AZ), EC2 → RDS(다른 AZ) 등 AZ 간 내부 통신 시 발생하며, 송신과 수신 양방향 각각 과금된다.</p>
<h3 id="사용량-1">사용량</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>단가</td>
<td>$0.01 / GB</td>
</tr>
<tr>
<td>6일 사용량</td>
<td>29.04 GB</td>
</tr>
<tr>
<td>일 평균</td>
<td>4.84 GB</td>
</tr>
</tbody></table>
<h3 id="비용-산정-3">비용 산정</h3>
<pre><code>21일 예상 사용량
= 4.84 GB × 21일
= 101.64 GB</code></pre><pre><code>21일 예상 비용
= 101.64 GB × $0.01
≈ $1.02</code></pre><p><strong>예상 비용 (21일): $1.02</strong></p>
<hr>
<h3 id="323-s3-저장-및-요청">3.2.3 S3 저장 및 요청</h3>
<p>S3 버킷에 저장된 데이터 용량 및 API 요청 수에 따라 과금된다.</p>
<p>파일 업로드/다운로드, 정적 자산 저장, 로그 저장 등이 해당된다.</p>
<h3 id="단가-1">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>S3 Standard 저장</td>
<td>$0.025 / GB-월</td>
</tr>
<tr>
<td>PUT / COPY / POST / LIST 요청</td>
<td>$0.0045 / 1,000건</td>
</tr>
<tr>
<td>GET / SELECT 요청</td>
<td>$0.00035 / 1,000건</td>
</tr>
</tbody></table>
<h3 id="예상-비용">예상 비용</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>현재 사용량</td>
<td>극히 미미</td>
</tr>
<tr>
<td>21일 예상 비용</td>
<td>&lt; $0.01</td>
</tr>
</tbody></table>
<hr>
<h3 id="324-가변비용-합계">3.2.4 가변비용 합계</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>21일 예상 비용</th>
</tr>
</thead>
<tbody><tr>
<td>Internet Out</td>
<td>$3.68</td>
</tr>
<tr>
<td>Regional (AZ간)</td>
<td>$1.02</td>
</tr>
<tr>
<td>S3</td>
<td>~$0.01</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td><strong>$4.71</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="33-혼합-비용">3.3 혼합 비용</h2>
<blockquote>
<p>혼합비용은 리소스가 존재하는 시간에 따라 발생하는 고정비용과, 실제 데이터 처리량에 따라 발생하는 가변비용이 함께 포함되는 비용이다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>리소스</th>
<th>고정 비용</th>
<th>가변 비용</th>
<th>수량</th>
</tr>
</thead>
<tbody><tr>
<td>NAT Gateway</td>
<td>$0.045 / hour</td>
<td>$0.045 / GB</td>
<td>1</td>
</tr>
<tr>
<td>VPC Endpoint (Interface)</td>
<td>$0.01 / hour / AZ</td>
<td>$0.01 / GB</td>
<td>5</td>
</tr>
<tr>
<td>ALB</td>
<td>$0.0225 / hour</td>
<td>$0.008 / LCU</td>
<td>2</td>
</tr>
<tr>
<td>RDS</td>
<td>인스턴스 시간당 과금</td>
<td>I/O 및 백업 초과분</td>
<td>2</td>
</tr>
</tbody></table>
<h3 id="331-nat-gateway">3.3.1 NAT Gateway</h3>
<p>프라이빗 서브넷의 리소스가 인터넷에 접근할 때 사용되는 게이트웨이이다.</p>
<p>NAT Gateway가 존재하는 것만으로 시간당 요금이 발생하며, 이를 통해 처리되는 데이터량에 따라 추가 요금이 발생한다.</p>
<p>프라이빗 서브넷의 EC2가 외부 API 호출, 패키지 다운로드 등 인터넷 접근 시 사용된다.</p>
<h3 id="단가-2">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>시간당 요금 (고정)</td>
<td>$0.045 / hour</td>
</tr>
<tr>
<td>데이터 처리 (가변)</td>
<td>$0.045 / GB</td>
</tr>
</tbody></table>
<h3 id="구성">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>수량</td>
<td>1개</td>
</tr>
<tr>
<td>NAT Gateway ID</td>
<td>nat-0fd43898dec6551c3</td>
</tr>
</tbody></table>
<h3 id="비용-산정-4">비용 산정</h3>
<pre><code>고정 비용
= $0.045 × 24h × 21일
= $22.68</code></pre><pre><code>가변 비용
= 데이터 처리량 기반
≈ $0.50</code></pre><p><strong>소계: $23.18</strong></p>
<h3 id="332-vpc-endpoint-interface">3.3.2 VPC Endpoint (Interface)</h3>
<p>프라이빗 서브넷에서 AWS 서비스에 접근할 때 인터넷을 거치지 않고 AWS 내부 네트워크를 통해 연결하는 엔드포인트이다.</p>
<p>SSM(Session Manager), EC2 Messages 등 AWS 관리형 서비스 접근 시 사용된다.</p>
<p>엔드포인트가 존재하는 것만으로 시간당 요금이 발생하며, 통과하는 데이터량에 따라 추가 요금이 발생한다.</p>
<h3 id="단가-3">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>시간당 요금 (고정)</td>
<td>$0.01 / hour / AZ</td>
</tr>
<tr>
<td>데이터 처리 (가변)</td>
<td>$0.01 / GB</td>
</tr>
</tbody></table>
<h3 id="구성-1">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>수량</td>
<td>5개</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>Endpoint ID</th>
<th>서비스</th>
</tr>
</thead>
<tbody><tr>
<td>vpce-02add8c3cac31f4c1</td>
<td>com.amazonaws.ap-northeast-2.ssm</td>
</tr>
<tr>
<td>vpce-09a855e0d28f08953</td>
<td>com.amazonaws.ap-northeast-2.ec2messages</td>
</tr>
<tr>
<td>vpce-062a38b55e0f5868f</td>
<td>com.amazonaws.ap-northeast-2.ssmmessages</td>
</tr>
<tr>
<td>vpce-09263cce14ec92779</td>
<td>PrivateLink (사용자 정의)</td>
</tr>
<tr>
<td>vpce-07e82dcbe08ebb6b7</td>
<td>PrivateLink (사용자 정의)</td>
</tr>
</tbody></table>
<h3 id="비용-산정-5">비용 산정</h3>
<pre><code>고정 비용
= 5개 × $0.01 × 24h × 21일
= $25.20</code></pre><pre><code>가변 비용
= 데이터 처리량 기반
≈ $0.10</code></pre><p><strong>소계: $25.30</strong></p>
<h3 id="333-alb-application-load-balancer">3.3.3 ALB (Application Load Balancer)</h3>
<p>들어오는 트래픽을 여러 EC2 인스턴스에 분산하는 로드밸런서이다.</p>
<p>ALB가 존재하는 것만으로 시간당 요금이 발생하며, 처리하는 트래픽량에 따라 LCU(Load Balancer Capacity Unit) 요금이 추가된다.</p>
<h3 id="단가-4">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>시간당 요금 (고정)</td>
<td>$0.0225 / hour</td>
</tr>
<tr>
<td>LCU 요금 (가변)</td>
<td>$0.008 / LCU-hour</td>
</tr>
</tbody></table>
<h3 id="구성-2">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>수량</td>
<td>2개</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>ALB 이름</th>
<th>환경</th>
</tr>
</thead>
<tbody><tr>
<td>mine-mvp1-alb-internet</td>
<td>release</td>
</tr>
<tr>
<td>mine-mvp1-alb-internet-prod</td>
<td>prod</td>
</tr>
</tbody></table>
<h3 id="비용-산정-6">비용 산정</h3>
<pre><code>고정 비용
= 2개 × $0.0225 × 24h × 21일
= $22.68</code></pre><pre><code>가변 비용
= LCU 사용량 기반
≈ $2.00</code></pre><p><strong>소계: $24.68</strong></p>
<hr>
<h3 id="334-rds-relational-database-service">3.3.4 RDS (Relational Database Service)</h3>
<p>관리형 관계형 데이터베이스 서비스이다.</p>
<p>인스턴스가 실행 중인 시간에 대해 요금이 발생하며, gp3 스토리지의 경우 기본 IOPS(3000)가 포함되어 있어 일반적인 워크로드에서는 추가 I/O 비용이 발생하지 않는다.</p>
<p>백업은 할당된 스토리지 용량까지 무료이며, 초과 시 추가 요금이 발생한다.</p>
<h3 id="단가-5">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스 시간당 요금 (고정)</td>
<td>$0.056 / hour (db.t3.micro)</td>
</tr>
<tr>
<td>I/O (가변)</td>
<td>gp3 기본 포함</td>
</tr>
<tr>
<td>백업 (가변)</td>
<td>할당 스토리지까지 무료</td>
</tr>
</tbody></table>
<h3 id="구성-3">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>수량</td>
<td>2개</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>DB 인스턴스</th>
<th>환경</th>
<th>클래스</th>
<th>엔진</th>
<th>Multi-AZ</th>
</tr>
</thead>
<tbody><tr>
<td>mine-mvp1-db</td>
<td>release</td>
<td>db.t3.micro</td>
<td>PostgreSQL</td>
<td>No</td>
</tr>
<tr>
<td>mine-mvp1-db-prod</td>
<td>prod</td>
<td>db.t3.micro</td>
<td>PostgreSQL</td>
<td>No</td>
</tr>
</tbody></table>
<h3 id="비용-산정-7">비용 산정</h3>
<pre><code>고정 비용
= 2개 × $0.056 × 24h × 21일
= $56.45</code></pre><pre><code>가변 비용
= I/O, 백업 초과분
≈ $0.00</code></pre><p><strong>소계: $56.45</strong></p>
<hr>
<h3 id="335-cloudwatch">3.3.5 CloudWatch</h3>
<p>AWS 리소스의 모니터링 및 로그 관리 서비스이다. 기본 메트릭(EC2, RDS 등)은 무료로 제공되며, 커스텀 메트릭과 10개 초과 알람은 추가 과금된다. 로그는 수집 시 GB당, 저장 시 GB/월 단위로 과금된다.</p>
<h3 id="단가-6">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>메트릭 (고정)</td>
<td>처음 10개 무료, 이후 $0.30 / 메트릭 / 월</td>
</tr>
<tr>
<td>알람 (고정)</td>
<td>처음 10개 무료, 이후 $0.10 / 알람 / 월</td>
</tr>
<tr>
<td>로그 수집 (가변)</td>
<td>$0.76 / GB</td>
</tr>
<tr>
<td>로그 저장 (가변)</td>
<td>$0.03 / GB / 월</td>
</tr>
</tbody></table>
<h3 id="구성-4">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>로그 그룹</td>
<td>13개</td>
</tr>
<tr>
<td>로그 저장량</td>
<td>0.11 GB</td>
</tr>
<tr>
<td>알람</td>
<td>12개</td>
</tr>
</tbody></table>
<h3 id="비용-산정-8">비용 산정</h3>
<pre><code>고정 비용
= 2개 × $0.10 × 0.68
= $0.14</code></pre><pre><code>가변 비용
= 0.11GB × $0.03 × 0.68
≈ $0.01</code></pre><p><strong>소계: $0.15</strong></p>
<hr>
<h3 id="-2"></h3>
<h3 id="336-route-53">3.3.6 Route 53</h3>
<p>AWS의 DNS 서비스이다. 도메인의 DNS 레코드를 관리하는 호스팅 존당 월 고정 요금이 발생하며, DNS 쿼리 수에 따라 추가 요금이 발생한다.</p>
<h3 id="단가-7">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>호스팅 존 (고정)</td>
<td>$0.50 / 존 / 월</td>
</tr>
<tr>
<td>DNS 쿼리 (가변)</td>
<td>$0.40 / 백만 쿼리</td>
</tr>
</tbody></table>
<h3 id="구성-5">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>호스팅 존 수</td>
<td>1개</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>호스팅 존</th>
<th>도메인</th>
</tr>
</thead>
<tbody><tr>
<td>Z05484972ZPH788IL0JR6</td>
<td><a href="http://imymemine.kr/">imymemine.kr</a></td>
</tr>
</tbody></table>
<h3 id="비용-산정-9">비용 산정</h3>
<pre><code>고정 비용
= 1개 × $0.50 × 0.68
= $0.34</code></pre><pre><code>가변 비용
= DNS 쿼리 수 기반
≈ $0.01</code></pre><p><strong>소계: $0.35</strong></p>
<hr>
<h3 id="337-parameter-store">3.3.7 Parameter Store</h3>
<p>AWS Systems Manager의 구성 데이터 및 시크릿 관리 서비스이다. 표준 파라미터는 무료이며, 고급 파라미터는 파라미터당 과금된다. 현재는 표준 파라미터만 사용 중인 것으로 가정하였다.</p>
<h3 id="단가-8">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>표준 파라미터 (고정)</td>
<td>무료</td>
</tr>
<tr>
<td>고급 파라미터 (고정)</td>
<td>$0.05 / 파라미터 / 월</td>
</tr>
<tr>
<td>API 호출 (가변)</td>
<td>표준 무료, 고급 $0.05 / 10,000건</td>
</tr>
</tbody></table>
<h3 id="구성-6">구성</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>파라미터 수</td>
<td>89개</td>
</tr>
<tr>
<td>파라미터 유형</td>
<td>표준 (추정)</td>
</tr>
</tbody></table>
<h3 id="비용-산정-10">비용 산정</h3>
<pre><code>고정 비용
= 표준 파라미터 무료
= $0.00</code></pre><pre><code>가변 비용
= API 호출 무료
= $0.00</code></pre><p><strong>소계: $0.00</strong></p>
<hr>
<h3 id="-3"></h3>
<h3 id="338-elasticache-valkeyredis">3.3.8 ElastiCache (Valkey/Redis)</h3>
<p>인메모리 캐시 서비스로 세션 관리, 캐싱 등에 사용된다. Valkey(Redis 호환) 엔진을 사용하며, 인스턴스 실행 시간에 대해 고정 과금되고 백업 스토리지와 데이터 전송에 대해 가변 과금된다.</p>
<h3 id="단가-9">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스 시간당 (고정)</td>
<td>$0.019 / hour (cache.t4g.micro)</td>
</tr>
<tr>
<td>백업 스토리지 (가변)</td>
<td>$0.085 / GB-월</td>
</tr>
</tbody></table>
<h3 id="구성-7">구성</h3>
<table>
<thead>
<tr>
<th>클러스터</th>
<th>환경</th>
<th>노드 타입</th>
<th>노드 수</th>
</tr>
</thead>
<tbody><tr>
<td>release-redis-001</td>
<td>release</td>
<td>cache.t4g.micro</td>
<td>1</td>
</tr>
<tr>
<td>prod-redis-001</td>
<td>prod</td>
<td>cache.t4g.micro</td>
<td>1</td>
</tr>
</tbody></table>
<h3 id="비용-산정-11">비용 산정</h3>
<pre><code>고정 비용
= 2개 × $0.019 × 24h × 21일
= $19.15</code></pre><pre><code>가변 비용
= 백업/전송량 기반
≈ $0.00</code></pre><p><strong>소계: $19.15</strong></p>
<hr>
<h3 id="339-amazon-mq-rabbitmq">3.3.9 Amazon MQ (RabbitMQ)</h3>
<p>메시지 브로커 서비스로 서비스 간 비동기 통신에 사용된다. RabbitMQ 엔진을 사용하며, 브로커 인스턴스 시간과 스토리지에 대해 고정 과금되고 데이터 전송에 대해 가변 과금된다.</p>
<h3 id="단가-10">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스 시간당 (고정)</td>
<td>$0.034 / hour (mq.t3.micro)</td>
</tr>
<tr>
<td>스토리지 (고정)</td>
<td>$0.12 / GB-월</td>
</tr>
<tr>
<td>데이터 전송 (가변)</td>
<td>표준 데이터 전송 요금</td>
</tr>
</tbody></table>
<h3 id="구성-8">구성</h3>
<table>
<thead>
<tr>
<th>브로커</th>
<th>환경</th>
<th>인스턴스 타입</th>
<th>배포 모드</th>
</tr>
</thead>
<tbody><tr>
<td>rel-mq-broker</td>
<td>release</td>
<td>mq.t3.micro</td>
<td>SINGLE_INSTANCE</td>
</tr>
<tr>
<td>prod-mq-broker</td>
<td>prod</td>
<td>mq.t3.micro</td>
<td>SINGLE_INSTANCE</td>
</tr>
</tbody></table>
<h3 id="비용-산정-12">비용 산정</h3>
<pre><code>고정 비용
= 2개 × $0.034 × 24h × 21일
= $34.27</code></pre><pre><code>가변 비용
= 메시지/전송량 기반
≈ $0.10</code></pre><p><strong>소계: $34.37</strong></p>
<hr>
<h3 id="-4"></h3>
<h3 id="3310-dms-database-migration-service">3.3.10 DMS (Database Migration Service)</h3>
<p>데이터베이스 마이그레이션 서비스로 무중단 배포 시 DB 동기화에 사용된다. 복제 인스턴스 실행 시간에 대해 고정 과금되며, 전송 데이터량에 따라 추가 비용이 발생한다. 아래 비용은 2월 실제 사용량을 기준으로 산정하였다.</p>
<h3 id="단가-11">단가</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>단가</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스 시간당 (고정)</td>
<td>$0.070 / hour (dms.t3.small)</td>
</tr>
<tr>
<td>데이터 전송 (가변)</td>
<td>$0.02 / GB</td>
</tr>
</tbody></table>
<h3 id="사용량-2">사용량</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>사용량</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스 타입</td>
<td>dms.t3.small</td>
</tr>
<tr>
<td>사용 시간</td>
<td>219시간</td>
</tr>
<tr>
<td>데이터 전송 (In)</td>
<td>0.49 GB</td>
</tr>
<tr>
<td>데이터 전송 (Out)</td>
<td>0.001 GB</td>
</tr>
</tbody></table>
<h3 id="비용-산정-13">비용 산정</h3>
<pre><code>고정 비용
= 219h × $0.070
= $15.33</code></pre><pre><code>가변 비용
= 0.49 GB × $0.02
≈ $0.01</code></pre><p><strong>소계: $15.34</strong></p>
<h3 id="-5"></h3>
<h3 id="3311-혼합비용-합계">3.3.11 혼합비용 합계</h3>
<table>
<thead>
<tr>
<th>리소스</th>
<th>고정 부분</th>
<th>가변 부분</th>
<th>21일 합계</th>
</tr>
</thead>
<tbody><tr>
<td>NAT Gateway (1개)</td>
<td>$22.68</td>
<td>~$0.50</td>
<td>$23.18</td>
</tr>
<tr>
<td>VPC Endpoint (5개)</td>
<td>$25.20</td>
<td>~$0.10</td>
<td>$25.30</td>
</tr>
<tr>
<td>ALB (2개)</td>
<td>$22.68</td>
<td>~$2.00</td>
<td>$24.68</td>
</tr>
<tr>
<td>RDS 인스턴스 (2개)</td>
<td>$56.45</td>
<td>~$0.00</td>
<td>$56.45</td>
</tr>
<tr>
<td>CloudWatch</td>
<td>$0.14</td>
<td>~$0.01</td>
<td>$0.15</td>
</tr>
<tr>
<td>Route 53 (1개 존)</td>
<td>$0.34</td>
<td>~$0.01</td>
<td>$0.35</td>
</tr>
<tr>
<td>Parameter Store</td>
<td>$0.00</td>
<td>$0.00</td>
<td>$0.00</td>
</tr>
<tr>
<td>ElastiCache (2개)</td>
<td>$19.15</td>
<td>~$0.00</td>
<td>$19.15</td>
</tr>
<tr>
<td>Amazon MQ (2개)</td>
<td>$34.27</td>
<td>~$0.10</td>
<td>$34.37</td>
</tr>
<tr>
<td>DMS</td>
<td>$15.33</td>
<td>~$0.01</td>
<td>$15.34</td>
</tr>
<tr>
<td><strong>혼합비용 총합</strong></td>
<td><strong>$196.24</strong></td>
<td><strong>~$2.73</strong></td>
<td><strong>$198.97</strong></td>
</tr>
</tbody></table>
<h2 id="-6"></h2>
<h1 id="-7"></h1>
<h1 id="4-총합">4. 총합</h1>
<p>총 예상 비용은 <strong>고정비용, 가변비용, 혼합비용</strong>을 합산하여 산정하였다.</p>
<h2 id="41-비용-합계">4.1 비용 합계</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>21일 예상 비용</th>
</tr>
</thead>
<tbody><tr>
<td>고정 비용</td>
<td>$165.33</td>
</tr>
<tr>
<td>가변 비용</td>
<td>$4.71</td>
</tr>
<tr>
<td>혼합 비용</td>
<td>$198.97</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td><strong>$369.01</strong></td>
</tr>
</tbody></table>
<h2 id="42-산정식">4.2 산정식</h2>
<pre><code>총 예상 비용
= 고정 비용 + 가변 비용 + 혼합 비용
= $165.33 + $4.71 + $198.97
= $369.01</code></pre><h2 id="43-최종-정리">4.3 최종 정리</h2>
<p>2026.03.02부터 2026.03.22까지 총 <strong>21일간 MINE 서비스 인프라를 운영할 경우 전체 예상 비용은 $369.01로 산정된다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[V2 FIS를 활용한 장애 발생과 대응 시나리오 1 (ASG 환경에서 인스턴스가 다운된 상황)]]></title>
            <link>https://velog.io/@halo_3735/V2-FIS%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9E%A5%EC%95%A0-%EB%B0%9C%EC%83%9D%EA%B3%BC-%EB%8C%80%EC%9D%91-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-1-ASG-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EA%B0%80-%EB%8B%A4%EC%9A%B4%EB%90%9C-%EC%83%81%ED%99%A9</link>
            <guid>https://velog.io/@halo_3735/V2-FIS%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9E%A5%EC%95%A0-%EB%B0%9C%EC%83%9D%EA%B3%BC-%EB%8C%80%EC%9D%91-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-1-ASG-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EA%B0%80-%EB%8B%A4%EC%9A%B4%EB%90%9C-%EC%83%81%ED%99%A9</guid>
            <pubDate>Tue, 03 Mar 2026 07:11:59 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개요">1. 개요</h1>
<h2 id="11-문서-목적">1.1 문서 목적</h2>
<p>FI와 ASG 제한 걸린 상황일 때, 장애 알림과 대응에대한 하나의 케이스를 정리한 문서이다.</p>
<h2 id="12-목차">1.2 목차</h2>
<p>1) 개요</p>
<p>2) 준비</p>
<p>3) 실행 </p>
<p>4) 결과</p>
<p>5) 대응 방법</p>
<p>6) 도입 후 개선된 사항</p>
<p>7) 고찰</p>
<h1 id="2-상황">2. 상황</h1>
<blockquote>
<p>백엔드 인스턴스가 2개가 디폴트인 ASG에서 하나가 문제가 생겨 다운되었지만, ASG 자체에 문제가 생겨 인스턴스가 띄워지지 않아 트래픽 분산이 정상적으로 이루어지지 않는 상황</p>
</blockquote>
<h2 id="21--부하테스트">2.1  부하테스트</h2>
<ul>
<li>soak 테스트</li>
<li>동시접속자 3000명</li>
<li>Get /categories</li>
<li>10분</li>
</ul>
<h2 id="22-fi">2.2. FI</h2>
<ul>
<li>백엔드 인스턴스 1개 중지</li>
<li><strong>FIS Action</strong>: <code>aws:ec2:stop-instances</code></li>
<li><strong>대상</strong>: <code>mine_mvp1_be_asg</code> 중 1개</li>
<li><strong>주입 시간</strong>: 5분</li>
</ul>
<h2 id="23-asg-제한">2.3. ASG 제한</h2>
<ul>
<li><p>백엔드 Release 서버 ASG 그룹 새로운 인스턴스 Lanch 정지</p>
<p>  <img src="attachment:48276104-e485-4518-ac34-418805de7a79:image.png" alt="image.png"></p>
<ul>
<li>ASG의 장애를 표현하고자함</li>
</ul>
</li>
</ul>
<h2 id="24-api-base-line">2.4. API Base Line</h2>
<blockquote>
<p>부하 없었던 정상 트래픽을 가지고 있는 release 서버 2026.02.20 00:00:00 ~ 2026.03.01 11:59:59 시간대 API별 p95 API 응답 속도 리스트 사용</p>
</blockquote>
<table>
<thead>
<tr>
<th>URI</th>
<th>p95 (ms)</th>
</tr>
</thead>
<tbody><tr>
<td>/auth/logout</td>
<td>88.4</td>
</tr>
<tr>
<td>/auth/oauth/{provider}</td>
<td>256</td>
</tr>
<tr>
<td>/cards</td>
<td>14.3</td>
</tr>
<tr>
<td>/cards/{cardId}</td>
<td>54.8</td>
</tr>
<tr>
<td>/cards/{cardId}/attempts</td>
<td>66.8</td>
</tr>
<tr>
<td>/cards/{cardId}/attempts/{attemptId}</td>
<td>48.7</td>
</tr>
<tr>
<td>/cards/{cardId}/attempts/{attemptId}/upload-complete</td>
<td>22600</td>
</tr>
<tr>
<td>/categories</td>
<td>20</td>
</tr>
<tr>
<td>/categories/{categoryId}/keywords</td>
<td>60.8</td>
</tr>
<tr>
<td>/keywords</td>
<td>6.75</td>
</tr>
<tr>
<td>/learning/presigned-url</td>
<td>66.8</td>
</tr>
<tr>
<td>/learning/warmup</td>
<td>22.1</td>
</tr>
<tr>
<td>/pvp/histories</td>
<td>167</td>
</tr>
<tr>
<td>/pvp/rooms</td>
<td>73.2</td>
</tr>
<tr>
<td>/pvp/rooms/submissions/{submissionId}/complete</td>
<td>66.3</td>
</tr>
<tr>
<td>/pvp/rooms/{roomId}</td>
<td>38.6</td>
</tr>
<tr>
<td>/pvp/rooms/{roomId}/join</td>
<td>66.4</td>
</tr>
<tr>
<td>/pvp/rooms/{roomId}/result</td>
<td>88.6</td>
</tr>
<tr>
<td>/pvp/rooms/{roomId}/start-recording</td>
<td>36.1</td>
</tr>
<tr>
<td>/pvp/rooms/{roomId}/submissions</td>
<td>55.1</td>
</tr>
<tr>
<td>/users/me</td>
<td>7.09</td>
</tr>
<tr>
<td>/users/me/profile-image/presigned-url</td>
<td>617</td>
</tr>
<tr>
<td>/users/nickname/check</td>
<td>22.1</td>
</tr>
<tr>
<td>/ws/**</td>
<td>85.0</td>
</tr>
</tbody></table>
<h1 id="3-실행">3. 실행</h1>
<h2 id="31-순서">3.1 순서</h2>
<p>1) 부하테스트 실행</p>
<p>2) FI 실행</p>
<p>3) 알림 관측</p>
<h1 id="4-결과-예상">4. 결과 예상</h1>
<h2 id="가-모니터링">가. 모니터링</h2>
<p><img src="attachment:400b0b22-9d3a-4ed1-bb66-95287b703b7d:image.png" alt="image.png"></p>
<ul>
<li>get /catergories 의 초당 요청 횟수 증가</li>
<li>get /catergories 200 응답 퍼센테이지 감소</li>
<li>get /catergories의 API p95 응답지연 증가</li>
<li>get /catergories의 API p99 응답지연 증가</li>
</ul>
<h2 id="나-알림">나. 알림</h2>
<p>1) Discord 인스턴스 수 부족 경고</p>
<p><img src="attachment:839bd653-31d7-482d-b685-d838757d9eed:image.png" alt="image.png"></p>
<p>2) Discord API 응답시간 경고</p>
<p><img src="attachment:72844bde-50a1-4af6-9153-6acbf16c0dc3:image.png" alt="image.png"></p>
<p>3) Discord 500 에러 증가 경고</p>
<p><img src="attachment:268eab28-4c4f-477d-bfa1-3a3ca2c94ba7:image.png" alt="image.png"></p>
<h1 id="4-결과">4. 결과</h1>
<h3 id="411-모니터링">4.1.1 모니터링</h3>
<p><img src="attachment:54874635-0108-4199-8b92-5372f99212d6:image.png" alt="image.png"></p>
<p>P95가 21:06분부터 baseline을 넘어 5분동안 지속되고 21:12분에 23.04ms를 찍는 것을 확인할 수 있다.</p>
<h3 id="412-discord-asg-인스턴스-수-부족-경고">4.1.2 Discord asg 인스턴스 수 부족 경고</h3>
<p><img src="attachment:04c82767-7d80-4b60-8c4b-5024252e9e97:image.png" alt="image.png"></p>
<p>인스턴스의 개수가 크기조절 한도 최소값보다 작은 asg에 문제가 있는 상황에 대한 알림이 도착</p>
<h3 id="413-api-응답시간-지연-알람">4.1.3 API 응답시간 지연 알람</h3>
<p><img src="attachment:93ef1f83-891a-4d4c-983a-c849ac0c5033:image.png" alt="image.png"></p>
<p>4.1.1 에서 언급한, 5분동안 baseline을 넘은, 21:12분에 23.04ms /categores api 응답시간 지연 감지 알림이 도착</p>
<h3 id="414-discord-500-에러-증가-경고">4.1.4 Discord 500 에러 증가 경고</h3>
<p>500에러가 발생하지 않아 해당 알림이 오지 않았다.</p>
<h1 id="5-대응-방법">5. 대응 방법</h1>
<h2 id="51-aws-asg-접속해서-문제-해결">5.1 AWS ASG 접속해서 문제 해결</h2>
<h2 id="52-인스턴스-정상-복구-알림-대기">5.2 인스턴스 정상 복구 알림 대기</h2>
<p><img src="attachment:51e2d4c4-bdf1-4eec-b680-88c4b24df42d:image.png" alt="image.png"></p>
<p>ASG가 정상적으로 동작하여 최소 개수보다 인스턴스가 띄워지면 해당알림이 도착한다.</p>
<h2 id="53-트래픽-관측">5.3 트래픽 관측</h2>
<p>4.1.3의 알림에 나온 바로가기 (<a href="http://localhost:3333/d/adx8tjx)%EC%9D%98">http://localhost:3333/d/adx8tjx)의</a> <strong>PR 95 Requests Duration 패널과 2.4의 API baseline을 참고하여 응답시간 정상화를 확인한다.</strong></p>
<h2 id="54-장애-대응-일지-작성">5.4 장애 대응 일지 작성</h2>
<p><img src="attachment:88ffeb33-becc-461d-a1a3-c6ea154f2120:image.png" alt="image.png"></p>
<h1 id="6-도입-후-개선된-사항">6. 도입 후 개선된 사항</h1>
<p>1) 개발자 혹은 인프라 팀원들에게 장애를 알림을 알려주어 발생시 즉시 조치할 수 있을 뿐더러, 대응 프로세스까지 제공하여 조치에 걸리는 시간을 줄일 수 있다.</p>
<p>2) 팀원들과 장애를 공유하여 같은 장애가 일어날 시, 추적에 용이하다.</p>
<h1 id="7-고찰">7. 고찰</h1>
<p>1) 새로 띄워진 인스턴스 자체에 문제가 생기는 경우에 대한 알림 추가 필요</p>
<p>2) 해당 값이 100% 참값은 아니기 때문에 지속적인 baseline에 대한 지속적인 유효성 검사 필요</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이분탐색]]></title>
            <link>https://velog.io/@halo_3735/%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@halo_3735/%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89</guid>
            <pubDate>Tue, 24 Feb 2026 14:29:58 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개요">1. 개요</h1>
<h2 id="가-문서-목적">가. 문서 목적</h2>
<p>이분탐색에 대해 정리한 글이다.</p>
<h2 id="나-목차">나. 목차</h2>
<h1 id="2-시간-복잡도">2. 시간 복잡도</h1>
<p>모든 알고리즘에서 시간복잡도는 빼먹을 수 없다. 왜냐하면 가장 효율적인 알고리즘을 찾아 컴퓨터 연산량을 줄임으로서 회사에게 이득이 될 수 있고 빠른 사용자 응답 제공으로 이탈률을 줄일 수 있기 때문이다.</p>
<p>이분탐색의 시간복잡도는 정렬 후에 빨라진다. 만약 탐색을 한번만하고 대상 배열을 안 쓸 예정이라면 정렬을 할 필요가 없다. 왜냐하면 정렬에는 O(nlogn)이 사용된다. 만약 당신이 병합 정렬을 쓴다면 말이다. 순차탐색은 n 이므로 한번만 할 시에는 O(n)이다. 하지만 만약 여러번의 탐색을 진행한다면 이야기가 달라진다. 순차탐색은 매번 O(n)을 여러번인 n번 만큼 진행하기 때문에 시간복잡도는 O(n^2)을 의미한다. <strong>하지만 이분탐색은 O(logn)을 여러번인 n번 만큼 진행한다면 O(nlogn)이 되므로 순차탐색보다 빠르다.</strong> 여기서 집중해야할 점은 한번 탐색하는게 아닌 여러번 탐색하는 경우에 이미 정렬된 배열을 1/2씩 쪼개면서 탐색하는 이분탐색은 O(logN)의 성능을 가졌기 때문으로, 매번 O(n)이 시행되어야 하는 순차탐색보다 빠르다는 것이다.</p>
<p>결국 이분탐색은 초기 O(nlogn)인 정렬만 해주면 되는 처음에만 힘이 빡 들어가는 그 뒤에는 순탄한 알고리즘 인 것이다.</p>
<p>하지만 새로운 데이터가 계속 추가된다면? 그때는 이분탐색을 사용해서 들어갈 위치를 찾고 그 위치의 뒤에 원소들을 한칸씩 뒤로 밀면 되므로, O(logn) + O(n) = O(n)이 된다.</p>
<h1 id="3-mid">3. mid</h1>
<p>(left+right) // 2 = mid</p>
<h1 id="4-진행-방법">4. 진행 방법</h1>
<p>mid를 먼저 구하고 찾고자 하는 값이 mid면 그 값이나 인덱스를 출력한다. 만약 아니라면 mid와 대소비교를 진행한다. mid보다 크면 left = mid +1, 작다면 right = mid -1이다.</p>
<h1 id="5-키워드">5. 키워드</h1>
<ul>
<li>left  = mid + 1</li>
<li>right = mid - 1</li>
<li>left &lt;= right : 결국 마지막에 같은 값이 되서</li>
</ul>
<h1 id="6-느낀점">6. 느낀점</h1>
<p>내 생각에 알고리즘은 뚝심이다. 끝까지 풀어서 모든 로직의 이유를 경험하여 직관적으로 풀 수 있게 만드는 &quot;뚝심&quot;</p>
<h1 id="7-시간초과">7. 시간초과</h1>
<pre><code class="language-python">import sys

input = sys.stdin.readline

N = int(input())

a = sorted(list(map(int, input().split())))


M = int(input())


n_arr = list(map(int, input().split()))

def bs(n):
    left = 0
    right = N-1


    while left &lt;= right:
        mid = (left + right) // 2

        if n == a[mid]:
            return mid

        elif n &lt;= a[mid]:
            right = mid -1

        else:
            left = mid + 1
    return -1


def find_left_right(idx, n):
    origin_idx = idx
    cnt = 1
    start_idx = 0
    end_idx = N-1

    # 왼쪽 찾기
    while idx &gt; 0 :
        idx -=1
        if a[idx] == n:
            cnt+=1
        else:
            break

    idx = origin_idx
    # 왼쪽 찾기
    while idx &lt; end_idx :
        idx +=1
        if a[idx] == n:
            cnt+=1
        else:
            break

    return cnt       


    # 오른쪽 찾기

ans = []
for n in n_arr:
    cnt=0
    idx = bs(n)

    if idx == -1:
        cnt = 0
    else:
        cnt = find_left_right(idx, n)

    ans.append(cnt)

for i in ans:
    print(i, end=&quot; &quot;)</code></pre>
<h1 id="8-문제">8. 문제</h1>
<h2 id="가-백준-1920">가. 백준 1920</h2>
<pre><code class="language-python">import sys

input = sys.stdin.readline


N,K = map(int, input().split())

a=[]

for i in range(N):
    a.append(int(input()))


# 이분탐색

max_num = max(a)

left = 1
right = max_num
ans = 0

while left &lt;= right:
    mid = (left + right) //2
    cnt = 0

    for i in a:
        cnt += i//mid


    if cnt &gt;= K :
        left = mid + 1
        ans = mid

    else:
        right = mid - 1

print(ans)



# 결론
# 1. 더 긴 선의 길이로 얻고자하는 선의 개수만큼 얻을 수 있다면
#   mid보다 큰 값 = left = mid + 1로 탐색을 진행하면 되고

# 2. 얻고자하는 선의 개수를 만족하지 않아, 선을 더 작게 가져가야 한다면
#   mid보다 작은 값 = right = mid - 1로 탐색을 진행해야 한다.


</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[중앙집중식 모니터링]]></title>
            <link>https://velog.io/@halo_3735/%EC%A4%91%EC%95%99%EC%A7%91%EC%A4%91%EC%8B%9D-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@halo_3735/%EC%A4%91%EC%95%99%EC%A7%91%EC%A4%91%EC%8B%9D-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81</guid>
            <pubDate>Tue, 24 Feb 2026 08:48:43 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개요">1. 개요</h1>
<h2 id="11-문서-목적">1.1 문서 목적</h2>
<p>여러 인스턴스로 되어있는 우리 서비스의 트래픽과 각종 지표들을 하나의 모니터링 서버에서 관측해보자</p>
<h2 id="12-목차">1.2 목차</h2>
<p>1) 개요
2) 각 서비스별 매트릭 수집
3) 남은 서비스 매트릭 발산 로직 추가
4) </p>
<h1 id="2-각-서비스별-매트릭-수집">2. 각 서비스별 매트릭 수집</h1>
<h2 id="21-현재-상황">2.1 현재 상황</h2>
<p>현재 프론트와 백엔드는 매트릭을 수집중이고 AI는 진행되고 있지 않다. DB는 로컬에서 매트릭을 수집하였지만 AWS RDS로 변경되어 매트릭을 수집을 추가해야한다.</p>
<h2 id="22-해야할-것">2.2 해야할 것</h2>
<p>현재 RDS와 AI의 매트릭을 수집해야한다.</p>
<h1 id="3-남은-서비스-매트릭-expose-로직-추가">3. 남은 서비스 매트릭 expose 로직 추가</h1>
<h2 id="31-현재-남은-서비스">3.1 현재 남은 서비스</h2>
<p>현재 AI와 RDS 매트릭 발산 로직을 추가해야한다.</p>
<h2 id="32-ai">3.2 AI</h2>
<p><a href="https://github.com/100-hours-a-week/4-team-IMYME-ai/pull/41">AI 서버 성능 지표 측정을 위한 메트릭 expose 구현
</a></p>
<h2 id="33-rds">3.3 RDS</h2>
<h1 id="4-프로메테우스">4. 프로메테우스</h1>
<p>로컬에서 아래 명령어 치고 <code>localhost:9090</code> 으로 접속</p>
<pre><code>aws ssm start-session \
  --target i-0fc15744c6ed54094 \
  --document-name AWS-StartPortForwardingSession \
  --parameters &#39;{&quot;portNumber&quot;:[&quot;9090&quot;],&quot;localPortNumber&quot;:[&quot;9090&quot;]}&#39; \
  --region ap-northeast-2</code></pre><h1 id="5-그라파나">5. 그라파나</h1>
<p>로컬에서 아래 명령어 치고 <code>localhost:3000</code> 으로 접속</p>
<pre><code>aws ssm start-session \
  --target i-0fc15744c6ed54094 \
  --document-name AWS-StartPortForwardingSession \
  --parameters &#39;{&quot;portNumber&quot;:[&quot;3000&quot;],&quot;localPortNumber&quot;:[&quot;3000&quot;]}&#39; \
  --region ap-northeast-2   </code></pre><h2 id="51-문제-1">5.1 문제 1</h2>
<p>현재 126, 49가 ai prod 인스턴스들인데, </p>
<p>ai prod 1 : 10.0.4.126
ai prod 2 : 10.0.3.49</p>
<p>인데, 10.0.4.126 밖이 그라파나에 안나오고 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[시간복잡도에서 "복잡도"의 의미]]></title>
            <link>https://velog.io/@halo_3735/%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84%EC%97%90%EC%84%9C-%EB%B3%B5%EC%9E%A1%EB%8F%84%EC%9D%98-%EC%9D%98%EB%AF%B8</link>
            <guid>https://velog.io/@halo_3735/%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84%EC%97%90%EC%84%9C-%EB%B3%B5%EC%9E%A1%EB%8F%84%EC%9D%98-%EC%9D%98%EB%AF%B8</guid>
            <pubDate>Sun, 22 Feb 2026 07:10:01 GMT</pubDate>
            <description><![CDATA[<h1 id="연산횟수가-늘어난다고-해서-복잡도라는-표현을-쓰는-것이-맞을까">연산횟수가 늘어난다고 해서 복잡도라는 표현을 쓰는 것이 맞을까?</h1>
<p>이건 외국인 즉 시간복잡도, Time Complexity를 이름 붙인 서양인의 관점에서 생각해보아야 한다.</p>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/14b29aab-367b-43bd-a243-f306976eea81/image.png" alt="">
<img src="https://velog.velcdn.com/images/halo_3735/post/bab3bd33-02a3-4cea-ae64-e215123bc804/image.png" alt=""></p>
<p>복잡하다는 감정적인 단어보다 왜 복잡이라는 단어가 나왔는지 이유에 대해 봐야한다. 해당 설명에서는 &quot;여러 부분&quot;이라는 단어가 나왔다. 이 키워드에 집중을 해보자</p>
<h1 id="여러-부분이라는게-뭘-의미하는-걸까">여러 부분이라는게 뭘 의미하는 걸까?</h1>
<p>단순한 순차탐색인 O(n)이라면 하나의 배열에서 모든게 이루어진다. 그러면 하나의 부분일까? 아니다. 해당 배열에서도 연산구조가 여러개로 나뉜다. 바로 루프와 조회 등등이다. </p>
<p>이러한 관점에서 보면 모든 알고리즘은 여러 연산 구조로 이루어져있기 때문에 &quot;복잡도&quot;라는 단어를 쓰는 것이 타당한 것이다.</p>
<p>하지만 왜 하필 &quot;시간&quot;일까.</p>
<h1 id="왜-하필-시간일까">왜 하필 시간일까?</h1>
<p>여러 연산이 걸리는 횟수를 표현한 것이기 때문에 횟수 복잡도가 맞지 않을까? 시간이 먼저는 아니지 않는가. 연산횟수가 적어지면 연산 시간이 줄어들거 아닌가.</p>
<p>과연 그럴까? 연산횟수가 무조건 적은 연산이 연산 시간이 적다고 말할 수 있냐는 말이다. &quot;시간&quot;이라는 단어가 붙은 이유는 이러한 현실적인 예외 속에 있다.</p>
<p>CPU는 항상 같은 연산 횟수를 처리할 때, 동일 연산 시간이 보장되어 지지 않는다. 다양한 변수들이 존재하기 때문이다. 메모리, 유동적인 클럭 등 다양한 이유들이 그 예시이다. 따라서 인간의 관점에서 인간의 문제를 해결할 수 있는데, 사용할 수 있는 인간이 정해놓은 지표인 &quot;시간&quot;을 사용하는 것이다.</p>
<h1 id="그래서-시간-복잡도가-뭔데">그래서 시간 복잡도가 뭔데</h1>
<p>결론은 시간복잡도는 여러부분으로 이루어진 인간이 측정할 수 있는 시간이라는 단위를 사용하여 포장한 &quot;연산 횟수&quot;를 의미한다.</p>
<p>복잡도라는 단어를 쓰는 이유는 여러 연산 로직이 들어가 있어서 이고 시간이라는 단어를 쓰는 이유는 CPU 동일  연산 시간별 동일 연산 횟수를 보장하지 않기 때문에, 인간 입장에서 절대적으로 측정할 수 있는 지표인 시간을 사용한 것이다. (해당 지표를 써서 컴퓨팅 자원 소모를 아낌으로서 비용 및 시간적인 측면에서 아낄 수 있고 이는 회사의 성장으로 이어진다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정리 필요] 부하테스트]]></title>
            <link>https://velog.io/@halo_3735/%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@halo_3735/%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Mon, 09 Feb 2026 06:15:45 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/100-hours-a-week/4-team-IMYME-wiki/wiki/%5BCloud%5D-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%84%A4%EA%B3%84">https://github.com/100-hours-a-week/4-team-IMYME-wiki/wiki/%5BCloud%5D-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%84%A4%EA%B3%84</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mine MVP1 비용 예측 리포트]]></title>
            <link>https://velog.io/@halo_3735/Mine-MVP1-%EB%B9%84%EC%9A%A9-%EC%98%88%EC%B8%A1-%EB%A6%AC%ED%8F%AC%ED%8A%B8</link>
            <guid>https://velog.io/@halo_3735/Mine-MVP1-%EB%B9%84%EC%9A%A9-%EC%98%88%EC%B8%A1-%EB%A6%AC%ED%8F%AC%ED%8A%B8</guid>
            <pubDate>Mon, 09 Feb 2026 05:49:13 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="1-개요"><strong>1. 개요</strong></h1>
<h2 id="11-문서-목적">1.1 문서 목적</h2>
<p>이 문서는 Mine 서비스의 클라우드 운영 비용을 예측하기 위해 작성되었다.</p>
<h2 id="12-mine-서비스-mvp1-범위">1.2 Mine 서비스 MVP1 범위</h2>
<p>2026년 2월 2일부터 3월 2일까지 29일동안 운영된다.</p>
<h2 id="13-mvp1-단계에서-비용-예측이-필요한-이유">1.3 MVP1 단계에서 비용 예측이 필요한 이유</h2>
<p>Mine 서비스는 자체 사내 인프라가 구축되어있지 않고 현재 클라우드 서비스를 사용중이며 해당 서비스를 지출할 돈이 없으면 서버 운영을 할 수가 없는 상황이다. 따라서 해당 서비스에 투자된 1,000,000원보다 운영 비용이 커지면 지속적인 운영이 어려워지고 이러한 상황을 사전에 방지하기 위해 비용 예측을 진행하여 위와 같은 문제를 방지할 예정이다. 이 문서는 위와 같은 목적으로 작성되었다.</p>
<h1 id="2-mvp1-클라우드-구성"><strong>2. MVP1 클라우드 구성</strong></h1>
<h2 id="21-mvp1에서-사용-중인-aws-서비스">2.1 MVP1에서 사용 중인 AWS 서비스</h2>
<ul>
<li>t4g.medium 인스턴스 2개 (dev/prod 각 1개)</li>
<li>EBS 볼륨 30 GiB × 2 (dev/prod 각 1개)</li>
<li>EBS 스냅샷 30 GiB × 2 (dev/prod 각 1개)</li>
<li>퍼블릭 IP 2개 (dev) / 2개 (prod)</li>
<li>데이터 전송 (리전 내)</li>
<li>기타 사용량 유형 없음 (dev/prod 반씩)</li>
</ul>
<h2 id="22-전체-아키텍처-간단-요약">2.2 전체 아키텍처 간단 요약</h2>
<p>현재 운영서버와 프로덕션 서버를 동일한 환경에서 운영중이다. 이에 따른 요금도 동일하게 나온다.</p>
<h1 id="3-mvp1-비용-예측을-위한-기준"><strong>3. MVP1 비용 예측을 위한 기준</strong></h1>
<h2 id="31-고정-비용"><strong>3.1 고정 비용</strong></h2>
<ul>
<li>EC2 인스턴스 비용</li>
<li>EBS 볼륨 기본 프로비저닝 비용</li>
<li>퍼블릭 IP 사용 비용</li>
</ul>
<h2 id="32-변동-비용"><strong>3.2 변동 비용</strong></h2>
<ul>
<li>스토리지 IOPS : 프로비저닝 비용 + 초과비용</li>
<li>스토리지 처리량 : IOPS랑 동일한 형식</li>
<li>EBS 스냅샷: 변경된 블록만 증분식 저장 비용 발생</li>
<li>사용량 유형 없음</li>
</ul>
<blockquote>
<p>주의: 현재 IOPS와 처리량은 무료 범위 내이며, 추후 MAU 증가 시 측정 필요</p>
</blockquote>
<hr>
<h1 id="4-mvp1-기간-동안의-비용-예상"><strong>4. MVP1 기간 동안의 비용 예상</strong></h1>
<h2 id="41-고정-비용"><strong>4.1 고정 비용</strong></h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>비용 (US$)</th>
<th>dev</th>
<th>prod</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>EC2 t4g.medium</td>
<td>13.61</td>
<td>6.805</td>
<td>6.805</td>
<td>인스턴스 사용 비용</td>
</tr>
<tr>
<td>EBS Volume 30 GiB</td>
<td>2.736</td>
<td>2.736</td>
<td>2.736</td>
<td>볼륨 프로비저닝 비용</td>
</tr>
<tr>
<td>InUse Public IP</td>
<td>3.18</td>
<td>1.59</td>
<td>1.59</td>
<td>사용 중인 퍼블릭 IP</td>
</tr>
<tr>
<td><strong>합계</strong></td>
<td>19.526</td>
<td>11.131</td>
<td>11.131</td>
<td>dev/prod 합계</td>
</tr>
</tbody></table>
<h2 id="42-변동-비용"><strong>4.2 변동 비용</strong></h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>비용 (US$)</th>
<th>dev</th>
<th>prod</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>EBS Snapshot 30 GiB</td>
<td>3.00</td>
<td>1.50</td>
<td>1.50</td>
<td>스탠더드 증분식 스냅샷 비용</td>
</tr>
<tr>
<td>데이터 전송(리전 내)</td>
<td>0.87</td>
<td>-</td>
<td>-</td>
<td>리전 내 송수신량에 따른 비용</td>
</tr>
<tr>
<td>사용량 유형 없음</td>
<td>2.98</td>
<td>1.49</td>
<td>1.49</td>
<td>세부 항목 미분류 비용</td>
</tr>
<tr>
<td><strong>합계</strong></td>
<td>6.85</td>
<td>2.99</td>
<td>2.99</td>
<td>dev/prod 합계</td>
</tr>
</tbody></table>
<h2 id="43-비용-총합"><strong>4.3 비용 총합</strong></h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>dev (US$)</th>
<th>prod (US$)</th>
<th>총합 (US$)</th>
</tr>
</thead>
<tbody><tr>
<td>고정 비용</td>
<td>11.131</td>
<td>11.131</td>
<td>22.262</td>
</tr>
<tr>
<td>변동 비용</td>
<td>2.99</td>
<td>2.99</td>
<td>5.98</td>
</tr>
<tr>
<td><strong>총합</strong></td>
<td>14.121</td>
<td>14.121</td>
<td>28.242</td>
</tr>
</tbody></table>
<h1 id="5-추가적인-비용-추적-필요한-사항">5. 추가적인 비용 추적 필요한 사항</h1>
<h2 id="51-필요사항">5.1 필요사항</h2>
<ul>
<li>스토리지 처리량 : 100만 MAU시, 평균 처리량 측정 필요</li>
<li>스토리지 iops : 100만 MAU시, 평균 iops 측정 필요</li>
</ul>
<h2 id="52-추적-방법">5.2 추적 방법</h2>
<p>node_exporter 설치 후 매트릭을 프로메테우스로 수집하여 그라파나로 추적 예정</p>
<h3 id="521-iops">5.2.1 iops</h3>
<ul>
<li><strong>읽기 IOPS:</strong> <code>node_disk_reads_completed_total</code></li>
<li><strong>쓰기 IOPS:</strong> <code>node_disk_writes_completed_tota</code></li>
<li>최종 IOPS = 읽기 IOPS + 쓰기 IOPS = <code>node_disk_reads_completed_total</code>+<code>node_disk_writes_completed_total</code></li>
</ul>
<h3 id="522-처리량">5.2.2 처리량</h3>
<ul>
<li><strong>읽기 바이트:</strong> <code>node_disk_read_bytes_total</code></li>
<li><strong>쓰기 바이트:</strong> <code>node_disk_written_bytes_total</code></li>
<li>최종 바이트 = 읽기 바이트 + 쓰기 바이트 = <code>node_disk_read_bytes_total</code> + <code>node_disk_written_bytes_total</code></li>
</ul>
<h2 id="53-추후-mvp2-비용-산정에-반영">5.3 추후 MVP2 비용 산정에 반영</h2>
<p>해당 수치들을 일일 및 월별로 측정하여 비용산정에 반영 예정</p>
<h1 id="6-비용-감소-가능-항목"><strong>6. 비용 감소 가능 항목</strong></h1>
<h2 id="61-비용-감소-가능-항목-리스트"><strong>6.1 비용 감소 가능 항목 리스트</strong></h2>
<ul>
<li>EC2 로그 S3 이전을 통한 로컬 디스크 사용량 감소</li>
<li>불필요한 EBS 스냅샷 생성 최소화</li>
</ul>
<hr>
<h2 id="62-적용-방법"><strong>6.2 적용 방법</strong></h2>
<h3 id="621-ec2-로그-s3-이전"><strong>6.2.1 EC2 로그 S3 이전</strong></h3>
<ul>
<li>EC2에 쌓이는 로그 파일을 정기적으로 S3로 이동</li>
<li>로컬 디스크 사용량 감소 → EBS 볼륨 추가 확장 비용 절감</li>
<li>장기 보관 로그는 S3 Standard-IA나 Glacier 사용으로 비용 최소화</li>
</ul>
<h3 id="621-스냅샷-생성-주기-조정"><strong>6.2.1 스냅샷 생성 주기 조정</strong></h3>
<ul>
<li>자동 스냅샷 정책을 주기별(예: 주 1회)로 설정</li>
<li>사용하지 않는 스냅샷은 주기적으로 삭제</li>
</ul>
<h1 id="7-정리"><strong>7. 정리</strong></h1>
<h2 id="71-mvp1-운영-가능-여부-판단">7.1 MVP1 운영 가능 여부 판단</h2>
<p>예상 총 운영 비용은 약 28.242 US$로, 계획된 예산 1,000,000원 내에서 충분히 운영 가능하고 고정 비용과 변동 비용 모두 MVP1 수준에서는 큰 변동이 없으며, <strong>스</strong>냅샷 주기 조정과 로그 S3 이전 등 최소한의 비용 최적화로 안정적 운영이 가능하다.</p>
<h2 id="72-100mau-대비-mvp2-확장-시-고려해야-할-비용-요소">7.2 100MAU 대비 MVP2 확장 시 고려해야 할 비용 요소</h2>
<h3 id="721-스토리지-iops-및-처리량-증가">7.2.1 스토리지 IOPS 및 처리량 증가</h3>
<p>MAU 증가로 디스크 읽기와 쓰기 요청량이 증가하면서 EBS 비용이 상승할 가능성이 있으며, 이를 대비해 부하 테스트와 모니터링을 통해 비용을 재고려해야한다.</p>
<h3 id="722-오토스케일링을-위한-aws-로드밸런서-비용-고려">7.2.2 오토스케일링을 위한 AWS 로드밸런서 비용 고려</h3>
<p>멀티 인스턴스 구성 시 ASG와 ALB(Application Load Balancer) 도입이 예정이며, ALB의 시간당 과금과 LCU(Load Balancer Capacity Unit) 기반 변동 비용이 추가로 발생한다. 또한 인스턴스 수 증가에 따른 퍼블릭 IP 비용도 함께 고려해야 한다.</p>
<h3 id="723-데이터베이스-분리-및-관리형-서비스-전환-비용"><strong>7.2.3 데이터베이스 분리 및 관리형 서비스 전환 비용</strong></h3>
<p>현재 EC2 내에서 운영 중인 데이터베이스를 RDS 등 관리형 서비스로 분리할 경우, 인스턴스 비용과 스토리지 비용이 별도로 발생하며 DB 장애 대비 Multi-AZ 구성 시 비용이 약 2배로 증가할 수 있으므로 DB 분리 시점과 스펙을 검토해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지 추가 필요] 서비스 모니터링]]></title>
            <link>https://velog.io/@halo_3735/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B6%94%EA%B0%80-%ED%95%84%EC%9A%94</link>
            <guid>https://velog.io/@halo_3735/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B6%94%EA%B0%80-%ED%95%84%EC%9A%94</guid>
            <pubDate>Fri, 06 Feb 2026 08:01:15 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="1-필요성">1. 필요성</h1>
<p>모니터링은 서버가 문제에 생겼을시, 문제가 어느 지점인지 빠르게 파악하고 해결하기 위해서 진행한다. 예를들어 모니터링의 지표를 확인하여 DB에 병목이 생겨 문제가 생긴 것을 알았다면 쿼리를 최적화를 시켜 개수를 줄이는 방향으로 DB IO 문제를 해결할 수 있다.</p>
<ul>
<li><p>유진 수정</p>
<p>  MINE 서비스는 <strong>음성 입력 → AI 분석 → 피드백 생성</strong>까지 하나의 학습 흐름이 길고,</p>
<p>  각 단계가 <strong>Backend(Spring Boot) · AI(FastAPI) · DB(PostgreSQL)</strong> 에 걸쳐 분산되어 있다.</p>
<p>  이로 인해 장애나 지연이 발생했을 때 단순히 “느리다 / 안 된다” 수준으로는 <strong>문제 지점을 즉시 특정하기 어렵다</strong>는 문제가 있었다.</p>
<p>  따라서 모니터링의 목적은 다음과 같다.</p>
<ul>
<li><p>장애 발생 시 <strong>프론트·백엔드·DB·AI 중 어느 계층이 병목인지 즉시 구분</strong></p>
</li>
<li><p>“CPU 문제인지 / DB 병목인지 / 쿼리 문제인지”를 <strong>지표 기반으로 판단</strong></p>
</li>
<li><p>사용자 학습 흐름(녹음 → 분석 → 피드백)이 <strong>끊기지 않도록 선제 대응</strong></p>
<p>예를 들어,</p>
</li>
<li><p>API 응답 시간이 증가했을 때</p>
<p>  → 백엔드 CPU 사용률은 낮고</p>
<p>  → DB Connection 수와 Cache Miss 비율이 급증한다면</p>
<p>  → <strong>DB 병목으로 판단하고 쿼리/커넥션 풀을 우선 점검</strong>할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<pre><code>즉, 모니터링은 단순 상태 확인이 아니라

“**장애의 원인을 빠르게 좁히기 위한 도구**”로 활용하기 위해 구축하였다.</code></pre><h1 id="2-필요한-데이터-지표-설정">2. 필요한 데이터 지표 설정</h1>
<ul>
<li>모니터링 지표는 다음 기준으로 선정하였다.<ul>
<li><strong>실제 장애 상황에서 원인 분리에 도움이 되는가</strong></li>
<li>각 파트(프론트 / 백엔드 / DB)가 “우리 쪽 문제인가?”를 <strong>지표로 설명할 수 있는가</strong></li>
<li>학습·대결·챌린지처럼 <strong>트래픽이 몰리는 시점의 병목을 파악할 수 있는가</strong></li>
</ul>
</li>
</ul>
<p>특히 MINE은</p>
<ul>
<li>실시간 요청(녹음, PvP)</li>
<li>AI 분석처럼 처리 시간이 긴 작업</li>
</ul>
<p>이 혼합된 구조이기 때문에 단순 트래픽 지표보다 “지연 원인을 설명해주는 지표”를 우선적으로 선택했다.</p>
<h2 id="21-백엔드">2.1 백엔드</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>왜 필요한가</th>
</tr>
</thead>
<tbody><tr>
<td>CPU 사용률</td>
<td>특정 시점에 CPU 사용률이 급증하면</td>
</tr>
<tr>
<td>→ AI 연동 요청 폭증, 비효율적인 로직, 무한 루프 가능성을 의심API 지연이 <strong>서버 연산 문제인지 판단</strong>하기 위한 1차 지표</td>
<td></td>
</tr>
<tr>
<td>JVM 힙 메모리</td>
<td>• AI 결과 처리, 대결 데이터, 피드백 생성 과정에서</td>
</tr>
<tr>
<td>• 객체 생성이 많아 <strong>GC 지연 또는 OOM 가능성</strong> 존재</td>
<td></td>
</tr>
<tr>
<td>• 힙 사용량 추이를 통해 → 메모리 누수 / 객체 정리 실패 여부를 확인</td>
<td></td>
</tr>
<tr>
<td>HikariCP 커넥션 풀</td>
<td>• 커넥션 풀이 고갈되면 → API 응답 대기 → 전체 서비스 지연으로 확산</td>
</tr>
<tr>
<td>• <strong>DB 병목의 전조 신호</strong>로 활용</td>
<td></td>
</tr>
<tr>
<td>Slow Query</td>
<td>• 특정 학습 카드 조회, 대결 기록 조회 시</td>
</tr>
<tr>
<td>• 응답 지연의 원인이 <strong>쿼리 자체인지 판단</strong>“코드 문제 vs DB 문제”를 구분하기 위한 핵심 지표</td>
<td></td>
</tr>
<tr>
<td>- cpu : 어떤 프로세스가 cpu를 많이 잡아먹는지 판단 v</td>
<td></td>
</tr>
<tr>
<td>- jvm 힙 : 힙 오버플로우 방지</td>
<td></td>
</tr>
<tr>
<td>- 히카리시피 풀 : 적절한 커넥션 풀 설정으로 인한 응답 지연 방지</td>
<td></td>
</tr>
<tr>
<td>- 슬로우 쿼리 : 사용자 입장 특정 쿼리 개선 필요</td>
<td></td>
</tr>
</tbody></table>
<h2 id="22-프론트">2.2 프론트</h2>
<ul>
<li>프론트는 “서버는 정상인데 사용자는 느리다고 느끼는 상황”을 판단하기 위해 <strong>체감 성능 지표(Core Web Vitals 중심)</strong>로 선정했다.</li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>FCP(First Contentful Paint)</td>
<td>학습 화면 진입 시 첫 콘텐츠 표시 시점</td>
</tr>
<tr>
<td>LCP(Largest Contentful Paint)</td>
<td>카드/피드백 등 주요 콘텐츠 로딩 체감</td>
</tr>
<tr>
<td>CLS(Cumulative Layout Shift)</td>
<td>피드백 카드 렌더링 중 레이아웃 흔들림 여부</td>
</tr>
<tr>
<td>TBT(Total Blocking Time)</td>
<td>AI 결과 렌더링 시 메인 스레드 블로킹 여부</td>
</tr>
<tr>
<td>SI(Speed Index)</td>
<td>전체 페이지 로딩 체감 속도</td>
</tr>
</tbody></table>
<h2 id="24-db">2.4 DB</h2>
<ul>
<li>DB 지표는 <strong>API 지연 발생 시 원인이 DB인지 여부를 판별하는 핵심 기준</strong>으로 사용한다.</li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th>지표명</th>
<th>왜 필요한가</th>
</tr>
</thead>
<tbody><tr>
<td>DB Connection (연결 수)</td>
<td><code>pg_stat_activity_count</code></td>
<td>• PostgreSQL은 <strong>연결 1개당 프로세스 1개</strong>를 사용</td>
</tr>
<tr>
<td>• 단일 인스턴스 환경에서 커넥션 급증 시 → 메모리 소모 → 백엔드/AI 서버 OOM 위험</td>
<td></td>
<td></td>
</tr>
<tr>
<td>• AI 서버와 백엔드가 동시에 접근하기 때문에<strong>커넥션 수는 가장 우선적으로 모니터링</strong></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Cache Hit Ratio (캐시 적중률)</td>
<td><code>pg_stat_database_blks_hit</code> / <code>pg_stat_database_blks_read</code></td>
<td>• 캐시 적중률이 낮아지면 디스크 I/O 증가→ API 응답 시간 증가 → 학습 흐름 끊김</td>
</tr>
<tr>
<td>• 일반적으로 <strong>99% 이상을 안정 기준</strong>으로 삼아 캐시 효율 저하 시 즉각 원인 분석</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Transaction Throughput (트랜잭션 처리량)</td>
<td><code>pg_stat_database_xact_commit</code>, <code>pg_stat_database_xact_rollback</code></td>
<td>• 서비스 사용량 증가에 따른 DB 부하를 파악 가능</td>
</tr>
<tr>
<td>• 특히 <strong>롤백 비율 급증은 애플리케이션 버그, 락 경합, 타임아웃 문제</strong>의 신호일 수 있음</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="24-ai-서비스">2.4 AI 서비스</h2>
<ul>
<li><a href="https://grafana.com/grafana/dashboards/16110-fastapi-observability/">https://grafana.com/grafana/dashboards/16110-fastapi-observability/</a></li>
<li><a href="https://www.notion.so/2fe1715a156080ea8f0feed05ac8158f?pvs=21">성능지표</a></li>
</ul>
<p>위와같이 문제상황이나 서비스 성능 향상에 필요한 데이터가 필요하다고 판단되었고 해당 데이터 위주로 모니터링 환경 패널들을 구축할 예정이다.</p>
<h1 id="3-도구-선정">3. 도구 선정</h1>
<p>2에서 선정한 리소스에 병목이 혹은 부하가 생기는지 관측하기 위한 매트릭또한 얻어야했고 GUI가 필요하였다.</p>
<h2 id="31-매트릭-수집-도구">3.1 매트릭 수집 도구</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>Prometheus ✅</th>
<th>CloudWatch</th>
<th>Datadog</th>
</tr>
</thead>
<tbody><tr>
<td>CPU / 메모리 / 디스크</td>
<td>O</td>
<td>O</td>
<td>O</td>
</tr>
<tr>
<td>네트워크</td>
<td>O</td>
<td>O</td>
<td>O</td>
</tr>
<tr>
<td>JVM / 힙</td>
<td>O (Micrometer, JMX)</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>DB 슬로우 쿼리</td>
<td>O (Exporter)</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>TPS</td>
<td>O</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>API별 응답 시간</td>
<td>O</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>힙 메모리</td>
<td>O</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>비용</td>
<td>무료(Open Source)</td>
<td>사용량 기반</td>
<td>고비용</td>
</tr>
</tbody></table>
<h2 id="32-gui-도구">3.2 GUI 도구</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>Grafana ✅</th>
<th>Kibana</th>
<th>CloudWatch Dashboard</th>
</tr>
</thead>
<tbody><tr>
<td>메트릭 시각화</td>
<td>O</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>실시간 관측</td>
<td>O</td>
<td>O</td>
<td>△</td>
</tr>
<tr>
<td>프론트 성능 지표 시각화</td>
<td>O</td>
<td>△</td>
<td>△</td>
</tr>
<tr>
<td>클라우드 리소스 관측</td>
<td>O</td>
<td>△</td>
<td>O</td>
</tr>
<tr>
<td>백엔드 지표 관측</td>
<td>O</td>
<td>O</td>
<td>△</td>
</tr>
<tr>
<td>AI/ML 지표 관측</td>
<td>O</td>
<td>△</td>
<td>△</td>
</tr>
<tr>
<td>비용</td>
<td>무료(Open Source)</td>
<td>Elasticsearch 필요</td>
<td>사용량 기반</td>
</tr>
</tbody></table>
<p>결론적으로 프로메테우스와 그라파나를 선정하였다.</p>
<h1 id="4-매트릭-추출">4. 매트릭 추출</h1>
<h2 id="41-백엔드">4.1 백엔드</h2>
<ul>
<li>백엔드 매트릭을 acutrator를 사용하여 수집하였다.</li>
</ul>
<p><img src="attachment:9577f5c2-0d38-4682-8cd9-3a278d53bbeb:image.png" alt="image.png"></p>
<h3 id="411-매트릭-분석-예시">4.1.1 매트릭 분석 예시</h3>
<p>API별 평균 응답시간을 계산해보았다.</p>
<pre><code class="language-bash">http_server_requests_seconds_count{application=&quot;mine&quot;,error=&quot;none&quot;,exception=&quot;none&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/cards&quot;} 23
http_server_requests_seconds_sum{application=&quot;mine&quot;,error=&quot;none&quot;,exception=&quot;none&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/cards&quot;} 0.738544951</code></pre>
<ul>
<li>http_server_requests_seconds_sum : GET <code>/cards</code> API 전체 요청 횟수</li>
<li>http_server_requests_seconds_count : GET <code>/cards</code> API 전체 요청에 걸린 초</li>
</ul>
<p>그라파나 예상 쿼리문은 다음과 같다.</p>
<pre><code class="language-bash">sum by(uri)(http_server_requests_seconds_sum{application=&quot;$app_name&quot;}) / sum by(uri)(http_server_requests_seconds_count{application=&quot;$app_name&quot;})</code></pre>
<p>위 쿼리문을 수학공식으로 표현하면</p>
<p>$$
\text{평균 응답시간}<em>{uri} = \frac{\sum \text{http_server_requests_seconds_sum}</em>{uri}}{\sum \text{http_server_requests_seconds_count}_{uri}}
$$</p>
<aside>
📁

<p><strong>URI = Uniform Resource Identifier (ex. /server/cards)</strong></p>
</aside>

<ul>
<li>API별 평균 응답시간은 API별 전체 응답시간에서 API별 전체 응답 횟수로 나눈 것이다.</li>
<li>예를들어 영미, 철수가 뛰는데 1초, 2초가 걸렸다면 둘이 합친 전체시간이 6초라면 이 두사람의 평균 뛰는 시간은 (1+2)/6 = 0.5인 것이다.</li>
<li>이러한 방식으로 방법으로 API별 평균 응답시간을 구할 수 있고 GUI와 연결하여 시각적으로 확인할 예정이다. 추가적으로 그라파나에서 제공되는 템플릿을 활용할 예정이다.</li>
</ul>
<h3 id="412-백엔드-서비스가-매트릭-던지는-기능-추가">4.1.2 백엔드 서비스가 매트릭 던지는 기능 추가</h3>
<p>해당 매트릭을 Spring Boot에서 얻어내기 위한 방식은 다음과 같다. </p>
<p>먼저 Prometheus 기반 메트릭 수집을 위해 Spring Boot 백엔드에 Actuator 및 Micrometer 설정을 추가하였다. </p>
<p>이를 통해 애플리케이션의 상태 정보와 JVM 및 HTTP 요청 관련 메트릭을 Prometheus가 직접 수집할 수 있도록 구성하였다. </p>
<ol>
<li>Prometheus 메트릭 노출을 위해 Actuator 및 Prometheus Registry 의존성을 추가하였다.</li>
</ol>
<pre><code>// build.gradle
implementation &#39;org.springframework.boot:spring-boot-starter-actuator&#39;
implementation &#39;io.micrometer:micrometer-registry-prometheus&#39;</code></pre><ol>
<li><code>application.yml</code>에 Actuator 엔드포인트 노출 및 Prometheus 메트릭 설정을 추가하였다. <code>/actuator/prometheus</code> 엔드포인트를 통해 메트릭을 노출하고, 애플리케이션 식별을 위한 태그를 함께 설정하였다.</li>
</ol>
<pre><code class="language-yaml"># application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
      base-path: /actuator
  endpoint:
    health:
      show-details: always
    prometheus:
      enabled: true
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: ${spring.application.name}
</code></pre>
<ol>
<li>보안 설정에서는 Prometheus 서버가 인증 없이 메트릭을 수집할 수 있도록 Actuator 관련 엔드포인트를 허용하였다.</li>
</ol>
<pre><code class="language-java">// SecurityConfig.java
.requestMatchers(
    &quot;/actuator/prometheus&quot;,  // Prometheus 메트릭 수집 엔드포인트
    &quot;/actuator/health&quot;,      // 헬스체크
    &quot;/actuator/info&quot;         // 애플리케이션 정보
).permitAll()
</code></pre>
<h3 id="413-결론">4.1.3 결론</h3>
<p>이를 통해 Prometheus가 <code>/actuator/prometheus</code> 엔드포인트를 주기적으로 스크래핑하여</p>
<p>백엔드 애플리케이션의 상태, JVM 리소스, 요청 처리 메트릭을 수집할 수 있도록 구성하였다.</p>
<h2 id="42-프론트">4.2 프론트</h2>
<p>아래 사진을 보면, 매트릭 수집이 정상적으로 수집되는 모습을 볼 수 있다. </p>
<p>해당 매트릭을 수집하기 위해 Next.js 프론트엔드에 Web Vitals 수집 로직을 추가하고, Prometheus가 스크래핑할 수 있는 API 엔드포인트를 구현하였다.</p>
<p><img src="attachment:ba4af53a-7e9a-4659-a3fd-95e31ffb5c07:image.png" alt="image.png"></p>
<h3 id="421--프론트-매트릭-수집-로직-구현">4.2.1  프론트 매트릭 수집 로직 구현</h3>
<p>먼저 클라이언트 단에서는 <code>useReportWebVitals</code> 훅을 활용하여 Web Vitals(FCP, LCP, CLS, INP)를 수집하도록 구성하였다.</p>
<pre><code class="language-tsx">// src/shared/lib/WebVitalsTracker.tsx
&#39;use client&#39;

import { useReportWebVitals } from &#39;next/web-vitals&#39;
import { reportWebVitals as sendMetrics } from &#39;./webVitals&#39;

export function WebVitalsTracker() {
  useReportWebVitals(sendMetrics)
  return null
}
</code></pre>
<p>수집된 매트릭은 <code>sendBeacon</code>을 우선적으로 사용하여 페이지 이탈 시에도 안정적으로 서버에 전송되도록 하였다.</p>
<pre><code class="language-tsx">// src/shared/lib/webVitals.ts
import type { NextWebVitalsMetric } from &#39;next/app&#39;

export function reportWebVitals(metric: NextWebVitalsMetric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
  })

  if (navigator.sendBeacon) {
    navigator.sendBeacon(&#39;/api/metrics&#39;, body)
  } else {
    fetch(&#39;/api/metrics&#39;, {
      method: &#39;POST&#39;,
      body,
      keepalive: true,
    })
  }
}
</code></pre>
<p>서버 측에서는 <code>prom-client</code>의 <code>Histogram</code>을 사용하여 각 Web Vital 지표를 버킷 단위로 집계하였으며, Prometheus가 주기적으로 스크래핑할 수 있도록 GET 엔드포인트를 함께 제공하였다.</p>
<pre><code class="language-tsx">// src/app/api/metrics/route.ts
import { NextResponse } from &#39;next/server&#39;
import { register, Histogram } from &#39;prom-client&#39;

const fcpHistogram = new Histogram({
  name: &#39;nextjs_fcp&#39;,
  help: &#39;First Contentful Paint (in ms)&#39;,
  buckets: [100, 200, 500, 1000, 1500, 2500, 4000],
})

const histograms: Record&lt;string, Histogram&gt; = {
  FCP: fcpHistogram,
}

export async function POST(request: Request) {
  const { name, value } = await request.json()
  histograms[name]?.observe(value)
  return new NextResponse(&#39;Metric received&#39;, { status: 202 })
}

export async function GET() {
  return new NextResponse(await register.metrics(), {
    headers: { &#39;Content-Type&#39;: register.contentType },
  })
}
</code></pre>
<p>마지막으로 <code>layout.tsx</code>에 트래커 컴포넌트를 추가하여 모든 페이지에서 Web Vitals가 자동으로 수집되도록 적용하였다.</p>
<pre><code class="language-tsx">// src/app/layout.tsx
&lt;WebVitalsTracker /&gt;
</code></pre>
<h3 id="422--결론">4.2.2  결론</h3>
<p>이를 통해 Next.js → Prometheus → Grafana 흐름으로 프론트엔드 성능 지표를 시각화할 수 있는 기반을 구축하였다.</p>
<h2 id="43-ai--남는-사람이-진행할-예정">4.3 AI  @남는 사람이 진행할 예정</h2>
<h2 id="44-db">4.4 DB</h2>
<h3 id="441-postgresql-메트릭-수집-아키텍처">4.4.1 <strong>PostgreSQL 메트릭 수집 아키텍처</strong></h3>
<ul>
<li>PostgreSQL 자체는 Prometheus 메트릭을 직접 제공하지 않기 때문에, <strong>postgres-exporter를 통해 내부 통계 뷰(pg_stat_*)를 수집한다.</strong></li>
</ul>
<pre><code class="language-yaml">[ PostgreSQL (host) ]
        ↑
        |  (SQL로 상태 조회)
        |
[ postgres-exporter (container) ]
        ↑
        |  HTTP /metrics (9187)
        |
[ Prometheus (container) ]
        ↑
        |  PromQL query
        |
[ Grafana ]</code></pre>
<h3 id="442-메트릭-추출-방식">4.4.2 메트릭 추출 방식</h3>
<h4 id="1-모니터링-전용-db-계정-생성">1. 모니터링 전용 DB 계정 생성</h4>
<ul>
<li>운영 DB 보안을 위해 <strong>읽기 전용 모니터링 계정</strong>을 생성한다.</li>
<li>애플리케이션 계정과 분리하여 <strong>권한 최소화 원칙</strong>을 적용한다.</li>
</ul>
<pre><code class="language-yaml"># 모니터링 전용 유저 생성
CREATE USER &lt;user&gt; WITH PASSWORD &#39;&lt;pw&gt;&#39;;

# 시스템 통계 및 설정 읽기 권한 부여
GRANT pg_monitor TO &lt;user&gt;;

# 특정 DB(mine_project_db) 접속 권한 명시
GRANT CONNECT ON DATABASE mine_project_db TO &lt;user&gt;;
GRANT CONNECT ON DATABASE postgres TO &lt;user&gt;;</code></pre>
<h4 id="2-postgresql-설정-확인-및-수정">2. PostgreSQL 설정 확인 및 수정</h4>
<ul>
<li>Postgres가 외부(컨테이너) 접근을 허용하도록 설정한다.</li>
<li><code>pg_hba.conf</code>에 들어가는 네트워크 대역은 아래 명령어로 확인할 수 있다.<ul>
<li>docker compose exec postgres-exporter sh -lc &quot;ip a; echo &#39;---&#39;; ip route&quot;</li>
</ul>
</li>
</ul>
<pre><code class="language-yaml"># postgresql.conf
listen_addresses = &#39;*&#39;

# pg_hba.conf
host    all    monitor    &lt;Docker 컨테이너 네트워크 대역&gt;    md5</code></pre>
<h4 id="3-방화벽ufw-설정">3. 방화벽(UFW) 설정</h4>
<ul>
<li>우리 서버에 UFW 방화벽이 적용되어 있어, Docker 네트워크 접근을 명시적으로 허용한다.</li>
<li>해당 설정이 없을 경우 <strong>Exporter는 접속 시도는 하나 타임아웃으로 실패</strong>한다.</li>
</ul>
<pre><code class="language-yaml">sudo ufw allow from &lt;Docker 컨테이너 네트워크 대역&gt; to any port 5432 proto tcp</code></pre>
<h4 id="4-postgres-exporter-구성">4. postgres-exporter 구성</h4>
<ul>
<li>Docker Compose를 통해 postgres-exporter를 실행한다.</li>
<li>Exporter는 PostgreSQL 내부 통계 정보를 주기적으로 조회하여 <code>/metrics</code> 엔드포인트로 노출한다.</li>
</ul>
<pre><code class="language-yaml">  postgres-exporter:
    image: prometheuscommunity/postgres-exporter
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    ports:
      - &quot;9187:9187&quot;
    environment:
      DATA_SOURCE_NAME: &quot;postgresql://user:pw@host.docker.internal:5432/postgres?sslmode=disable&quot;
      WEB_LISTEN_ADDRESS: &quot;:9187&quot;
    depends_on:
      - prometheus
    networks:
      - monitoring</code></pre>
<h1 id="5-prometheus-기반-메트릭-수집">5. Prometheus 기반 메트릭 수집</h1>
<aside>
💡

<p>promethues.yml 코드 추가 필요</p>
</aside>

<h2 id="51-백엔드">5.1 백엔드</h2>
<p>백엔드(Spring Boot)가 <code>/actuator/prometheus</code>엔드포인트로 메트릭을 노출하고, Prometheus가 해당 엔드포인트를 주기적으로 scrape하여 메트릭을 수집하는 것을 확인하였다.</p>
<p><img src="attachment:116665c9-cc07-4117-9eb4-e38139544710:image.png" alt="image.png"></p>
<h2 id="52-프론트">5.2 프론트</h2>
<p>Prometheus가 Next.js에서 노출한 <code>/api/metrics</code> 엔드포인트를 통해 프론트엔드 메트릭을 정상적으로 수집하는 것을 확인하였다.</p>
<p><img src="attachment:213e4622-7f41-4eda-8bed-d17cb3516dd8:image.png" alt="image.png"></p>
<h2 id="53-ai">5.3 AI</h2>
<h2 id="54-db">5.4 DB</h2>
<p>PostgreSQL은 postgres-exporter를 통해 메트릭을 노출하고, Prometheus가 이를 수집하여 DB 상태 지표를 모니터링한다.</p>
<p><img src="attachment:33bc6959-c2fd-4ebf-8f01-5d3081a6dd1d:image.png" alt="image.png"></p>
<h1 id="6-서비스-별-모니터링-구축">6. 서비스 별 모니터링 구축</h1>
<h2 id="61--백엔드">6.1  백엔드</h2>
<aside>
❓

<p><strong>사용법</strong> : <a href="http://43.200.201.50:3001/d/admdjqm/mine-dashboard?var-pg_interval=10m&amp;orgId=1&amp;from=2026-02-05T04:58:05.313Z&amp;to=2026-02-05T05:14:21.062Z&amp;timezone=browser&amp;var-application=mine&amp;var-instance=172.17.0.1:443&amp;var-hikaricp=HikariPool-1&amp;var-namespace=&amp;var-memory_pool_heap=$__all&amp;var-memory_pool_nonheap=$__all&amp;var-jvm_memory_pool_heap=$__all&amp;var-jvm_memory_pool_nonheap=$__all&amp;var-version=&amp;var-pg_instance=&amp;var-Database=$__all&amp;var-query0=&amp;var-log_keyword=&amp;var-app_name=mine&amp;refresh=5s">http://43.200.201.50:3001/d/admdjqm/mine-dashboard?var-pg_interval=10m&amp;orgId=1&amp;from=2026-02-05T04:58:05.313Z&amp;to=2026-02-05T05:14:21.062Z&amp;timezone=browser&amp;var-application=mine&amp;var-instance=172.17.0.1:443&amp;var-hikaricp=HikariPool-1&amp;var-namespace=&amp;var-memory_pool_heap=$__all&amp;var-memory_pool_nonheap=$__all&amp;var-jvm_memory_pool_heap=$__all&amp;var-jvm_memory_pool_nonheap=$__all&amp;var-version=&amp;var-pg_instance=&amp;var-Database=$__all&amp;var-query0=&amp;var-log_keyword=&amp;var-app_name=mine&amp;refresh=5s</a> ← 들어가서 <strong>BE</strong> 섹션 클릭</p>
</aside>

<h3 id="611-백엔드-grafana-대시보드-구성">6.1.1 백엔드 Grafana 대시보드 구성</h3>
<p><img src="attachment:9d37f2e1-83df-4b81-b144-ad17a648d039:image.png" alt="image.png"></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>항목</th>
<th>내용</th>
<th>단위/비고</th>
</tr>
</thead>
<tbody><tr>
<td><strong>1. BE 서비스 CPU 사용량</strong></td>
<td>System CPU Usage</td>
<td>OS 전체 CPU 사용률</td>
<td>%</td>
</tr>
<tr>
<td></td>
<td>Process CPU Usage</td>
<td>백엔드 프로세스 CPU 사용률</td>
<td>%</td>
</tr>
<tr>
<td></td>
<td>Mean / Last / Max / Min</td>
<td>평균 / 마지막 / 최대 / 최소 사용률</td>
<td>%</td>
</tr>
<tr>
<td><strong>2. API별 평균 응답 속도</strong></td>
<td>API 엔드포인트</td>
<td>각 API의 평균 응답 시간</td>
<td>ms / s</td>
</tr>
<tr>
<td></td>
<td>색상 의미</td>
<td>초록 → 빨강 : 빠름 → 느림</td>
<td>시각화 참고</td>
</tr>
<tr>
<td><strong>3. JVM Heap</strong></td>
<td>used</td>
<td>현재 사용 중인 Heap</td>
<td>MiB</td>
</tr>
<tr>
<td></td>
<td>committed</td>
<td>JVM이 확보한 Heap</td>
<td>MiB</td>
</tr>
<tr>
<td></td>
<td>max</td>
<td>JVM 설정 최대 Heap</td>
<td>MiB</td>
</tr>
<tr>
<td><strong>4. 히카리CP 커넥션 풀 사용량</strong></td>
<td>Active</td>
<td>현재 사용 중인 연결</td>
<td>개수</td>
</tr>
<tr>
<td></td>
<td>Idle</td>
<td>풀에서 대기 중인 연결</td>
<td>개수</td>
</tr>
<tr>
<td></td>
<td>Pending</td>
<td>연결 요청 대기</td>
<td>개</td>
</tr>
</tbody></table>
<h3 id="612-상세보기">6.1.2 상세보기</h3>
<p><img src="attachment:fb0c9729-be05-4489-ac2d-0a1f17f4da63:image.png" alt="image.png"></p>
<p>백엔드 그라파나 전체보기에서 “다른 지표들 더보기 링크”에 들어가면 여러 리소스 상태를 관측할 수 있도록 설정했다.</p>
<ul>
<li>각 지표에는 <strong>description과 해석 기준을 함께 기재</strong>하여, 해당 지표가 어떤 것을 의미하는지를 바로 알 수 있디.</li>
</ul>
<p><img src="attachment:66575170-f601-40e2-a422-245418ee89ca:image.png" alt="image.png"></p>
<h2 id="62-프론트---halowon원현섭">6.2 프론트 - @Halo.won(원현섭)</h2>
<h3 id="621-프론트-grafana-대시보드-구성">6.2.1 프론트 Grafana 대시보드 구성</h3>
<p><img src="attachment:7caa2d3a-36a7-4d59-93fd-e8839a7af9de:image.png" alt="image.png"></p>
<p>좌측에는 현재 측정되고 있는 평균값들을 나타내게 하였고 우측에는 지속해서 시간별로 변하는 평균값들을 그래프로 표현하게 하여 프론트 팀원이 시간대별로 해당 지표를 확인하고 성능 개선에 도움이 되게 구성하였다.</p>
<h3 id=""></h3>
<h2 id="63-db">6.3 DB</h2>
<h3 id="631-db-grafana-대시보드-구성">6.3.1 DB Grafana 대시보드 구성</h3>
<ul>
<li><strong>2.4 DB에서 정의한 핵심 지표</strong>를 중심으로, DB 병목 지점을 판단하는 데 필요한 추가 지표들을 함께 수집하여 Grafana 대시보드로 시각화하였다.</li>
<li>각 지표에는 <strong>description과 해석 기준을 함께 기재</strong>하여, DB에 익숙하지 않은 작업자도 “정상 / 이상 상태”를 빠르게 판단할 수 있도록 구성하였다.</li>
<li>본 대시보드는 <strong>API 응답 지연 발생 시, DB가 원인인지 여부를 빠르게 판단하는 용도</strong>로 사용한다.</li>
</ul>
<p><img src="attachment:7da87b69-98b1-4158-93ca-72458d820747:image.png" alt="image.png"></p>
<p><img src="attachment:4ae3f35d-ed71-4525-9cfa-a6c4925a6d36:image.png" alt="image.png"></p>
<h3 id="632-db-병목-판단을-위한-주요-지표-정리">6.3.2 DB 병목 판단을 위한 주요 지표 정리</h3>
<ul>
<li>2.4 DB에서 정의한 지표를 위의 대시보드에서 찾아보면 아래와 같이 정리할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>항목</th>
<th>내용</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><strong>1. DB Connection</strong></td>
<td>Active Sessions</td>
<td>현재 DB에 연결되어 있는 활성 세션(연결) 수</td>
<td>Max Connections에 근접하면 신규 요청이 대기 상태가 되며, API 응답 지연 또는 서비스 중단으로 이어질 수 있음</td>
</tr>
<tr>
<td></td>
<td>Max Connections</td>
<td>DB가 허용하는 최대 동시 연결 수</td>
<td>단일 인스턴스 환경에서 커넥션 수는 메모리 사용량과 직결됨</td>
</tr>
<tr>
<td><strong>2. Cache Hit Ratio</strong></td>
<td>Cache Hit Rate</td>
<td>요청한 데이터가 메모리(Buffer Cache)에서 바로 조회된 비율</td>
<td>일반적으로 <strong>99% 이상을 안정 상태</strong>로 판단</td>
</tr>
<tr>
<td></td>
<td></td>
<td>디스크 I/O가 발생할수록 API 응답 시간이 증가</td>
<td>캐시 적중률 하락 시 쿼리 또는 인덱스 점검 대상</td>
</tr>
<tr>
<td><strong>3. Transaction Throughput</strong></td>
<td>Transactions (commit / rollback)</td>
<td>초당 처리되는 트랜잭션 수 및 성공(commit) / 실패(rollback) 비율</td>
<td>rollback 비율이 높을 경우 애플리케이션 에러, 타임아웃, 락 경합을 의심</td>
</tr>
<tr>
<td><strong>4. Idle Sessions</strong></td>
<td>Idle / Idle in transaction</td>
<td>DB에 연결은 되어 있으나 실제 쿼리를 수행하지 않는 세션</td>
<td>idle in transaction 상태가 지속되면 커넥션 고갈 위험</td>
</tr>
<tr>
<td><strong>5. Lock / Deadlock</strong></td>
<td>Lock tables / Deadlocks</td>
<td>테이블 또는 행 단위 락 발생 여부</td>
<td>특정 시점에 급증할 경우 동시성 문제 또는 잘못된 트랜잭션 설계 가능성</td>
</tr>
</tbody></table>
<h1 id="7-트러블-슈팅">7. 트러블 슈팅</h1>
<h2 id="71--프롬테일-과다-cpu-점유-문제">7.1  프롬테일 과다 CPU 점유 문제</h2>
<h3 id="711-문제점">7.1.1 문제점</h3>
<p>현재 EC2 CPU</p>
<table>
<thead>
<tr>
<th>이름</th>
<th>vCPU 코어수</th>
<th>램</th>
</tr>
</thead>
<tbody><tr>
<td>t4g.medium</td>
<td>2</td>
<td>4G</td>
</tr>
<tr>
<td>- 1차 테스트</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<pre><code>![image.png](attachment:3af5202d-86f4-4ede-89e8-329d27d71b00:image.png)</code></pre><ul>
<li><p>2차 테스트</p>
<p>  <img src="attachment:8a0e0eff-c589-49c7-912b-4813b3416424:image.png" alt="image.png"></p>
</li>
<li><p>3차 테스트</p>
<p>  <img src="attachment:5dfe0d39-2b04-4e6f-9be3-d0ecaed071b3:image.png" alt="image.png"></p>
</li>
</ul>
<p>프롬테일이 CPU는 코어 하나 기준 35%이상 잡아먹는 상황 발생하였고 램도 초반에 컨테이너가 띄워질때는 전체의 42%를 잡아먹는 현상이 발생하였다. 4G기준 42%면 1.8G라고 하였을 때, 기본 OS 메모리를 생각하면 4G까지 생각하여야 했다. 따라서 기존 프로덕션 서버 인스턴스와 동일한 , vcpu 2코어 및 Ram 4G, t4g.medium을 선정했다.</p>
<h3 id="712--원인-파악">7.1.2  원인 파악</h3>
<p>큰 용량의 로그파일을 읽는 프롬테일 프로세스 CPU 과다 점유 문제였다.</p>
<table>
<thead>
<tr>
<th>프로세스</th>
<th>cpu(%)</th>
<th>메모리(G)</th>
</tr>
</thead>
<tbody><tr>
<td>로키1</td>
<td>15.6</td>
<td>34 (1.36G)</td>
</tr>
<tr>
<td>프롬테일 (4개의 프로세스 총합)</td>
<td>78</td>
<td>4</td>
</tr>
</tbody></table>
<p>현재 프롬테일이 78%나 CPU를 먹고있는 상황이라 해당 문제의 원인을 파악할 필요가 있었고 그 문제를 아래 사진의 로그 파일에서 찾았다. 따라서 로그파일을 몇일 주기로 저장해서 프롬테일이 읽을 로그 파일 크기를 줄일지 결정할 필요성이 있었다.</p>
<p><img src="attachment:64f82f19-aea8-4e92-9891-b2684ab69803:image.png" alt="image.png"></p>
<p>현재 프롬테일이 읽고 있는 백엔드 로그가 1.8G라는것을 확인하였고 프롬테일 시작시 파일 끝 위치 계산하고 stack_trace가 제한이 8192mb라 읽는 비용이 많이들고 positions이 존재하지 않아 해당 큰 로그파일을 한번에 읽는데 많은 CPU를 사용하게 된다는 문제점 또한 파악하였다.</p>
<h3 id="713-해결-방법">7.1.3 해결 방법</h3>
<p>로그를 하루 단위로 날짜이름으로 저장하기로 결정하였다.  실시간으로 하루의 로그를 프롬테일이 읽어서 실시간 로그를 그라파나를 통해서 개발자들에게 제공하기로 하였고 하루단위로 따로 저장해서 개발자들에게 제공하기로 결정하였다. 하루로 정한 이유는 날짜별로 관리하기 쉽게 하기 위해서였다.</p>
<p>cron + logrotate 조합을 사용하여 일단위로 명령을 실행하게 하였고 logrotage로 로그를 잘랐다.</p>
<ul>
<li><p><strong>logrotate 설정 파일 생성</strong></p>
<pre><code class="language-bash">  sudo vi /etc/logrotate.d/backend</code></pre>
</li>
</ul>
<ul>
<li><p>작성</p>
<pre><code class="language-bash">  /home/ubuntu/mine/backend/shared/logs/backend.log {
      daily
      rotate 14
      missingok
      notifempty
      dateext
      dateformat -%Y%m%d
      olddir /home/ubuntu/mine/backend/shared/logs/save_logs
      copytruncate
      compress
  }
</code></pre>
</li>
</ul>
<ul>
<li><p>테스트</p>
<pre><code class="language-bash">  sudo logrotate -f /etc/logrotate.d/backend
</code></pre>
</li>
</ul>
<ul>
<li><p>결과</p>
<p>  <img src="attachment:353c5b6e-7cb2-46eb-89c3-cf0ff12840f7:image.png" alt="image.png"></p>
</li>
</ul>
<p>위 결과 사진처럼 로그파일이 날짜이름으로 분리되는 것을 확인하였다.</p>
<h3 id="714-결과">7.1.4 결과</h3>
<p><img src="attachment:87845e26-3270-41fa-99e3-4bffe27061ba:image.png" alt="image.png"></p>
<p>그 결과 프롬테일이 CPU를 0.7% 그리고 메모리를 2.1% 사용하고 로키 또한 0.7% 그리고 2.1%씩 차지하는 것을 확인할 수 있었다.</p>
<p><img src="attachment:e5771267-2bc6-45ca-a3d3-fbff60433687:image.png" alt="image.png"></p>
<p>추가적으로  2026년 2월 4일에 52M의 로그가 쌓인 것을 확인가능하였고 계속해서 크기를 추적하여 인스턴스 디스크 용량 산정에 반영할 예정이다.</p>
<h2 id="72-방화벽-설정으로-인한-postgresql-메트릭-수집-실패">7.2 방화벽 설정으로 인한 PostgreSQL 메트릭 수집 실패</h2>
<h3 id="721-문제점">7.2.1 문제점</h3>
<ul>
<li><p>Grafana에서 PostgreSQL 대시보드가 <strong><code>No data</code></strong> 로 표시되었다.</p>
</li>
<li><p>Prometheus Targets 페이지에서는 <code>postgres-exporter</code>가 UP 상태이나</p>
<p>  실제 DB 관련 메트릭(<code>pg_stat_activity_count</code> 등)이 수집되지 않았다.</p>
</li>
<li><p><code>postgres-exporter</code> 로그에서 타임아웃 오류 반복 발생했다.</p>
</li>
</ul>
<h3 id="722-원인-파악">7.2.2 원인 파악</h3>
<ul>
<li>exporter 컨테이너에서 DB 호스트(<code>host.docker.internal:5432</code>)로의 네트워크 연결 <strong>타임아웃</strong> 발생하는것으로 보아 서버에 설정된 UFW 방화벽문제라는 것을 알았다.<ul>
<li>Docker bridge 네트워크(&lt;Docker 컨테이너 네트워크 대역&gt;, <code>docker0</code>)에서 들어오는 트래픽이 PostgreSQL 포트(5432)에 대해 허용되지 않아 exporter → DB 연결이 차단되었다.</li>
</ul>
</li>
</ul>
<h3 id="723-해결방법">7.2.3 해결방법</h3>
<ul>
<li><p>Docker 네트워크 대역에서 PostgreSQL 포트 접근 허용했더니 문제가 해결되었다.</p>
<pre><code class="language-yaml">  sudo ufw allow from &lt;Docker 컨테이너 네트워크 대역&gt; to any port 5432 proto tcp</code></pre>
</li>
<li><p>Prometheus → postgres-exporter → PostgreSQL 연결 정상화 확인</p>
<ul>
<li><code>/metrics</code> 엔드포인트에서 <code>pg_stat_*</code> 메트릭 노출</li>
<li>Grafana PostgreSQL 대시보드에 메트릭 정상 표시</li>
</ul>
</li>
</ul>
<h2 id="73-프로메테우스-컨테이너에서-nextjsmetrics에-접근이-안되는-문제">7.3 프로메테우스 컨테이너에서 nextjs/metrics에 접근이 안되는 문제</h2>
<h3 id="731-문제점">7.3.1 문제점</h3>
<p><code>curl [http://localhost:3000/api/metrics](http://172.31.39.74:3000/api/metrics)</code> 실행시, 현재 아래와 같은 매트릭을 반환하고 있다.</p>
<pre><code class="language-bash"># HELP nextjs_fcp First Contentful Paint (in ms)

# TYPE nextjs_fcp histogram

nextjs_fcp_bucket{le=&quot;100&quot;} 4
nextjs_fcp_bucket{le=&quot;200&quot;} 20
nextjs_fcp_bucket{le=&quot;500&quot;} 25
nextjs_fcp_bucket{le=&quot;1000&quot;} 36
nextjs_fcp_bucket{le=&quot;1500&quot;} 36
nextjs_fcp_bucket{le=&quot;2500&quot;} 38
nextjs_fcp_bucket{le=&quot;4000&quot;} 40
nextjs_fcp_bucket{le=&quot;+Inf&quot;} 40
nextjs_fcp_sum 21670.300000071526
nextjs_fcp_count 40

# HELP nextjs_lcp La</code></pre>
<p>하지만 현재 프로메테우스가 컨테이너로 실행되고 있고 어째서인지 로컬호스트에서 실행중인 nextjs가 주는 매트릭에 접근이 안된다.</p>
<pre><code class="language-bash">- job_name: &#39;nextjs-frontend&#39;
    static_configs:
      - targets: [&#39;172.31.39.74:3000&#39;]  # EC2면 자체 IP 사용 가능
    metrics_path: &#39;/api/metrics&#39;</code></pre>
<p>현재 nextjs/metrics에 접근하는 prometheus.yml파일은 위와 같다.</p>
<h3 id="732-시도">7.3.2 시도</h3>
<ol>
<li>첫번째 시도 - 실패</li>
</ol>
<pre><code class="language-bash">- targets: [&#39;172.31.39.74:3000&#39;]  # EC2면 자체 IP 사용 가능</code></pre>
<ol>
<li>두번째 시도 -  실패</li>
</ol>
<pre><code class="language-bash"> - targets: [&#39;172.17.0.1:3000&#39;] </code></pre>
<p><img src="attachment:ea4c7752-9946-4ab6-ab00-5e72804d67b1:image.png" alt="image.png"></p>
<ol>
<li>세번째 시도 - 실패</li>
</ol>
<pre><code class="language-bash"> - targets: [&#39;localhost:3000&#39;] </code></pre>
<p><img src="attachment:f0a56f15-e8ff-488f-8b20-647efb65d16d:image.png" alt="image.png"></p>
<h3 id="733-해결-방법">7.3.3 해결 방법</h3>
<hr>
<h3 id="서버에-있는-파일-모니터링-코드-원본">서버에 있는 파일 모니터링 코드 원본</h3>
<ul>
<li><p>docker-compose.yml</p>
<pre><code class="language-yaml">  services:
    loki:
      image: grafana/loki:latest
      ports:
        - &quot;3100:3100&quot;
      command: -config.file=/etc/loki/local-config.yaml
      volumes:
        - loki-data:/loki
      networks:
        - monitoring

    promtail:
      image: grafana/promtail:latest
      volumes:
        - /home/ubuntu/mine/backend/shared/logs:/logs
        - /home/ubuntu/loki-setup/promtail-config.yml:/etc/promtail/config.yml
      command: -config.file=/etc/promtail/config.yml
      networks:
        - monitoring

    prometheus:
      image: prom/prometheus:latest
      ports:
        - &quot;9090:9090&quot;
      volumes:
        - /home/ubuntu/loki-setup/prometheus.yml:/etc/prometheus/prometheus.yml
        - prometheus-data:/prometheus
      command:
        - &#39;--config.file=/etc/prometheus/prometheus.yml&#39;
      networks:
        - monitoring

    grafana:
      image: grafana/grafana:latest
      ports:
        - &quot;3001:3000&quot;
      environment:
        - GF_SECURITY_ADMIN_PASSWORD=admin
      volumes:
        - grafana-data:/var/lib/grafana
      networks:
        - monitoring

    postgres-exporter:
      image: prometheuscommunity/postgres-exporter
      ports:
        - &quot;9187:9187&quot;
      environment:
        DATA_SOURCE_NAME: &quot;postgresql://mine:305dadd728bc1dc07c1a0bde523dba47@mine-db:5432/mine_project_db?sslmode=disable&quot;
        WEB_LISTEN_ADDRESS: &quot;:9187&quot;
      depends_on:
        - prometheus
      networks:
        - monitoring

  volumes:
    grafana-data:
    loki-data:
    prometheus-data:

  networks:
    monitoring:
      driver: bridge</code></pre>
</li>
<li><p>prometheus.yml</p>
<pre><code class="language-yaml">  global:
    scrape_interval: 15s

  scrape_configs:
    - job_name: &#39;prometheus&#39;
      static_configs:
        - targets: [&#39;localhost:9090&#39;]

    - job_name: &#39;loki&#39;
      static_configs:
        - targets: [&#39;loki:3100&#39;]

    - job_name: &#39;promtail&#39;
      static_configs:
        - targets: [&#39;promtail:9080&#39;]

    - job_name: &#39;postgres&#39;
      static_configs:
        - targets: [&#39;postgres-exporter:9187&#39;]

    - job_name: &#39;backend&#39;
      static_configs:
        - targets: [&#39;172.17.0.1:443&#39;]
      scheme: https
      metrics_path: &#39;/server/actuator/prometheus&#39;
      tls_config:
        insecure_skip_verify: true
</code></pre>
</li>
<li><p>promtail-config.yml</p>
<pre><code class="language-yaml">  server:
    http_listen_port: 9080

  positions:
    filename: /tmp/positions.yaml

  clients:
    - url: http://loki:3100/loki/api/v1/push

  scrape_configs:
    - job_name: backend
      static_configs:
        - targets:
            - localhost
          labels:
            job: backend
            app: mine
            __path__: /logs/backend.log

      pipeline_stages:
        - json:
            expressions:
              timestamp: timestamp
              level: level
              message: message
              logger: logger
              path: path
              method: method
              clientIp: clientIp
              status: status
              userId: userId
              stack_trace: stack_trace

        - labels:
            level:
            logger:
            path:
            method:
            status:
            userId:

        - timestamp:
            source: timestamp
            format: RFC3339</code></pre>
</li>
</ul>
<h1 id="8-병목-지점-파악-사례">8. 병목 지점 파악 사례</h1>
<h2 id="81-상황-발생">8.1 상황 발생</h2>
<ul>
<li>서비스 오픈 이후, <strong>일부 사용자 요청에서 간헐적인 오류가 발생</strong></li>
<li>초기에는 일시적인 네트워크 문제로 판단했으나, 동일 시점에 오류 로그가 반복적으로 발생하여 원인 분석을 진행했다.</li>
</ul>
<h2 id="82-백엔드-로그-기반-이상-징후-확인">8.2 백엔드 로그 기반 이상 징후 확인</h2>
<ul>
<li>백엔드 로그 확인 결과, 특정 시점에 <strong>카드 생성 API (<code>POST /server/cards</code>) 요청이 비정상적으로 집중</strong>되고 있었다.</li>
<li>다수의 요청이 짧은 시간 안에 반복적으로 유입되며, 일부 요청은 정상 처리되지 못하고 실패(499 등)로 종료되는 현상이 확인되었다 → 단순 요청 실패가 아닌, <strong>트래픽 집중으로 인한 병목 가능성</strong>을 의심했다.</li>
</ul>
<p><img src="attachment:1d2ac96c-d73b-415a-bdc3-bb4dcd11157c:0205_%E1%84%87%E1%85%AE%E1%84%92%E1%85%A1%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%B5%E1%86%B8_%E1%84%87%E1%85%A2%E1%86%A8%E1%84%8B%E1%85%A6%E1%86%AB%E1%84%83%E1%85%B3_499%E1%84%85%E1%85%A9%E1%84%80%E1%85%B3.png" alt="0205 부하주입 백엔드 499로그.png"></p>
<h2 id="83-모니터링-지표-기반-교차-분석">8.3 모니터링 지표 기반 교차 분석</h2>
<p>로그만으로는 원인을 단정할 수 없기 때문에, 기존에 구축해 둔 <strong>모니터링 지표를 함께 확인</strong>했다.</p>
<h3 id="831-백엔드-관점-분석---halowon원현섭">8.3.1 백엔드 관점 분석 - @Halo.won(원현섭)</h3>
<ol>
<li>문제점</li>
</ol>
<p>가. 트래픽 부하시, 응답속도 저하 발생</p>
<p><img src="attachment:a584c962-33b4-4955-ba4a-c0e55b7e5553:image.png" alt="image.png"></p>
<p><img src="attachment:6ace1182-b43b-4808-b73e-cc7b2e856f3f:image.png" alt="image.png"></p>
<p>운영서버 평균 응답속도가 0.4ms로 관측되지만, 부하시에는 최대 약 5초까지 나오는 것을 확인할 수 있었다. 해당이유를 아래 2가지 경우에서 찾아 볼 수 있었다.</p>
<p>나. CPU 코어 수 부족</p>
<p><img src="attachment:65897550-a826-4499-92a7-b5ee156dd264:image.png" alt="image.png"></p>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Load Average[1m]</td>
<td>1분동안 CPU에 실행중인 task와 대기중이던 task의 합의 평균</td>
</tr>
<tr>
<td>Cpu Core Size</td>
<td>연산가능한 vCPU 개수</td>
</tr>
</tbody></table>
<p>CPU가 처리해야하는 실행가능한 작업(톰캣 워커 스레드, DB 쿼리 실행중인 비즈니스 로직 스레드 등)은 하나의 CPU에 할당되어야 처리가 된다. 하지만 위와 같은 지표에서 부하가 걸렸을 시, 특정 스레드들을 cpu가 바로 처리가 못하는 상황에서 병목이 생겼였다</p>
<p><img src="attachment:9bf1754f-336f-4db8-afa7-7f3484ef6b2f:image.png" alt="image.png"></p>
<p>최고치는 17:57:15 시각에 16의 실행가능한 작업들이 남아있었고 cpu를 바로 교체할 수 없기에 해당 문제들을 해결할 수 있는 방법을  지표를 활용해 찾을 예정이다.</p>
<ul>
<li><p>x</p>
<p>  <img src="attachment:18a59a87-2b90-4a65-9e9f-0ccf7a43fcd8:image.png" alt="image.png"></p>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Used</td>
<td>현재 실제로 사용 중인 힙 메모리</td>
</tr>
<tr>
<td>Commited</td>
<td>jvm이 os로 부터 할당받은 메모리 크기</td>
</tr>
</tbody></table>
<p>  위 사진은 17:57:15 시각에 힙 중에서 eden에서 살아남은 데이터들이 저장되는 survivor space이다. 사용가능한 힙메모리가 0.5mib 남지않고 그 뒤에는 10mib를 풀로써 어 근데 이게 병목 이유가되나</p>
</li>
</ul>
<ol>
<li>해결 방안</li>
</ol>
<p>가. HikariCP  늘리기</p>
<p><img src="attachment:1b041a06-3313-43b2-94ee-0079890b41f3:image.png" alt="image.png"></p>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Active</td>
<td>현재 애플레케이션에서 사용중인 커넥션 수</td>
</tr>
<tr>
<td>idle</td>
<td>스레드에 할당 가능한 커넥션 수</td>
</tr>
<tr>
<td><strong>pending</strong></td>
<td>커넥션 할당 대기중인 스레드 수</td>
</tr>
</tbody></table>
<p>위 그림과 표에 따르면 할당가능한 커넥션수(idle)는 10개를 넘어가지 않는데 커넥션 할당 대기중인 스레드 수(pending)가 특정 시점 188까지 오르는 것으로 보인다. 해당 시점에서 스레드는 커넥션을 할당받지 못해 DB 접근을 못하게 되고 해당 과정에서 병목이 생기는 것을 알 수 있었다.</p>
<p>커넥션 객체 증가에 대한 JVM 힙 메모리 할당 트레이드 오프를 생각해보면 현재 부하시, 커넥션 풀 최대 185개 필요하고 여유있게 200까지 놓는다고 하면(현재 10개 생성중), 커넥션 객체를 하나에 4kb 잡아서 200개시 약 0.7mb heap 메모리가 필요하다. 현재 관측된 바로는 jvm이 힙을 Os로부터 최대 1G , 사전 확보 256mb 이므로 0.7mb정도는 트레이드 오프관점에서 괜찮다고 판단하였다.</p>
<p>따라서 Spring boot에서 HikariCp 커넥션 풀을 늘리는 방법을 진행한다.</p>
<table>
<thead>
<tr>
<th>옵션명</th>
<th>설명</th>
<th>기본값</th>
<th>권장 설정</th>
</tr>
</thead>
<tbody><tr>
<td><code>maximum-pool-size</code></td>
<td>풀에서 동시에 사용할 수 있는 최대 커넥션 수</td>
<td>10</td>
<td>200</td>
</tr>
<tr>
<td><code>minimum-idle</code></td>
<td>유지할 최소 유휴 커넥션 수</td>
<td>10</td>
<td>10</td>
</tr>
<tr>
<td><code>idle-timeout</code></td>
<td>유휴 커넥션을 유지하는 시간 (ms)</td>
<td>600000</td>
<td>300000</td>
</tr>
<tr>
<td><code>max-lifetime</code></td>
<td>커넥션의 최대 수명 (ms)</td>
<td>1800000</td>
<td>1800000</td>
</tr>
<tr>
<td><code>connection-timeout</code></td>
<td>커넥션을 얻기까지 대기하는 최대 시간 (ms)</td>
<td>30000</td>
<td>30000</td>
</tr>
</tbody></table>
<p><code>application.properties</code>를 아래와 같이 변경한다.</p>
<pre><code class="language-bash">spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
</code></pre>
<p><code>application.yml</code>일 경우는 아래와 같다.</p>
<pre><code class="language-bash">spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      idle-timeout: 300000
      max-lifetime: 1800000
      connection-timeout: 30000
</code></pre>
<ol>
<li>결론</li>
</ol>
<p>주된 원인으로는 HikariCp 커넥션 풀 개수가 10개라서 DB쿼리가 해당 요청을 처리 못하는 것으로 나왔다. 이 때문에 IO작업을 대기하는 작업들이 생겨나고 CPU가 처리해야하지만 하지 못하는 작업들이 존재함에따라 CPU부하가 생겨나는 것이였다. </p>
<p>따라서 일단  HikariCp 커넥션 풀 개수를 50개로 늘려보고 부하를 한번더 주입하여 테스트 하기로 하였다.</p>
<ol>
<li>기타</li>
</ol>
<p>아래 링크는 부하가 걸렸을 때, 그라파나 스냅샷이다.</p>
<p><a href="https://snapshots.raintank.io/dashboard/snapshot/cRALkG5IhEPJ51JbnGQv1Ik4GHVjyxkn?orgId=0&amp;from=2026-02-05T08:40:00.000Z&amp;to=2026-02-05T09:20:00.000Z&amp;timezone=browser&amp;var-application=mine&amp;var-instance=172.17.0.1:443&amp;var-hikaricp=HikariPool-1&amp;var-memory_pool_heap=$__all&amp;var-memory_pool_nonheap=$__all&amp;refresh=5s">https://snapshots.raintank.io/dashboard/snapshot/cRALkG5IhEPJ51JbnGQv1Ik4GHVjyxkn?orgId=0&amp;from=2026-02-05T08:40:00.000Z&amp;to=2026-02-05T09:20:00.000Z&amp;timezone=browser&amp;var-application=mine&amp;var-instance=172.17.0.1:443&amp;var-hikaricp=HikariPool-1&amp;var-memory_pool_heap=$__all&amp;var-memory_pool_nonheap=$__all&amp;refresh=5s</a></p>
<h3 id="832-db-관점-분석">8.3.2 DB 관점 분석</h3>
<ol>
<li><p><strong>DB CPU와 Memory</strong></p>
<p> <img src="attachment:47092e56-da7d-4507-973e-ead79bab7719:image.png" alt="image.png"></p>
<ul>
<li>CPU 사용량이 최대치(100%)를 계속 점유하지 않았다는 것은, 실행된 SQL 쿼리들이 복잡한 연산(CPU Bound)을 필요로 하기보다는 <strong>대기(Wait)</strong>하는 시간이 더 많았기 때문에, 시스템의 물리적 한계치에 도달하지 않았다는 것을 알 수 있었다.</li>
<li>전체 메모리 점유율이 매우 낮은것으로보아, 메모리 부족으로 인해 DB가 강제 종료되거나 스왑(Swap)이 발생하여 느려진 것도 아닌것으로 판단했다.</li>
</ul>
</li>
<li><p><strong>커넥션과 세션 상태</strong></p>
<p> <img src="attachment:9f3430a0-3748-4e7f-99f8-91a07e8833b4:image.png" alt="image.png"></p>
<p> <img src="attachment:f7f6d533-87dc-409f-8a54-e2e45837fe72:image.png" alt="image.png"></p>
</li>
</ol>
<ul>
<li><strong>Active sessions (활성 세션 급증):</strong> 평소 0~1 수준이던 활성 세션이 특정 시점에 <strong>최대 9개(Max 9)</strong>까지 수직 상승하는 것을 보아 이는 DB가 요청을 처리하는 속도보다 새로운 요청이 들어오는 속도가 더 빠르다는 것을 의미한다. 순간 DB가 동시에 9개의 쿼리를 처리하느라 풀 가동 중이었다는 뜻이다.</li>
<li><strong>Idle sessions (대기 세션):</strong> <code>Idle in transaction</code> 상태의 세션이 관찰되는데, 이는 애플리케이션이 DB에 연결을 열어놓고 쿼리를 실행한 뒤, 트랜잭션을 제대로 닫지(Commit/Rollback) 않고 붙잡고 있다는 신호입니다. 이 세션들이 커넥션 풀을 점유하여 병목을 만든다고 판단할 수 있다.</li>
<li><strong>결론</strong><ul>
<li>일부 세션이 <code>Idle in transaction</code> 상태로 들어가며 DB 자원을 점유한 채 응답을 안 줌.</li>
<li>남은 자원으로 요청을 처리하려다 보니 <code>Active sessions</code>가 9까지 치솟으며 부하 발생.</li>
<li>트랜잭션과 락을 오래 붙잡는 요청이 누적되면서 DB 응답을 기다리는 백엔드 요청이 쌓였고, 그 결과 백엔드 워커 스레드가 고갈되어 타임아웃이 발생했다고 판단했다.</li>
</ul>
</li>
</ul>
<ol>
<li><p><strong>락(Lock) 경합 발생</strong></p>
<p> <img src="attachment:b970eb12-201c-46d2-bc49-4792ba2d5763:image.png" alt="image.png"></p>
</li>
</ol>
<ul>
<li><code>RowExclusiveLock</code>의 의미: &quot;나 지금 쓰는 중이야!&quot;<ul>
<li>그래프에서 가장 눈에 띄는 빨간색 계열의 <code>rowexclusivelock</code>은 주로 <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>와 같은 <strong>쓰기 작업</strong>을 할 때 자연스럽게 발생한다. 다만, 동시에 너무 많이 생긴것이 문제이다.<ul>
<li><strong>상황 해석:</strong> 카드를 생성하는 <code>POST</code> 요청이 들어오면 DB는 해당 데이터를 테이블에 넣기 위해 행(Row) 단위로 잠금을 건다.</li>
<li><strong>병목 지점:</strong> 수백 개의 API 요청이 동시에 테이블에 쓰기작업을 하러 <code>RowExclusiveLock</code>을 요청하면서 DB가 이 순서를 처리하느라 부하가 급증한 것으로 판단된다.</li>
</ul>
</li>
</ul>
</li>
<li><code>AccessShareLock</code>의 동반 상승: &quot;나도 좀 읽자!&quot;<ul>
<li>노란색 그래프인 <code>accesssharelock</code> 수치도 498까지 높게 올라가있는 것을 볼 수 있고, 이는 보통 <code>SELECT</code> 쿼리를 실행할 때 발생한다.<ul>
<li><strong>상황 해석:</strong> 카드 생성(<code>INSERT</code>)만 일어나는 게 아니라, 생성 직후에 &quot;잘 생성됐는지 확인&quot;하거나 &quot;전체 카드 목록을 다시 불러오는&quot; 등의 <strong>조회 작업</strong>이 동시에 몰렸을 수 있겠다고 판단했다.</li>
<li><strong>경합 발생:</strong> PostgreSQL에서 읽기(<code>AccessShare</code>)와 쓰기(<code>RowExclusive</code>)는 서로를 직접적으로 막지는 않지만, 너무 많은 요청이 한꺼번에 몰리면 CPU와 메모리 자원을 나눠 쓰느라 전체적인 처리 속도가 느려진 것이 아닌가 하는 생각이 들었다.</li>
</ul>
</li>
</ul>
</li>
<li><code>RowShareLock</code>: &quot;이 데이터 건들지 마&quot;<ul>
<li><code>rowsharelock</code> 수치(365)도 굉장이 높은데, 이는 보통 <code>SELECT ... FOR SHARE</code> 같은 쿼리나 <strong>외래 키(Foreign Key)</strong> 제약 조건을 확인할 때 발생한다.<ul>
<li><strong>결정적 원인:</strong> 카드 생성 시 유저 ID나 카테고리 ID 같은 <strong>외래 키</strong>를 참조하고 있다보니, DB는 부모 테이블의 데이터가 지워지지 않도록 락을 건것으로 보인다.</li>
<li><strong>결과:</strong> 수많은 카드 생성 요청이 부모 테이블(유저 테이블)의 특정 행을 동시에 참조하려고 시도하면서 여기서도 대기열이 발생한 것으로 볼수도 있을 것 같다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>결론</strong> : DB가 죽은 게 아니라, 줄이 너무 긴 상태라고 판단했다.<ul>
<li>현재 락 그래프가 0이다가 특정 시점에 수직으로 솟구친 것은 <strong>DB 하드웨어의 한계보다는 &#39;동시성 제어&#39;에서 병목</strong>이 온 것 같다.</li>
<li><code>exclusive lock</code>처럼 테이블 전체를 꽉 막아버리는 락은 없지만, <strong>자잘한 로우 락(Row Lock)들이 수천 개</strong>가 얽히면서 DB 세션이 포화 상태가 되어 API 응답 시간이 길어지고, 결국 백엔드 서버의 커넥션 풀이 다 차버려서 서비스가 &quot;터지는&quot; 현상으로 이어진 것 같다.</li>
</ul>
</li>
</ul>
<p><strong>DB 관점 최종 결론</strong></p>
<ul>
<li>DB CPU, 메모리, 커넥션 수치는 모두 임계치에 도달하지 않았으며, DB 하드웨어 또는 쿼리 성능 자체가 병목이 된 상황은 아니었다.</li>
<li>다만 특정 시점에 카드 생성 API 요청이 대량으로 유입되며, INSERT + SELECT가 혼합된 트랜잭션이 동시에 실행되었다.</li>
<li>이 과정에서 다수의 세션이 <strong>Idle in transaction 상태</strong>로 전환되었고, RowExclusiveLock, RowShareLock 등 <strong>다수의 row-level lock이 단시간에 집중</strong>되었다.</li>
<li>결과적으로 DB는 정상 동작 중이었으나, <strong>동시성 제어 비용과 트랜잭션 유지 시간 증가로 응답 지연이 발생</strong>하였고, 이 지연이 누적되며 백엔드 요청 타임아웃으로 이어졌다.</li>
</ul>
<h3 id="-1"></h3>
<h3 id="84-병목-지점-결론">8.4 병목 지점 결론</h3>
<p>CPU 부하가 걸리는 이유중에 백엔드에서는 DB 커넥션이 부족해 대기 작업이 많아질수록, 대기 스레드들이 깨어나 풀 상태를 계속 확인하게 된다. 이 작업에서 작동하는 루프나 로직 및 Lock을 획득하는 과정에서 CPU를 사용하게된다. 따라서 HikariCP 커넥션 풀을 10개에서 200개로 증가할 예정이고 추가 부하테스트를 진행하여 다른 병목지점을 찾을 예정이다.</p>
]]></description>
        </item>
    </channel>
</rss>