<?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>Sat, 16 May 2026 02:42:28 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/36d110c3-0b17-4b90-807a-78878e834801/image.png</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%95%BC%EB%A7%9D%EC%9D%B4%EB%9D%BC%EB%8A%94-%EA%B2%83%EC%9D%84-%EB%82%98%EC%81%9C-%EA%B2%83%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@halo_3735/%EC%95%BC%EB%A7%9D%EC%9D%B4%EB%9D%BC%EB%8A%94-%EA%B2%83%EC%9D%84-%EB%82%98%EC%81%9C-%EA%B2%83%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Sat, 16 May 2026 02:42:28 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>어느 CTO님의 말씀이 굉장히 가슴을 때렸다.</p>
<p>&quot;열정과 야망을 구분할 수 있어야한다.&quot;</p>
<p>야망이 나쁜 것인가를 생각해보았을때,</p>
<p>직관적으로 그렇다라고 말할 수는 없었다.</p>
<h2 id="야망은-나쁜-것인가">야망은 나쁜 것인가?</h2>
<p>가장 오래된 책중에 하나인 성경에 따르면,
야망이 죄로 이어질 때,</p>
<p>나쁜 것이 된다고 한다.</p>
<h2 id="야망이-죄로-이어지지-않게-하려면-무엇을-해야할까">야망이 죄로 이어지지 않게 하려면 무엇을 해야할까?</h2>
<p>뚜렷한 목표가 있어야한다.</p>
<p>우리는 이것을 &quot;비전&quot;이라고 한다.</p>
<h2 id="비전을-세우는-법">비전을 세우는 법</h2>
<hr>
<p>질문의 개수 : 2개</p>
]]></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[졸업]]></title>
            <link>https://velog.io/@halo_3735/%EC%A1%B8%EC%97%85</link>
            <guid>https://velog.io/@halo_3735/%EC%A1%B8%EC%97%85</guid>
            <pubDate>Fri, 06 Mar 2026 06:50:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/halo_3735/post/7d4c72ae-aa32-4cab-aebe-e02f4b9cc1c1/image.png" alt=""></p>
<p>^^</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="1-개요">1. 개요</h1>
<h2 id="가-문서-목적">가. 문서 목적</h2>
<p>이분 탐색의 시간복잡도에 대해 알아보던 중, 복잡도라는 표현을 사용한 사람의 의도가 궁금해서 한번 찾아보았다. 더불어 &quot;시간&quot;에 대한 나의 해석 또한 들어가있다.</p>
<h2 id="나-목차">나. 목차</h2>
<p>1) 개요
2) 연산횟수가 늘어난다고 해서 복잡도라는 표현을 쓰는 것은 맞을까
3) 여러 부분
4) 시간
5) 결론</p>
<h1 id="2-연산횟수가-늘어난다고-해서-복잡도라는-표현을-쓰는-것이-맞을까">2. 연산횟수가 늘어난다고 해서 복잡도라는 표현을 쓰는 것이 맞을까</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="3-여러-부분">3. 여러 부분</h1>
<p>단순한 순차탐색인 O(n)이라면 하나의 배열에서 모든게 이루어진다. 그러면 하나의 부분일까? 아니다. 해당 배열에서도 연산구조가 여러개로 나뉜다. 바로 루프와 조회 등등이다. </p>
<p>이러한 관점에서 보면 모든 알고리즘은 여러 연산 구조로 이루어져있기 때문에 &quot;복잡도&quot;라는 단어를 쓰는 것이 타당한 것이다.</p>
<p>하지만 왜 하필 &quot;시간&quot;일까.</p>
<h1 id="4-시간">4. 시간</h1>
<p>여러 연산이 걸리는 횟수를 표현한 것이기 때문에 횟수 복잡도가 맞지 않을까? 시간이 먼저는 아니지 않는가. 연산횟수가 적어지면 연산 시간이 줄어들거 아닌가.</p>
<p>과연 그럴까? 연산횟수가 무조건 적은 연산이 연산 시간이 적다고 말할 수 있냐는 말이다. &quot;시간&quot;이라는 단어가 붙은 이유는 이러한 현실적인 예외 속에 있다.</p>
<p>CPU는 항상 같은 연산 횟수를 처리할 때, 동일 연산 시간이 보장되어 지지 않는다. 다양한 변수들이 존재하기 때문이다. 메모리, 유동적인 클럭 등 다양한 이유들이 그 예시이다. 따라서 인간의 관점에서 인간의 문제를 해결할 수 있는데, 사용할 수 있는 인간이 정해놓은 지표인 &quot;시간&quot;을 사용하는 것이다.</p>
<h1 id="5-결론">5. 결론</h1>
<p>결론은 시간복잡도는 여러부분으로 이루어진 인간이 측정할 수 있는 시간이라는 단위를 사용하여 포장한 &quot;연산 횟수&quot;를 의미한다.</p>
<p>복잡도라는 단어를 쓰는 이유는 여러 연산 로직이 들어가 있어서 이고 시간이라는 단어를 쓰는 이유는 CPU 동일  연산 시간별 동일 연산 횟수를 보장하지 않기 때문에, 인간 입장에서 절대적으로 측정할 수 있는 지표인 시간을 사용한 것이다. (해당 지표를 써서 컴퓨팅 자원 소모를 아낌으로서 비용 및 시간적인 측면에서 아낄 수 있고 이는 회사의 성장으로 이어진다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[문서화하는법]]></title>
            <link>https://velog.io/@halo_3735/%EB%AC%B8%EC%84%9C%ED%99%94%ED%95%98%EB%8A%94%EB%B2%95</link>
            <guid>https://velog.io/@halo_3735/%EB%AC%B8%EC%84%9C%ED%99%94%ED%95%98%EB%8A%94%EB%B2%95</guid>
            <pubDate>Mon, 09 Feb 2026 12:55:51 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개요">1. 개요</h1>
<h2 id="가-문서-목적">가. 문서 목적</h2>
<p>해당 문서는 문서를 어떻게 하면 읽는 사람이 편하게 통일된 형식으로 작성할 수 있을까에 대해서 작성하였고 추후 문서를 작성하는 나자신의 참고자료로써도 사용될 예정이다.</p>
<h2 id="나-목차">나. 목차</h2>
<p>1) 개요
2) 문서화 형식
3) 문서화를 잘하면 작성자에게 좋은 점</p>
<h1 id="2-문서화-형식">2. 문서화 형식</h1>
<h2 id="가-개요를-통해-읽는-사람이-문서-전체-내용을-알게하라">가. 개요를 통해 읽는 사람이 문서 전체 내용을 알게하라</h2>
<p>*<em>두괄식으로 쓰라는 것이다. *</em></p>
<p>당신이 흥미진진한 글을 쓰는 작가가 아닌이상, 끝에 결과가 오는 미괄식으로 쓸 필요는 없지 않은가. 해당 문서에도 나와있듯이 모든 문서에는 개요라는 첫번째 단락을 통해서 읽는 사람이 해당 문서의 순서를 한눈에 파악할 수 있게 해야한다. 이 방식을 통해 읽는 사람은 이 문서를 두번 읽는 듯한 마치 알고 있는 것을 읽게만드는 효과를 느낄 수 있다.</p>
<h2 id="나-사람들은-크고-두꺼운-글자부터-읽는다">나. 사람들은 크고 두꺼운 글자부터 읽는다.</h2>
<p><strong>이게 눈이 먼저 갈껄?</strong></p>
<p>이거보다</p>
<h2 id="다-항목번호는-통일">다. 항목번호는 통일</h2>
<p>필자는 <code>1.</code> -&gt; <code>가</code> -&gt; <code>1)</code>을 사용하고 있다. 이 방식은 널리쓰이는 방식으로 독자가 읽기 훨씬 수월하게 한다.</p>
<h2 id="라--보단-표를-사용해라">라. <code>-</code>보단 표를 사용해라</h2>
<p><strong>노션이 유행한 나머지 많은 사람들이 아래와 같이 쓴다.</strong></p>
<ul>
<li>아침에 일어나서</li>
<li>밥을 먹었고</li>
<li>설거지를 했다</li>
</ul>
<table>
<thead>
<tr>
<th>순서</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>아침에 일어나서</td>
</tr>
<tr>
<td>2</td>
<td>밥을 먹었고</td>
</tr>
<tr>
<td>3</td>
<td>설거지를 했다</td>
</tr>
</tbody></table>
<p>뭐가 더 낫나요?</p>
<blockquote>
<p>📌 해당 문서는 2026년 02월 09일에 업데이트 되었습니다.</p>
</blockquote>
]]></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>
        <item>
            <title><![CDATA[[정리필요] Github Actions Runner IP를 AWS에 동적으로 허용]]></title>
            <link>https://velog.io/@halo_3735/%EC%A0%95%EB%A6%AC%ED%95%84%EC%9A%94</link>
            <guid>https://velog.io/@halo_3735/%EC%A0%95%EB%A6%AC%ED%95%84%EC%9A%94</guid>
            <pubDate>Wed, 04 Feb 2026 22:38:12 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/100-hours-a-week/4-team-IMYME-be/pull/34">https://github.com/100-hours-a-week/4-team-IMYME-be/pull/34</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[장애 알림 서비스 구축]]></title>
            <link>https://velog.io/@halo_3735/%EC%9E%A5%EC%95%A0-%EC%95%8C%EB%A6%BC-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@halo_3735/%EC%9E%A5%EC%95%A0-%EC%95%8C%EB%A6%BC-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Fri, 30 Jan 2026 08:29:44 GMT</pubDate>
            <description><![CDATA[<h1 id="1-장애대응-필요성">1. 장애대응 필요성</h1>
<p>장애가 일어난 시점에 빠르게 대응해서 서비스 손실과 이어지는 사용자 이탈률을 줄이는데 목적으로 한다.</p>
<h1 id="2-장애-상황-선정">2. 장애 상황 선정</h1>
<h2 id="21-시스템의-정상적인-사용이-어려운-경우">2.1 시스템의 정상적인 사용이 어려운 경우</h2>
<p>가. 서비스가 불능이고 이것은 리소스 낭비로 이어지므로 즉각적인 대응이 필요하다고 판단하여 장애 알림 대상으로 선정</p>
<h1 id="3-각-상황별-장애-판단-방법">3. 각 상황별 장애 판단 방법</h1>
<h2 id="31-시스템의-정상적인-사용이-어려운-경우">3.1 시스템의 정상적인 사용이 어려운 경우</h2>
<p>가. 각 서비스가 완전히 다운되는 경우</p>
<ul>
<li>헬스체크를 시도하여 생존 여부 판단</li>
<li>헬스체크 실패시 서비스 완전 다운 알람 클라우드 및 해당 담당 개발자들에게 전송</li>
</ul>
<p>나. 서비스 응답이 자꾸 실패하는 경우 (5xx error 기준)</p>
<ul>
<li>5분간 100명의 동시요청중 5~9명까지는 응답이 실패해도 재요청으로 처리하고 장애 경고 알람만 보내는 것으로 결정</li>
<li>10% 이상부터는 문제상황이라고 인지하고 장애 확정 알림을 보내 해당 서비스 개발자들 및 엔지니어가 즉시 조치 혹은 롤백 예정</li>
</ul>
<table>
<thead>
<tr>
<th>알림 유형</th>
<th>오류율</th>
<th>근거</th>
<th>추적</th>
</tr>
</thead>
<tbody><tr>
<td>장애 경고 알림</td>
<td>5~9%</td>
<td>MVP1에서 100명의 동시요청중 5~9명까지는 응답이 실패해도 해당 사용자 환경에 문제가 있다고 판단, 나머지 90명이 잘 활동하는것이 그 증거</td>
<td>추후 해당 수치를 유지한채로 서버를 운영하였을 때, 사용자 이탈률을 추적해 다음 MVP 때 오류율 검토에 반영 예정</td>
</tr>
<tr>
<td>장애 확정 알림</td>
<td>10% 이상</td>
<td>사용자 체감 불편 발생 가능, 재요청으로 해결되지 않는 비율</td>
<td>추후 해당 수치 장애</td>
</tr>
</tbody></table>
<h1 id="4-장애-알람">4. 장애 알람</h1>
<h2 id="41-헬스체크-실패-알람">4.1 헬스체크 실패 알람</h2>
<ol>
<li>인스턴스 자체의 헬스체크</li>
</ol>
<img width="900" height="200" alt="image" src="https://github.com/user-attachments/assets/84662091-39e7-4b80-bc8e-c86d06b33556" />    

<p>1분간격 헬스체크 1번 실패시 아래의 같은 디스코드 봇이 알람을 보내게 설정, 자세한 구현 방식은 바로 아래 4.2 참고</p>
<h2 id="42-서비스-응답-5xx-10-이상-알람">4.2 서비스 응답 5xx 10% 이상 알람</h2>
<p>5분간격으로 모아진 에러들의 10%가 5xx일 경우 아래 사진과 같이 디스코드 알림 계획</p>
<img width="1106" height="218" alt="image" src="https://github.com/user-attachments/assets/e42e6bee-3c0c-416e-ba26-14ef989484dd" />
### 가. EC2 IAM Role 설정

<p>EC2 인스턴스에 <code>CloudWatchAgentServerPolicy</code> 정책이 포함된 IAM Role 삽입</p>
<h3 id="나-nginx-로그-분리">나. <strong>Nginx 로그 분리</strong></h3>
<ol>
<li>nginx sites-available/default 수정 </li>
</ol>
<pre><code class="language-jsx">...
# 프론트 서버(Next.js 서버)
    location ^~ /api/ {
        access_log /var/log/nginx/fe_access.log json_log;
        error_log /var/log/nginx/fe_error.log;
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_redirect http://localhost:3000 https://$host;
        proxy_redirect http://127.0.0.1:3000 https://$host;
    }

    # 백엔드 API (Spring Boot)
    location ^~ /server/  {
        access_log /var/log/nginx/be_access.log json_log;
        error_log /var/log/nginx/be_error.log;
        proxy_pass http://127.0.0.1:8080/;
    }

    # AI 서버 (FastAPI)
    location /ai/ {
        access_log /var/log/nginx/ai_access.log json_log;
        error_log /var/log/nginx/ai_error.log;
        proxy_pass http://127.0.0.1:8000/;
    }

    # 프론트 (Next.js SSR)
    location / {
        access_log /var/log/nginx/fe_access.log json_log;
        error_log /var/log/nginx/fe_error.log;
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_redirect http://localhost:3000/ /;
        proxy_redirect https://localhost:3000/ /;
        proxy_redirect http://127.0.0.1:3000/ /;
        proxy_redirect https://127.0.0.1:3000/ /;
    }
}

...</code></pre>
<ul>
<li><p>전체 코드</p>
<pre><code class="language-jsx">  ##
  # You should look at the following URL&#39;s in order to grasp a solid understanding
  # of Nginx configuration files in order to fully unleash the power of Nginx.
  # https://w.nginx.com/resources/wiki/start/
  # https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
  # https://wiki.debian.org/Nginx/DirectoryStructure
  #
  # In most cases, administrators will remove this file from sites-enabled/ and
  # leave it as reference inside of sites-available where it will continue to be
  # updated by the nginx packaging team.
  #
  # This file will automatically load configuration files provided by other
  # applications, such as Drupal or Wordpress. These applications will be made
  # available underneath a path with that package name, such as /drupal8.
  #
  # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
  ##

  # Default server configuration
  #
  #server {
  #       listen 80 default_server;
  #       listen [::]:80 default_server;

          # SSL configuration
          #
          # listen 443 ssl default_server;
          # listen [::]:443 ssl default_server;
          #
          # Note: You should disable gzip for SSL traffic.
          # See: https://bugs.debian.org/773332
          #
          # Read up on ssl_ciphers to ensure a secure configuration.
          # See: https://bugs.debian.org/765782
          #
          # Self signed certs generated by the ssl-cert package
          # Don&#39;t use them in a production server!
          #
          # include snippets/snakeoil.conf;

  #       root /var/www/html;

          # Add index.php to the list if you are using PHP
  #       index index.html index.htm index.nginx-debian.html;

  #       server_name _;

  #       location / {
                  # First attempt to serve request as file, then
                  # as directory, then fall back to displaying a 404.
  #               try_files $uri $uri/ =404;
  #       }

          # pass PHP scripts to FastCGI server
          #
          #location ~ \.php$ {
          #       include snippets/fastcgi-php.conf;
          #
          #       # With php-fpm (or other unix sockets):
          #       fastcgi_pass unix:/run/php/php7.4-fpm.sock;
          #       # With php-cgi (or other tcp sockets):
          #       fastcgi_pass 127.0.0.1:9000;
          #}

          # deny access to .htaccess files, if Apache&#39;s document root
          # concurs with nginx&#39;s one
          #
          #location ~ /\.ht {
          #       deny all;
          #}
  #}

  # Virtual Host configuration for example.com
  #
  # You can move that to a different file under sites-available/ and symlink that
  # to sites-enabled/ to enable it.
  #
  #server {
  #       listen 80;
  #       listen [::]:80;
  #
  #       server_name example.com;
  #
  #       root /var/www/example.com;
  #       index index.html;
  #
  #       location / {
  #               try_files $uri $uri/ =404;
  #       }
  #}

  #server {

          # SSL configuration
          #
          # listen 443 ssl default_server;
          # listen [::]:443 ssl default_server;
          #
          # Note: You should disable gzip for SSL traffic.
          # See: https://bugs.debian.org/773332
          #
          # Read up on ssl_ciphers to ensure a secure configuration.
          # See: https://bugs.debian.org/765782
          #
          # Self signed certs generated by the ssl-cert package
          # Don&#39;t use them in a production server!
          #
          # include snippets/snakeoil.conf;

  #       root /var/www/html;

          # Add index.php to the list if you are using PHP
  #       index index.html index.htm index.nginx-debian.html;
   #   server_name imymemine.kr; # managed by Certbot

  #       location / {
                  # First attempt to serve request as file, then
                  # as directory, then fall back to displaying a 404.
  #               try_files $uri $uri/ =404;
  #       }

          # pass PHP scripts to FastCGI server
          #
          #location ~ \.php$ {
          #       include snippets/fastcgi-php.conf;
          #
          #       # With php-fpm (or other unix sockets):
          #       fastcgi_pass unix:/run/php/php7.4-fpm.sock;
          #       # With php-cgi (or other tcp sockets):
          #       fastcgi_pass 127.0.0.1:9000;
          #}

          # deny access to .htaccess files, if Apache&#39;s document root
          # concurs with nginx&#39;s one
          #
          #location ~ /\.ht {
          #       deny all;
          #}

   #   listen [::]:443 ssl ipv6only=on; # managed by Certbot
    #  listen 443 ssl; # managed by Certbot
     # ssl_certificate /etc/letsencrypt/live/imymemine.kr/fullchain.pem; # managed by Certbot
     # ssl_certificate_key /etc/letsencrypt/live/imymemine.kr/privkey.pem; # managed by Certbot
      #include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
     # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

  #}
  #server {
  #    if ($host = imymemine.kr) {
  #        return 301 https://$host$request_uri;
  #    } # managed by Certbot

  #       listen 80 ;
  #       listen [::]:80 ;
  #    server_name imymemine.kr;
  #    return 404; # managed by Certbot

  #}

  # /etc/nginx/sites-available/default

  # 80 -&gt; 443 리다이렉트 (+ 선택: ACME 챌린지)
  server {
      listen 80;
      listen [::]:80;

      server_name imymemine.kr www.imymemine.kr;

      # (선택) HTTP-01 챌린지를 명시적으로 유지하고 싶으면
      location ^~ /.well-known/acme-challenge/ {
          root /var/www/certbot;
      }

      location = /robots.txt {
          root /var/www/html;
          access_log off;
          log_not_found off;
      }

      location / {
          return 301 https://$host$request_uri;
      }
  }

  # HTTPS reverse proxy
  server {
      listen 443 ssl;
      listen [::]:443 ssl;

      server_name imymemine.kr www.imymemine.kr;

      ssl_certificate     /etc/letsencrypt/live/imymemine.kr/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/imymemine.kr/privkey.pem;

      include /etc/letsencrypt/options-ssl-nginx.conf;
      ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

      location = /robots.txt {
          root /var/www/html;
          access_log off;
          log_not_found off;
      }

      # 공통 프록시 헤더
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      # Next가 외부 origin 추론할 때 도움되는 헤더
      proxy_set_header X-Forwarded-Host $host;
      proxy_set_header X-Forwarded-Port $server_port;

      # Swagger(FastAPI)
      location = /openapi.json {
          access_log /var/log/nginx/ai_access.log json_log;
          error_log /var/log/nginx/ai_error.log;
          proxy_pass http://127.0.0.1:8000/api/v1/openapi.json;
      }

      location = /api/v1/openapi.json {
          access_log /var/log/nginx/ai_access.log json_log;
          error_log /var/log/nginx/ai_error.log;
          proxy_pass http://127.0.0.1:8000/api/v1/openapi.json; 
      }

      # Swagger/OpenAPI docs endpoints (springdoc 기본)
      location ^~ /v3/api-docs/ {
          access_log /var/log/nginx/be_access.log json_log;
          error_log /var/log/nginx/be_error.log;
          proxy_pass http://127.0.0.1:8080/v3/api-docs/;
      }

      location = /v3/api-docs {
          access_log /var/log/nginx/be_access.log json_log;
          error_log /var/log/nginx/be_error.log;
          proxy_pass http://127.0.0.1:8080/v3/api-docs;
      }

      location = /v3/api-docs/swagger-config {
          access_log /var/log/nginx/be_access.log json_log;
          error_log /var/log/nginx/be_error.log;
          proxy_pass http://127.0.0.1:8080/v3/api-docs/swagger-config;
      }

      # 프론트 서버(Next.js 서버)
      location ^~ /api/ {
          access_log /var/log/nginx/fe_access.log json_log;
          error_log /var/log/nginx/fe_error.log;
          proxy_pass http://127.0.0.1:3000;
          proxy_http_version 1.1;

          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-Host $host;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header X-Forwarded-Port $server_port;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

          proxy_redirect http://localhost:3000 https://$host;
          proxy_redirect http://127.0.0.1:3000 https://$host;
      }

      # 백엔드 API (Spring Boot)
      location ^~ /server/  {
          access_log /var/log/nginx/be_access.log json_log;
          error_log /var/log/nginx/be_error.log;
          proxy_pass http://127.0.0.1:8080/;
      }

      # AI 서버 (FastAPI)
      location /ai/ {
          access_log /var/log/nginx/ai_access.log json_log;
          error_log /var/log/nginx/ai_error.log;
          proxy_pass http://127.0.0.1:8000/;
      }

      # 프론트 (Next.js SSR)
      location / {
          access_log /var/log/nginx/fe_access.log json_log;
          error_log /var/log/nginx/fe_error.log;
          proxy_pass http://127.0.0.1:3000;
          proxy_http_version 1.1;

          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-Host $host;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header X-Forwarded-Port $server_port;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

          proxy_redirect http://localhost:3000/ /;
          proxy_redirect https://localhost:3000/ /;
          proxy_redirect http://127.0.0.1:3000/ /;
          proxy_redirect https://127.0.0.1:3000/ /;
      }
  }
</code></pre>
</li>
</ul>
<ol start="2">
<li>nginx.conf 수정</li>
</ol>
<pre><code class="language-jsx">... 

        #  CloudWatch용 JSON 포맷 추가
        log_format json_log escape=json &#39;{&#39;
                &#39;&quot;time&quot;:&quot;$time_iso8601&quot;,&#39;
                &#39;&quot;remote_addr&quot;:&quot;$remote_addr&quot;,&#39;
                &#39;&quot;status&quot;:$status,&#39;
                &#39;&quot;request&quot;:&quot;$request&quot;,&#39;
                &#39;&quot;request_time&quot;:$request_time,&#39;
                &#39;&quot;upstream_response_time&quot;:&quot;$upstream_response_time&quot;,&#39;
...</code></pre>
<ul>
<li><p>전체 코드</p>
<pre><code class="language-jsx">  ubuntu@ip-172-31-39-74:/etc/nginx$ cat nginx.conf
  user www-data;
  worker_processes auto;
  pid /run/nginx.pid;
  error_log /var/log/nginx/error.log;
  include /etc/nginx/modules-enabled/*.conf;

  events {
          worker_connections 768;
          # multi_accept on;
  }

  http {

          map $http_upgrade $connection_upgrade {
              default upgrade;
              &#39;&#39;      close;
          }
          ##
          # Basic Settings
          ##

          sendfile on;
          tcp_nopush on;
          types_hash_max_size 2048;
          # server_tokens off;

          # server_names_hash_bucket_size 64;
          # server_name_in_redirect off;

          include /etc/nginx/mime.types;
          default_type application/octet-stream;

          ##
          # SSL Settings
          ##

          ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
          ssl_prefer_server_ciphers on;

          ##
          # Logging Settings
          ##
          log_format main
                  &#39;$remote_addr - $remote_user [$time_local] &#39;
                  &#39;&quot;$request&quot; $status $body_bytes_sent &#39;
                  &#39;&quot;$http_user_agent&quot; &#39;
                  &#39;request_time=$request_time &#39;
                  &#39;upstream_time=$upstream_response_time&#39;;

          # ✅ (halo) CloudWatch용 JSON 포맷 추가
          log_format json_log escape=json &#39;{&#39;
                  &#39;&quot;time&quot;:&quot;$time_iso8601&quot;,&#39;
                  &#39;&quot;remote_addr&quot;:&quot;$remote_addr&quot;,&#39;
                  &#39;&quot;status&quot;:$status,&#39;
                  &#39;&quot;request&quot;:&quot;$request&quot;,&#39;
                  &#39;&quot;request_time&quot;:$request_time,&#39;
                  &#39;&quot;upstream_response_time&quot;:&quot;$upstream_response_time&quot;,&#39;
                  &#39;&quot;body_bytes_sent&quot;:$body_bytes_sent&#39;
          &#39;}&#39;;

          access_log /var/log/nginx/access.log main;

          ##
          # Gzip Settings
          ##

          gzip on;

          # gzip_vary on;
          # gzip_proxied any;
          # gzip_comp_level 6;
          # gzip_buffers 16 8k;
          # gzip_http_version 1.1;
          # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

          ##
          # Virtual Host Configs
          ##

          include /etc/nginx/conf.d/*.conf;
          include /etc/nginx/sites-enabled/*;
  }

  #mail {
  #       # See sample authentication script at:
  #       # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
  #
  #       # auth_http localhost/auth.php;
  #       # pop3_capabilities &quot;TOP&quot; &quot;USER&quot;;
  #       # imap_capabilities &quot;IMAP4rev1&quot; &quot;UIDPLUS&quot;;
  #
  #       server {
  #               listen     localhost:110;
  #               protocol   pop3;
  #               proxy      on;
  #       }
  #
  #       server {
  #               listen     localhost:143;
  #               protocol   imap;
  #               proxy      on;
  #       }
  #}
  ubuntu@ip-172-31-39-74:/etc/nginx$ </code></pre>
</li>
</ul>
<h3 id="다-cloudwatch-ec2에-설치">다. CloudWatch EC2에 설치</h3>
<pre><code class="language-bash">rm amazon-cloudwatch-agent.deb*
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/arm64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb</code></pre>
<h3 id="라-cloudwatch-agent-config-구성">라. CloudWatch Agent config 구성</h3>
<p><code>/opt/aws/amazon-cloudwatch-agent/etc/config.json</code></p>
<pre><code class="language-bash">{
  &quot;logs&quot;: {
    &quot;logs_collected&quot;: {
      &quot;files&quot;: {
        &quot;collect_list&quot;: [
          {
            &quot;file_path&quot;: &quot;/var/log/nginx/fe_access.log&quot;,
            &quot;log_group_name&quot;: &quot;/nginx/fe&quot;,
            &quot;log_stream_name&quot;: &quot;{instance_id}&quot;
          },
          {
            &quot;file_path&quot;: &quot;/var/log/nginx/be_access.log&quot;,
            &quot;log_group_name&quot;: &quot;/nginx/be&quot;,
            &quot;log_stream_name&quot;: &quot;{instance_id}&quot;
          },
          {
            &quot;file_path&quot;: &quot;/var/log/nginx/ai_access.log&quot;,
            &quot;log_group_name&quot;: &quot;/nginx/ai&quot;,
            &quot;log_stream_name&quot;: &quot;{instance_id}&quot;
          }
        ]
      }
    }
  }
}
</code></pre>
<h3 id="마-cloudwatch-log-group-확인">마. CloudWatch Log Group 확인</h3>
<img width="700" height="100" alt="image" src="https://github.com/user-attachments/assets/a66f2332-781a-465a-a547-3e04de456364" />


<p>로그 그룹 확인 가능</p>
<img width="1502" height="600" alt="image" src="https://github.com/user-attachments/assets/464ef298-71c3-440a-bf1b-93febfb385f5" />
json형식 http 확인 가능

<h3 id="바-지표-필터-등록">바. 지표 필터 등록</h3>
<img width="500" height="400" alt="image" src="https://github.com/user-attachments/assets/c65cbbd8-3806-4d34-99f2-2a071e9ed245" />

<ul>
<li><p>BE-5XX-Filter : 500이상 에러들 필터링하는 지표 필터</p>
</li>
<li><p>BE-Total-Filter : 모든 에러를 필터링하는 지표 필터</p>
</li>
<li><p>5xx 에러 필터 패턴 예시</p>
</li>
</ul>
<img width="300" height="650" alt="image" src="https://github.com/user-attachments/assets/071e9687-cb6b-42b9-b650-ebda2acc33cb" />    

<h3 id="사-경보-추가">사. 경보 추가</h3>
<img width="900" height="400" alt="image" src="https://github.com/user-attachments/assets/aaf27f81-0718-4899-921d-1f1cb1fc27c1" />

<p>위의 두개의 필터 BE-5XX-Filter 및 BE-Total-Filter로 걸러진 필터들의 총 합계 비율을 퍼센트로 환산하였을 때, 10%가 넘으면 작동하는 경보 추가</p>
<h3 id="아-람다로-경보랑-디스코드-연동">아. 람다로 경보랑 디스코드 연동</h3>
<ol>
<li>디스코드 훅 연동</li>
</ol>
<img width="700" height="300" alt="image" src="https://github.com/user-attachments/assets/4bd54e7b-ec9b-41ef-be28-e0d8f10c5e63" />

<ol start="2">
<li><a href="http://%EB%9E%8C%EB%8B%A4.py">람다.py</a> 작성</li>
</ol>
<p>DISCORD_WEBHOOK_URL에 디스코드 봇 훅 링크 삽입</p>
<pre><code class="language-python">import json
import urllib.request

DISCORD_WEBHOOK_URL = [훅 링크]

LOG_LINKS = {
    &quot;BE&quot;: &quot;https://ap-northeast-2.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-2#logsV2:log-groups/log-group/$252Fnginx$252Fbe&quot;,
    &quot;FE&quot;: &quot;https://ap-northeast-2.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-2#logsV2:log-groups/log-group/$252Fnginx$252Ffe&quot;,
    &quot;AI&quot;: &quot;https://ap-northeast-2.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-2#logsV2:log-groups/log-group/$252Fnginx$252Fai&quot;
}

def lambda_handler(event, context):
    sns_message = event[&#39;Records&#39;][0][&#39;Sns&#39;][&#39;Message&#39;]

    try:
        alarm = json.loads(sns_message)
        alarm_name = alarm.get(&#39;AlarmName&#39;, &#39;Unknown&#39;)
        new_state = alarm.get(&#39;NewStateValue&#39;, &#39;Unknown&#39;)
        reason = alarm.get(&#39;NewStateReason&#39;, &#39;&#39;)

        if new_state == &#39;ALARM&#39;:
            emoji = &#39;🚨&#39;
            color_text = &#39;ALERT&#39;
        elif new_state == &#39;OK&#39;:
            emoji = &#39;✅&#39;
            color_text = &#39;OK&#39;
        else:
            emoji = &#39;⚠️&#39;
            color_text = new_state

        error_rate = &#39;&#39;
        if &#39;datapoints&#39; in reason:
            start = reason.find(&#39;[&#39;) + 1
            end = reason.find(&#39; (&#39;)
            if start &gt; 0 and end &gt; 0:
                error_rate = reason[start:end]

        server_type = &#39;BE&#39;
        if &#39;FE&#39; in alarm_name:
            server_type = &#39;FE&#39;
        elif &#39;AI&#39; in alarm_name:
            server_type = &#39;AI&#39;

        log_link = LOG_LINKS.get(server_type, LOG_LINKS[&#39;BE&#39;])

        content = emoji + &quot; **[&quot; + color_text + &quot;] &quot; + alarm_name + &quot;**\n&quot;
        if error_rate:
            content += &quot;Error Rate: **&quot; + str(round(float(error_rate), 1)) + &quot;%**\n&quot;
        content += &quot;Log: &quot; + log_link

    except:
        content = &quot;Alert: &quot; + sns_message[:500]

    discord_message = {&quot;content&quot;: content}

    req = urllib.request.Request(
        DISCORD_WEBHOOK_URL,
        data=json.dumps(discord_message).encode(&#39;utf-8&#39;),
        headers={
            &#39;Content-Type&#39;: &#39;application/json&#39;,
            &#39;User-Agent&#39;: &#39;AWS-Lambda-SNS-Discord&#39;
        },
        method=&#39;POST&#39;
    )

    urllib.request.urlopen(req)

    return {&#39;statusCode&#39;: 200}</code></pre>
<ol start="3">
<li>트리거 연동</li>
</ol>
<img width="650" height="300" alt="image" src="https://github.com/user-attachments/assets/7f3f5686-13d6-4cd7-bf64-1994754ced3a" />


<img width="700" height="330" alt="image" src="https://github.com/user-attachments/assets/3002c5fb-1188-40e4-8bbf-042a33a8d99e" />

<h3 id="자-알람-확인">자. 알람 확인</h3>
<ol>
<li>500에러  발생</li>
</ol>
<img width="550" height="500" alt="image" src="https://github.com/user-attachments/assets/46467e7b-bfd8-458a-a3b6-8f0124e5f225" />


<ol start="2">
<li>디스코드 알람</li>
</ol>
<img width="400" height="100" alt="image" src="https://github.com/user-attachments/assets/1f96b996-cbe2-48be-9de1-ed0cce4b4652" />    


<h1 id="5-장애-대응-방법">5. 장애 대응 방법</h1>
<ol>
<li>디스코드 알람에서 로그를 확인한 후 로그 링크를 클릭</li>
</ol>
<img width="400" height="100" alt="image" src="https://github.com/user-attachments/assets/8bfbde27-8cb4-4ab1-b343-5453931f430c" />


<ol start="2">
<li>로그스트림 클릭</li>
</ol>
<img width="400" height="100" alt="image" src="https://github.com/user-attachments/assets/8b228c6b-89b2-481a-89a7-ad6222927d2f" />



<ol start="3">
<li>로그이벤트 검색 </li>
</ol>
<img width="300" height="300" alt="image" src="https://github.com/user-attachments/assets/4ca5f53f-fb74-44c6-a834-5feb9a237989" />



<ul>
<li><p>검색 창에 입력</p>
<pre><code class="language-bash">  { $.status &gt;= 500 }</code></pre>
<p>  해당 페이지에서 어떤 API가 어느시간대에 500에러가 떳는지 확인할 수 있다.</p>
</li>
</ul>
<ol start="4">
<li>3번에서 얻은 에러 api와 시간 정보를 가지고 백엔드 서버 로그에서 500에러뜬 api 및  확인</li>
</ol>
<h1 id="6-개선-필요-사항">6. 개선 필요 사항</h1>
<p>가. 현재 5번 프로세스가 개발자 입장에서는 너무 복잡함</p>
<p>현재 EC2로그에 들어가서 500발생 시간대와 API를 가지고 로그 검색을 하기에는 로그가 너무 많아 어려운 상황이기 때문에 로그 분석 환경을 별도로 만들어 개발자들에게 제공할 필요성이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[dddd]]></title>
            <link>https://velog.io/@halo_3735/dddd</link>
            <guid>https://velog.io/@halo_3735/dddd</guid>
            <pubDate>Sun, 07 Dec 2025 13:14:04 GMT</pubDate>
            <description><![CDATA[<p>ddd</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DFS의 연결정보 Graph를 2차원 배열이 아닌 ArrayList로?]]></title>
            <link>https://velog.io/@halo_3735/DFS%EC%9D%98-%EC%97%B0%EA%B2%B0%EC%A0%95%EB%B3%B4-Graph%EB%A5%BC-2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%B4-%EC%95%84%EB%8B%8C-ArrayList%EB%A1%9C</link>
            <guid>https://velog.io/@halo_3735/DFS%EC%9D%98-%EC%97%B0%EA%B2%B0%EC%A0%95%EB%B3%B4-Graph%EB%A5%BC-2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%B4-%EC%95%84%EB%8B%8C-ArrayList%EB%A1%9C</guid>
            <pubDate>Fri, 10 Oct 2025 02:55:43 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/halo_3735/post/be2d06a4-6bb4-4663-897c-6ab656baab99/image.png" alt=""></p>
<p>드디어 풀었다..</p>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/13023">백준 13023</a></p>
</blockquote>
<h1 id="1-인사이트">1. 인사이트</h1>
<ol>
<li>ArrayList에 Initital Capacity를 초기에 잡아줘도 실제 size는 0이다.<pre><code class="language-java">ArrayList&lt;Integer&gt; a = new ArrayList&lt;&gt;(3);
System.out.println(a.size());
</code></pre>
</li>
</ol>
<p>...
0</p>
<pre><code>
2. 위 13023같은 문제는 모든 노드를 출발점으로 하여 DFS를 하기 때문에, 2차원 연결 정보 그래프보다 ArrayList를 사용하는 것이 TLE(Time limit Exceeded)를 방지할 수 있다.

# 2. 전체 풀이
```java
// 백준 13023

import java.util.*;
import java.io.*;

public class P25 {
    static boolean flag = true;
    static ArrayList&lt;ArrayList&lt;Integer&gt;&gt; graph;
    static boolean[] vst;
    static int cnt = 5;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;

        int N, M;

        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        graph = new ArrayList&lt;&gt;(N);

        for (int i =0; i&lt;N; i++){
            graph.add(new ArrayList&lt;&gt;());
        }

        for (int i = 0; i &lt; M; i++) {
            st = new StringTokenizer(br.readLine());

            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            graph.get(a).add(b);
            graph.get(b).add(a);
        }

        for (int i = 0; i &lt; N; i++) {
            if (!flag) break;

            else {
                vst = new boolean[N];
                vst[i] = true;
                DFS(i, 1);
            }
        }


        if (flag) {
            bw.write(0 + &quot;&quot;);
        } else {
            bw.write(1 + &quot;&quot;);
        }

        bw.flush();
        bw.close();

    }

    static void DFS(int n, int dep) {

        if (!flag) {
            return;
        }
        if (dep == cnt) {
            flag = false;
            return;
        }

        for (int i : graph.get(n)) {
            if (vst[i] == false) {
                vst[i] = true;
                DFS(i, dep + 1);
                vst[i] = false;
            }
        }

    }
}




</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Breadth First Search에 대한 고찰과 잡담]]></title>
            <link>https://velog.io/@halo_3735/Breadth-First-Search%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0%EA%B3%BC</link>
            <guid>https://velog.io/@halo_3735/Breadth-First-Search%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0%EA%B3%BC</guid>
            <pubDate>Tue, 07 Oct 2025 02:36:08 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.youtube.com/watch?v=rusGjAs0ius">자 이제 시작이야</a></p>
<blockquote>
<p>요즘 카카오 부트캠프 수업을 들으면서 깊이있는 탐구라는것이 나에게 얼마나 흥미와 관심을 가져다 주는지 모르겠다. 하지만 마음 한 편에는 우주를 공부하는 것에 막연한 기대감을 놓지 못하고 있다. 허성범의 강의를 보았고 좋아하는 것을 남들의 시선을 신경쓰지말고 최선을 다하며 하다보면 후회는 남지 않을 것이다라는 말을 되새기며 지금 하고 있는 일에 최선을 다해보려고 한다. 만약 이것이 정녕 내가 싫어하는 일이더라도 후회는 남기고 싶지 않기 때문이다. 🙈
(들어가는 이야기, 제리라는 풀스택 팀원 덕분에 해당 부분에 대한 즐거움(?)을 알게되었다.)</p>
</blockquote>
<p> BFS는 Breadth의 뜻이 가장 두드러지는 노드 탐색 방법이다. 노드와 노드끼리 연결되어 있다면 한 방향으로 계속 탐색하는 것이 아닌, 이어진 주변 노드에 계속 발을 걸치는 것이다. 하지만 실제 구현은 설계와는 다른 아이러니한 점이 발생한다.</p>
<p>간단히 생각해보면 노드를 방문해야 방문처리를 하지만, 구현에서는 해당 노드를 방문할 예정이라고 장바구니 담듯이 담아놓는 큐에 담을 때마다 방문처리를 하는 것이다. 왜냐하면 또다른 연결된 다른 노드에 의해 한번더 중복으로 특정 노드가 한번 더 담길 수 있기 때문이다.</p>
<p> 결론적으로 너비 우선 탐색으로 하는 BFS는 방문시에 방문체크를 한다고 하지만 정확히 말하면, 방문하기로 한 노드를 선별하는 타이밍에 방문 체크를 해줘야한다는 것이다. 바로 문제풀이로 넘어가보자</p>
<h1 id="🔍-problem"><strong>🔍 Problem</strong></h1>
<p><a href="https://www.acmicpc.net/problem/11724">백준 11724</a></p>
<hr>
<h1 id="📃-inputoutput"><strong>📃 Input&amp;Output</strong></h1>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/0973dd38-7a54-436d-b33c-946e2f075a1b/image.png" alt=""></p>
<hr>
<h1 id="🌁-문제-배경-">*<em>🌁 문제 배경 *</em></h1>
<p><strong>가. 문제 설명</strong>
BFS를 사용하여 총 경로의 개수를 구하는 것</p>
<p><strong>나. 접근 방법</strong>
BFS를 여러번 돌리며, <code>BFS가 실행된 개수 = 경로의 수</code> 라는 메커니즘으로 해결하였다.</p>
<p><strong>다. 문제 유형</strong>
BFS</p>
<hr>
<h1 id="📒-수도-코드"><strong>📒 수도 코드</strong></h1>
<p>*<em>가 *</em>
Bfs에 While문 씌우기</p>
<hr>
<h1 id="💻-code"><strong>💻 Code</strong></h1>
<pre><code class="language-java">import java.util.*;
import java.io.*;

public class P23 {
    static int cnt=0;

    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;

        int N,M;

        st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        int[][] graph = new int[N+1][N+1];
        boolean[] vst = new boolean[N+1];

        for (int i=0; i&lt;M; i++){
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            graph[a][b] = 1;
            graph[b][a] = 1;
        }

        BFS(vst, graph);

        bw.write(cnt+&quot;&quot;);
        bw.flush();

        bw.close();
    }

    static void BFS(boolean[] vst, int[][] graph){

        Queue&lt;Integer&gt; que = new LinkedList&lt;&gt;();

        for (int i=1; i&lt;vst.length; i++){
            if(!vst[i]){
                que.add(i);
                while(!que.isEmpty()){
                    int n = que.poll();
                    for (int j=1; j&lt;vst.length; j++){
                        if (graph[n][j] == 1 &amp;&amp; !vst[j]  ){
                            que.add(j);
                            vst[j]=true;
                        }
                    }
                }
                cnt++;
            }
        }
    }
}
</code></pre>
<h1 id="🤔-느낀점">🤔 느낀점</h1>
<p>재미를 느끼고 싶다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[병합 정렬]]></title>
            <link>https://velog.io/@halo_3735/merge-sort</link>
            <guid>https://velog.io/@halo_3735/merge-sort</guid>
            <pubDate>Tue, 30 Sep 2025 23:42:01 GMT</pubDate>
            <description><![CDATA[<h1 id="1-병합정렬이란">1. 병합정렬이란?</h1>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/e10f30bb-5c3b-4c27-99f7-689a0369b4f0/image.png" alt="">
<strong>나눠진 배열을 합치며 정렬해가는 알고리즘이다.</strong> 이게 무슨 말인고 하니, 순서대로 설명하자면 아래와 같다.</p>
<blockquote>
<ol>
<li>원소 한개씩으로 나눈다.</li>
<li>두개씩 묶는다.</li>
<li>1 묶은 배열을 정렬한다.</li>
<li>계속 두개씩 묶어서 계속 정렬한다.</li>
<li><strong>결국 정렬된 하나의 배열이 나온다.</strong></li>
</ol>
</blockquote>
<h1 id="2-수도코드">2. 수도코드</h1>
<p>25.10.02 - 08:30:00 작성예정</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[시간복잡도 logN과 N*logN]]></title>
            <link>https://velog.io/@halo_3735/%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84-logN%EA%B3%BC-NlogN</link>
            <guid>https://velog.io/@halo_3735/%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84-logN%EA%B3%BC-NlogN</guid>
            <pubDate>Mon, 29 Sep 2025 23:46:17 GMT</pubDate>
            <description><![CDATA[<h1 id="1-시간복잡도란">1. 시간복잡도란?</h1>
<p>특정 문제를 푸는데 컴퓨터가 실행하는 연산 횟수이다. 예를들어 특정 두 수의 크기를 한번 비교하면 한번의 연산이 이루어지는 것이다. 시간복잡도는 아래와 같이 3가지로 이루어진다. 해당 시간복잡도는 프로그램의 입력값의 길이에 의해 결정된다. 하지만 표기법에서는 입력값의 길이를 <code>n</code>으로 지정한다. 그리고 항상 최악의 경우를 고려한다.</p>
<h1 id="2-log-n">2. log N</h1>
<p>해당 시간복잡도의 대표적인 연산은 <code>이진탐색</code>이다.</p>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/9718a073-7574-4341-bd51-9e4b5329434c/image.png" alt=""></p>
<p>위에서 66을 찾기위해 중앙값과 비교하는 연산이 총 <code>3</code>번 일어나고 </p>
<h1 id="3-nlogn">3. N*logN</h1>
<p>해당 시간복잡도의 대표적인 연산은 <code>병합정렬</code>이다.<img src="https://velog.velcdn.com/images/halo_3735/post/9fcfda04-4d22-46d7-b032-c9c394788d49/image.png" alt="">
위에서 정렬하기 위해 각 층, 총 3개의 층에서 정렬이 한번씩 난다. 각 층마다 노드안에 포함되어 있는 숫자의 개수는 다르지만 층에서 봤을 때, 총8번의 정렬이 일어난다. 그리고 깊이는 3으로 <code>8x3</code>해서 총 24번이 일어난다. 시간복잡도로 따지면 <code>n=8</code>이므로 한층당 n번의 정렬이 일어나고 깊이는 <code>logn</code>이 된다. 따라서 병합정렬의 시간복잡도는 <code>NlogN</code>인 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[K번째 수 구하기 with 퀵정렬]]></title>
            <link>https://velog.io/@halo_3735/K%EB%B2%88%EC%A7%B8-%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-with-%ED%80%B5%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@halo_3735/K%EB%B2%88%EC%A7%B8-%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-with-%ED%80%B5%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Mon, 29 Sep 2025 22:57:21 GMT</pubDate>
            <description><![CDATA[<h1 id="🔍-problem"><strong>🔍 Problem</strong></h1>
<p><a href="https://www.acmicpc.net/problem/11004">백준 11004 K번째 수 구하기</a></p>
<hr>
<h1 id="📃-inputoutput"><strong>📃 Input&amp;Output</strong></h1>
<p><img src="https://velog.velcdn.com/images/halo_3735/post/bd4ee70b-4d5d-4fc9-b625-90be4007ce70/image.png" alt=""></p>
<hr>
<h1 id="🌁-문제-배경-">*<em>🌁 문제 배경 *</em></h1>
<p><strong>가. 문제 설명</strong>
정렬 문제이다. 하지만 필자는 퀵정렬을 학습하고자, 퀵정렬을 구현하였다.</p>
<p><strong>나. 접근 방법</strong></p>
<p><strong>다. 문제 유형</strong></p>
<hr>
<h1 id="📒-수도-코드"><strong>📒 수도 코드</strong></h1>
<p><strong>가. 퀵정렬을 한다. **
<a href="https://velog.io/@halo_3735/%ED%80%B5%EC%A0%95%EB%A0%AC-%EB%84%88-%EB%88%84%EA%B5%AC%EC%95%BC">🔥 퀵정렬이란?</a>
**나. 정렬된 배열에서 첫번째 입력 줄의 두번째 입력값을 idx로 하는 원소를 출력한다.</strong></p>
<hr>
<h1 id="💻-code"><strong>💻 Code</strong></h1>
<pre><code class="language-java">import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
import java.util.StringTokenizer;

public class P19 {

    static void quickSort(int[] arr){
        if(arr.length==1){
            return;
        }
        quickSort(arr, 0, arr.length-1);
    }

    static void quickSort(int[] arr, int start, int end) {
        int part2 = partition(arr,start,end); // part2 : 두번째 배열의 첫번째 idx
        if(start+1&lt;part2){
            quickSort(arr,start,part2-1);
        }
        if(part2&lt;end){
            quickSort(arr,part2,end);
        }

    }
    static int partition(int[] arr, int start, int end){
        int pivot = arr[(start+end)/2];

        while(start&lt;=end){
            while(arr[start]&lt;pivot) start++;
            while(arr[end]&gt;pivot) end--;
            if(start&lt;=end){
                swap(arr, start, end);
                start++;
                end--;
            }
        }

        return start;       // 두 번째 파티션의 첫 번째 index를 결국 start가 end를 넘어서 반환
    }

    static void swap(int[] arr, int start, int end){
        int tmp = arr[start];
        arr[start] = arr[end];
        arr[end] = tmp;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());
        int[] arr = new int[N];
        st = new StringTokenizer(br.readLine());
        for(int i=0; i&lt;N; i++){
            arr[i] = Integer.parseInt(st.nextToken());
        }
        quickSort(arr);
        bw.write((arr[M-1])+&quot;&quot;);

        bw.flush();
        bw.close();

    }
}
</code></pre>
<hr>
<h1 id="🤔-느낀점">🤔 느낀점</h1>
<table>
<thead>
<tr>
<th></th>
<th>Arrays.sort()</th>
<th>Collections.sort()</th>
</tr>
</thead>
<tbody><tr>
<td>알고리즘 이름</td>
<td>DualPivotQuicksort</td>
<td>TimeSort(삽입정렬+병합정렬)</td>
</tr>
<tr>
<td>시간복잡도</td>
<td>평균 : O(nlogn), 최악 : O(n^2)</td>
<td>All : O(nlogn)</td>
</tr>
</tbody></table>
<p>List정렬이 배열 정렬보다 더 안전하다고 한다. 그렇다면 배열을 리스트로 바꾸는 방법은 무엇일까</p>
<pre><code class="language-java">int[] arr = new int[]{1,2,4,1,2};
List&lt;Integer&gt; list = Arrays.stream(arr).boxed().toList()

Integer[] arr = ~;
list = Arrays.asList(arr);
</code></pre>
<p>결국 List는 래퍼클래스 자료형이 필요했던 것이다.</p>
]]></description>
        </item>
    </channel>
</rss>