<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>DONG_MIN.log</title>
        <link>https://velog.io/</link>
        <description>Developer보단 Engineer로 될래</description>
        <lastBuildDate>Wed, 05 Aug 2026 12:59:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>DONG_MIN.log</title>
            <url>https://velog.velcdn.com/images/dongmin_0204/profile/1be6947e-1ec9-4b79-9487-55cc42eff956/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. DONG_MIN.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dongmin_0204" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[AWS SAA] 6주차 - RDS / Aurora / DynamoDB / ElastiCache]]></title>
            <link>https://velog.io/@dongmin_0204/AWS-SAA-6%EC%A3%BC%EC%B0%A8-RDS-Aurora-DynamoDB-ElastiCache</link>
            <guid>https://velog.io/@dongmin_0204/AWS-SAA-6%EC%A3%BC%EC%B0%A8-RDS-Aurora-DynamoDB-ElastiCache</guid>
            <pubDate>Wed, 05 Aug 2026 12:59:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>이번 주 목표</strong>
SAA 시험에서 가장 자주 나오는 &quot;이 요구사항엔 어떤 DB를 써야 하나?&quot; 유형을 확실히 잡는다.</p>
<p><strong>글의 구성</strong></p>
<ul>
<li><strong>Part I. 개념편</strong> — 각 서비스가 무엇이고, 어떻게 동작하는가</li>
<li><strong>Part II. 비교편</strong> — 네 서비스를 나란히 놓고 판별하기</li>
<li><strong>Part III. 비용·최적화편</strong> — 같은 아키텍처에서 돈을 어떻게 아끼는가</li>
</ul>
</blockquote>
<hr>
<h2 id="0-들어가며">0. 들어가며</h2>
<h3 id="0-1-왜-db-비교가-saa의-핵심인가">0-1. 왜 DB 비교가 SAA의 핵심인가</h3>
<p>SAA 시험은 &quot;서비스 이름 암기&quot;가 아니라 <strong>요구사항 → 서비스 선택</strong> 문제로 나온다.
문제는 보통 이렇게 생겼다.</p>
<blockquote>
<p>&quot;한 스타트업이 모바일 게임을 운영한다. 사용자 프로필은 단순한 키-값 형태이고,
트래픽은 이벤트 때마다 수십 배로 튄다. 운영 인력은 최소화하고 싶다.
어떤 데이터베이스를 선택해야 하는가?&quot;</p>
</blockquote>
<p>여기서 &quot;단순한 키-값&quot;, &quot;트래픽 급변&quot;, &quot;운영 최소화&quot;라는 세 단어가 곧 <strong>DynamoDB</strong>를 가리킨다.
즉 시험은 <strong>키워드 → 서비스 매핑 능력</strong>을 묻는다.</p>
<p>DB 영역은 시험 도메인 1(복원력 있는 아키텍처), 도메인 2(고성능 아키텍처),
도메인 4(비용 최적화)에 모두 걸쳐 있어서 출제 비중이 특히 높다.</p>
<table>
<thead>
<tr>
<th>문제 속 키워드</th>
<th>정답 방향</th>
</tr>
</thead>
<tbody><tr>
<td>JOIN, 트랜잭션, 기존 MySQL/PostgreSQL 마이그레이션</td>
<td>RDS</td>
</tr>
<tr>
<td>고성능 관계형 + 글로벌 + 최소 운영 부담</td>
<td>Aurora</td>
</tr>
<tr>
<td>밀리초 지연, 서버리스, 무한 확장, 단순 키-값</td>
<td>DynamoDB</td>
</tr>
<tr>
<td>마이크로초 지연, 캐시, 세션 저장, 리더보드</td>
<td>ElastiCache (또는 DAX)</td>
</tr>
</tbody></table>
<h3 id="0-2-먼저-정리하고-갈-기본-용어">0-2. 먼저 정리하고 갈 기본 용어</h3>
<p>본문에서 계속 나올 단어들이다. 여기서 한 번 정리하고 시작하자.</p>
<table>
<thead>
<tr>
<th>용어</th>
<th>뜻</th>
<th>비유 / 예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>AZ (가용 영역)</strong></td>
<td>한 리전 안의 물리적으로 분리된 데이터센터 묶음</td>
<td>서울 리전 안에 데이터센터 건물이 4개 있는 것. 한 건물에 정전이 나도 나머지는 멀쩡</td>
</tr>
<tr>
<td><strong>동기(Synchronous) 복제</strong></td>
<td>원본과 복제본에 <strong>둘 다 저장이 끝나야</strong> 쓰기 성공으로 응답</td>
<td>계약서를 두 부 만들어 양쪽 다 도장 찍어야 계약 성립. 안전하지만 느림</td>
</tr>
<tr>
<td><strong>비동기(Asynchronous) 복제</strong></td>
<td>원본에만 저장하면 바로 성공 응답, 복제는 뒤따라감</td>
<td>일단 내 노트에 적고 나중에 친구에게 알려줌. 빠르지만 잠깐 정보가 다름</td>
</tr>
<tr>
<td><strong>최종 일관성(Eventual Consistency)</strong></td>
<td>지금은 값이 다를 수 있지만 시간이 지나면 결국 같아짐</td>
<td>방금 쓴 댓글이 새로고침하면 잠깐 안 보이는 현상</td>
</tr>
<tr>
<td><strong>강한 일관성(Strong Consistency)</strong></td>
<td>쓰기 직후 읽으면 <strong>무조건</strong> 최신값</td>
<td>계좌 잔액. 이체 직후 조회에 옛날 값이 나오면 큰일</td>
</tr>
<tr>
<td><strong>페일오버(Failover)</strong></td>
<td>주 서버가 죽었을 때 대기 서버가 역할을 넘겨받는 것</td>
<td>주전 선수 부상 시 벤치 선수 자동 투입</td>
</tr>
<tr>
<td><strong>엔드포인트(Endpoint)</strong></td>
<td>접속할 주소(DNS 이름)</td>
<td><code>mydb.abc123.ap-northeast-2.rds.amazonaws.com</code></td>
</tr>
<tr>
<td><strong>스로틀링(Throttling)</strong></td>
<td>허용 용량을 넘는 요청을 서비스가 거부하는 것</td>
<td>정원 초과라 입장 거부. 에러가 나면서 요청이 실패함</td>
</tr>
<tr>
<td><strong>인메모리(In-memory)</strong></td>
<td>디스크가 아니라 <strong>RAM</strong>에 데이터를 두는 것</td>
<td>디스크가 창고라면 RAM은 책상 위. 훨씬 빠르지만 좁고, 전원 나가면 사라짐</td>
</tr>
<tr>
<td><strong>프로비저닝(Provisioning)</strong></td>
<td>자원을 미리 정해서 할당해두는 것</td>
<td>식당에서 미리 자리 예약. 안 와도 자리값은 나감</td>
</tr>
<tr>
<td><strong>서버리스(Serverless)</strong></td>
<td>서버를 내가 관리하지 않고, 쓴 만큼만 내는 방식</td>
<td>서버가 없다는 뜻이 아니라 <strong>내가 신경 안 쓴다</strong>는 뜻</td>
</tr>
</tbody></table>
<!-- 📌 그림 자리 1 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/ccf63052-1d99-4e9c-b690-492540cc9ec8/image.png" alt="">!</p>
<blockquote>
<p><strong>그림 1.</strong> DB 선택 의사결정 트리
— &quot;관계형인가? → 예: RDS/Aurora, 아니오: DynamoDB → 캐시가 필요한가? → ElastiCache/DAX&quot;
형태의 플로우차트를 넣을 자리. draw.io 또는 Excalidraw로 제작 예정.</p>
</blockquote>
<hr>
<h1 id="part-i-개념편">Part I. 개념편</h1>
<hr>
<h2 id="1-amazon-rds--관리형-관계형-데이터베이스">1. Amazon RDS — 관리형 관계형 데이터베이스</h2>
<h3 id="1-1-관리형managed이-무슨-뜻인가">1-1. &quot;관리형(Managed)&quot;이 무슨 뜻인가</h3>
<p>학교 프로젝트에서 MySQL을 직접 써봤다면 이런 일을 해봤을 것이다.</p>
<ol>
<li>EC2 인스턴스를 켠다</li>
<li><code>apt install mysql-server</code>로 설치한다</li>
<li>설정 파일을 만지고 포트를 연다</li>
<li>보안 패치가 나오면 직접 업데이트한다</li>
<li>백업 스크립트를 크론탭에 등록한다</li>
<li>서버가 죽으면... 새벽에 일어나서 고친다</li>
</ol>
<p><strong>RDS는 3~6번을 AWS가 대신 해준다.</strong> 이게 &quot;관리형&quot;의 의미다.
지원 엔진은 6가지: <code>MySQL</code>, <code>PostgreSQL</code>, <code>MariaDB</code>, <code>Oracle</code>, <code>SQL Server</code>, <code>Db2</code>.</p>
<table>
<thead>
<tr>
<th>AWS가 대신 해줌 ✅</th>
<th>여전히 내 몫 ❌</th>
</tr>
</thead>
<tbody><tr>
<td>서버 프로비저닝, OS·DB 보안 패치</td>
<td>테이블 스키마 설계</td>
</tr>
<tr>
<td>자동 백업, 특정 시점 복구</td>
<td>느린 쿼리 튜닝, 인덱스 설계</td>
</tr>
<tr>
<td>모니터링 지표 수집</td>
<td>인스턴스 타입(크기) 선택</td>
</tr>
<tr>
<td>복제본·다중 AZ 구성 버튼 하나로</td>
<td>DB 파라미터 튜닝</td>
</tr>
<tr>
<td>—</td>
<td><strong>SSH 접속 불가</strong> (OS에 못 들어감)</td>
</tr>
</tbody></table>
<blockquote>
<p>⚠️ <strong>시험 함정 예시</strong>
&quot;DB 서버에 로그 수집 에이전트를 직접 설치해야 한다. 어떻게 해야 하는가?&quot;
→ RDS는 <strong>OS 접근이 막혀 있어서 불가능</strong>하다. 정답은 &quot;EC2에 DB를 직접 설치(self-managed)&quot;다.
RDS 문제에서 SSH, 커스텀 데몬 설치, OS 커널 설정 같은 말이 나오면 거의 함정이다.</p>
</blockquote>
<!-- 📌 그림 자리 2 -->
<p><a href="https://velog.velcdn.com/images/dongmin_0204/post/92f02c54-fbb2-4483-8bc2-ee3acd5c6af2/image.png"></a></p>
<blockquote>
<p><strong>그림 2.</strong> RDS 책임 공유 모델 — 하드웨어/OS/DB엔진/데이터를 계층으로 쌓고,
어디까지가 AWS 책임이고 어디부터가 사용자 책임인지 색으로 구분.</p>
</blockquote>
<h3 id="1-2-스토리지-오토스케일링--디스크가-알아서-늘어난다">1-2. 스토리지 오토스케일링 — 디스크가 알아서 늘어난다</h3>
<p>DB를 만들 때 &quot;스토리지 20GB&quot;처럼 용량을 정한다. 그런데 서비스가 잘 되면 데이터가 늘어난다.
디스크가 꽉 차면 DB는 그냥 멈춘다. 이걸 막아주는 기능이다.</p>
<p><strong>동작 조건 (셋 다 만족해야 발동)</strong></p>
<ul>
<li>여유 공간이 전체의 10% 미만</li>
<li>그 상태가 5분 이상 지속</li>
<li>마지막 증설 이후 6시간이 지남</li>
</ul>
<p><strong>예시</strong>: 100GB 디스크에서 여유가 8GB로 떨어지고 5분이 지나면 → AWS가 자동으로 늘려준다.</p>
<p><code>Maximum Storage Threshold</code>(최대 스토리지 임계값)를 설정해두면 무한정 늘어나는 걸 막을 수 있다.
버그로 로그가 폭주해서 디스크가 10TB까지 늘어나는 사고를 방지하는 안전장치다.</p>
<h3 id="1-3-읽기-전용-복제본-read-replica--읽기를-나눠-받는다">1-3. 읽기 전용 복제본 (Read Replica) — 읽기를 나눠 받는다</h3>
<p><strong>문제 상황</strong>: 쇼핑몰 DB 하나에 주문 처리(쓰기)와 마케팅팀 통계 쿼리(읽기)가 몰린다.
마케팅팀이 무거운 집계 쿼리를 돌릴 때마다 주문이 느려진다.</p>
<p><strong>해결</strong>: 읽기 전용 복사본을 따로 만들어서, 통계 쿼리는 그쪽으로 보낸다.</p>
<ul>
<li>목적: <strong>읽기 성능 확장(Scale Out)</strong> — 성능을 위한 기능</li>
<li>복제 방식: <strong>비동기</strong> → <strong>최종 일관성</strong><ul>
<li>즉 원본에 방금 들어온 주문이 복제본에는 몇 밀리초~몇 초 뒤에 보일 수 있다</li>
<li>통계·리포트에는 괜찮지만, &quot;결제 직후 잔액 조회&quot; 같은 데는 부적합</li>
</ul>
</li>
<li>개수: RDS 최대 5개(엔진마다 다름), Aurora는 최대 15개</li>
<li>위치: 같은 AZ / 다른 AZ / <strong>다른 리전</strong>까지 가능</li>
<li><strong>승격(Promote)</strong>: 복제본을 독립된 DB로 승격시킬 수 있다</li>
<li>애플리케이션이 <strong>읽기용 주소를 따로 알고</strong> 연결을 나눠줘야 한다 (코드 수정 필요)</li>
</ul>
<blockquote>
<p>💡 <strong>다른 리전 복제본 활용 예시</strong>
본사는 서울, 사용자 절반은 미국. 미국 리전에 읽기 복제본을 두면
미국 사용자의 조회 응답이 태평양을 왕복하지 않아 훨씬 빨라진다.</p>
</blockquote>
<h3 id="1-4-multi-az-배포--죽어도-살아남기">1-4. Multi-AZ 배포 — 죽어도 살아남기</h3>
<p><strong>문제 상황</strong>: 서울 리전의 한 데이터센터(AZ)에 장애가 났다. 내 DB가 거기 있었다.</p>
<p><strong>해결</strong>: 다른 AZ에 똑같은 DB를 하나 더 두고, 장애 시 자동으로 넘긴다.</p>
<ul>
<li>목적: <strong>고가용성(HA) / 재해 복구</strong> — 성능 확장이 <strong>아님</strong> (이게 제일 중요한 구분)</li>
<li>복제 방식: <strong>동기</strong> (원본과 대기본에 동시에 써야 성공 응답)</li>
<li>대기(Standby) 인스턴스는 <strong>평상시 읽기도 쓰기도 못 한다</strong>. 그냥 대기만 한다</li>
<li>장애 시 자동 페일오버, <strong>엔드포인트 주소는 그대로 유지</strong><ul>
<li>→ 애플리케이션 코드를 <strong>전혀 안 고쳐도</strong> 된다. DNS가 알아서 새 서버를 가리킨다</li>
</ul>
</li>
</ul>
<blockquote>
<p>⚠️ <strong>가장 많이 나오는 함정</strong>
&quot;읽기 성능이 부족하다. Multi-AZ를 켜면 되는가?&quot;
→ <strong>안 된다.</strong> 대기 인스턴스는 읽기를 안 받는다. 정답은 Read Replica.</p>
<p>반대로 &quot;AZ 장애에 대비해야 한다. Read Replica를 만들면 되는가?&quot;
→ 자동 페일오버가 없으므로 <strong>부적합</strong>. 정답은 Multi-AZ.</p>
</blockquote>
<!-- 📌 그림 자리 3 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/6baa8c00-bb69-4a7a-8391-a58acc81519d/image.png" alt=""></p>
<blockquote>
<p><strong>그림 3.</strong> 좌측에 Read Replica(비동기·읽기 분산), 우측에 Multi-AZ(동기·페일오버) 구조를 나란히 배치.
SAA에서 가장 많이 헷갈리는 두 개념이므로 반드시 시각화할 것.</p>
</blockquote>
<h4 id="📝-핵심-비교표">📝 핵심 비교표</h4>
<table>
<thead>
<tr>
<th>구분</th>
<th>Read Replica</th>
<th>Multi-AZ</th>
<th>Multi-Region</th>
</tr>
</thead>
<tbody><tr>
<td>목적</td>
<td>읽기 확장 (성능)</td>
<td>고가용성 (안정성)</td>
<td>재해 복구 + 지연 감소</td>
</tr>
<tr>
<td>복제</td>
<td>비동기 (최종 일관성)</td>
<td>동기 (강한 일관성)</td>
<td>비동기</td>
</tr>
<tr>
<td>읽기 가능</td>
<td>✅</td>
<td>❌ (대기만 함)</td>
<td>✅</td>
</tr>
<tr>
<td>자동 페일오버</td>
<td>❌ (수동 승격)</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>앱 코드 수정</td>
<td>필요 (읽기 주소 분리)</td>
<td>불필요</td>
<td>필요</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>Multi-AZ DB Cluster</strong>: 대기 인스턴스를 2개 두면서 <strong>읽기까지 받을 수 있게</strong> 만든 신형 옵션.
&quot;고가용성도 필요하고 읽기 성능도 필요하다&quot;는 문제에서 정답이 될 수 있다.
기존 Multi-AZ(대기본이 놀고 있음)의 아쉬움을 보완한 버전이라고 이해하면 된다.</p>
</blockquote>
<h3 id="1-5-백업과-복구">1-5. 백업과 복구</h3>
<p>두 가지 방식이 있고, 시험은 <strong>보존 기간</strong>으로 둘을 구분한다.</p>
<p><strong>① 자동 백업 (Automated Backup)</strong></p>
<ul>
<li>매일 1회 전체 스냅샷 + <strong>5분마다 트랜잭션 로그</strong>를 저장</li>
<li>덕분에 <strong>PITR(Point-In-Time Recovery, 특정 시점 복구)</strong> 가 가능하다<ul>
<li>예: &quot;오늘 오후 2시 37분에 실수로 DELETE를 날렸다&quot; → 2시 36분 상태로 되돌릴 수 있다</li>
</ul>
</li>
<li>보존 기간: <strong>1~35일</strong> (0으로 두면 자동 백업 꺼짐)</li>
<li>DB를 삭제하면 자동 백업도 같이 사라진다</li>
</ul>
<p><strong>② 수동 스냅샷 (Manual Snapshot)</strong></p>
<ul>
<li>내가 원할 때 직접 생성</li>
<li><strong>직접 지우기 전까지 무기한 보존</strong> — DB를 삭제해도 남아 있다</li>
<li>예: 마이그레이션 직전 안전장치, 감사(audit) 대비 장기 보관</li>
</ul>
<h3 id="1-6-rds-proxy--커넥션-폭주-막기">1-6. RDS Proxy — 커넥션 폭주 막기</h3>
<p><strong>문제 상황</strong>: Lambda 함수가 요청마다 DB에 접속한다. 트래픽이 몰려 Lambda가 1,000개로 늘어나면
DB 커넥션도 1,000개가 열린다. MySQL의 기본 최대 커넥션은 훨씬 작아서 DB가 뻗는다.
이걸 <strong>커넥션 폭증(connection storm)</strong> 이라고 한다.</p>
<p><strong>해결</strong>: 중간에 프록시를 두고 <strong>커넥션 풀(연결 재사용 창고)</strong> 을 관리한다.</p>
<ul>
<li>Lambda들은 프록시에 붙고, 프록시가 DB와의 실제 연결 몇 개를 돌려쓴다</li>
<li><strong>페일오버 시간을 최대 66% 단축</strong> (프록시가 새 마스터를 먼저 찾아준다)</li>
<li><strong>VPC 내부에서만 접근 가능</strong>, IAM 인증을 강제할 수 있음</li>
</ul>
<blockquote>
<p>🧪 <strong>내 논문 연결점</strong>: 서버리스 함수가 폭발적으로 스케일아웃될 때 DB 커넥션이 병목이 된다.
RDS Proxy는 이 &quot;다대일 자원 경합&quot;을 완충하는 계층이다.
오토스케일링이 컴퓨팅만 늘려도 <strong>하위 자원(커넥션)이 스케일되지 않으면 전체가 무너진다</strong>는
좋은 사례이기도 하다.</p>
</blockquote>
<h3 id="1-7-보안">1-7. 보안</h3>
<ul>
<li><strong>저장 시 암호화(at rest)</strong>: KMS(AES-256) 사용. <strong>DB 생성 시에만 켤 수 있다</strong><ul>
<li>이미 만든 비암호화 DB를 암호화하려면?
→ 스냅샷 생성 → <strong>암호화 옵션을 켜서 스냅샷 복사</strong> → 그 스냅샷으로 복원 (시험 단골)</li>
</ul>
</li>
<li><strong>전송 중 암호화(in transit)</strong>: SSL/TLS</li>
<li><strong>인증</strong>: 비밀번호 또는 <strong>IAM 데이터베이스 인증</strong><ul>
<li>IAM 인증은 15분짜리 임시 토큰을 발급받아 접속. DB 비밀번호를 코드에 안 박아도 된다</li>
</ul>
</li>
<li><strong>네트워크</strong>: 보안 그룹(인스턴스 레벨 방화벽)으로 제어. NACL은 서브넷 레벨이라 결이 다르다</li>
</ul>
<hr>
<h2 id="2-amazon-aurora--클라우드-네이티브-관계형-db">2. Amazon Aurora — 클라우드 네이티브 관계형 DB</h2>
<h3 id="2-1-aurora는-rds와-뭐가-다른가">2-1. Aurora는 RDS와 뭐가 다른가</h3>
<p>RDS는 <strong>기존 MySQL/PostgreSQL을 그대로 클라우드에 올린</strong> 것이다.
Aurora는 AWS가 <strong>스토리지 계층을 아예 새로 설계해서</strong> 만든 MySQL/PostgreSQL <strong>호환</strong> DB다.</p>
<p>&quot;호환&quot;이라는 말이 중요하다. 내부 구현은 완전히 다르지만
<strong>애플리케이션 입장에선 MySQL/PostgreSQL 드라이버를 그대로 쓸 수 있다.</strong></p>
<ul>
<li>MySQL 대비 최대 <strong>5배</strong>, PostgreSQL 대비 최대 <strong>3배</strong> 처리량</li>
<li>스토리지가 <strong>10GB에서 시작해 128TB까지 자동 확장</strong> — 용량을 미리 정할 필요가 없다</li>
<li>백업, 복구, 복제가 스토리지 계층에서 처리돼서 인스턴스 부담이 적다</li>
</ul>
<h3 id="2-2-아키텍처--6개-복제본-3개-az-쿼럼">2-2. 아키텍처 — 6개 복제본, 3개 AZ, 쿼럼</h3>
<p>Aurora의 모든 장점은 이 구조 하나에서 나온다. 핵심은 <strong>컴퓨팅과 스토리지의 분리</strong>다.</p>
<p>일반 DB는 서버 안에 디스크가 붙어 있다. Aurora는 <strong>DB 인스턴스(계산)</strong> 와
<strong>공유 스토리지 볼륨(저장)</strong> 이 분리돼 있고, 스토리지는 여러 서버에 흩어져 있다.</p>
<p><strong>쿼럼(Quorum)이란?</strong></p>
<blockquote>
<p>여러 복사본 중 &quot;몇 개가 동의하면 유효한 것으로 인정할지&quot;를 정한 정족수다.
국회 의결정족수와 같은 개념이다. 전부의 동의를 기다리지 않아서 빠르고,
일부가 죽어도 진행되니까 튼튼하다.</p>
</blockquote>
<p>Aurora의 규칙:</p>
<ul>
<li>데이터를 <strong>3개 AZ에 걸쳐 6개 복제본</strong>으로 저장</li>
<li><strong>쓰기</strong>: 6개 중 <strong>4개</strong>가 확인되면 성공 처리 (4/6 쿼럼)</li>
<li><strong>읽기</strong>: 6개 중 <strong>3개</strong>만 살아 있으면 가능 (3/6 쿼럼)</li>
</ul>
<p>이 규칙에서 자동으로 따라 나오는 결론:</p>
<ul>
<li>복제본 <strong>2개를 잃어도</strong> → 읽기·쓰기 <strong>모두 정상</strong> (4개 남았으니 쓰기 쿼럼 충족)</li>
<li>복제본 <strong>3개를 잃어도</strong> → <strong>읽기는 가능</strong> (3개 남았으니 읽기 쿼럼 충족)</li>
<li>AZ 하나가 통째로 날아가도(=2개 손실) 서비스가 안 멈춘다</li>
</ul>
<p>여기에 <strong>자가 복구(self-healing)</strong> 가 붙는다. 손상된 블록을 감지하면
다른 복제본에서 데이터를 가져와 알아서 고친다.</p>
<!-- 📌 그림 자리 4 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/548e632b-6c43-4eb9-844d-3bac0e491f77/image.png" alt=""></p>
<blockquote>
<p><strong>그림 4.</strong> Aurora 아키텍처 — 상단에 Writer 1개 + Reader N개,
하단에 3개 AZ에 걸친 공유 스토리지 볼륨(6개 복제본)을 그리고 화살표로 연결.
&quot;컴퓨팅 ↔ 스토리지 분리&quot;가 시각적으로 드러나야 함.</p>
</blockquote>
<h3 id="2-3-엔드포인트--어디로-접속할-것인가">2-3. 엔드포인트 — 어디로 접속할 것인가</h3>
<p>Aurora는 인스턴스가 여러 개(Writer 1개 + Reader N개)라서 접속 주소도 여러 종류다.</p>
<table>
<thead>
<tr>
<th>엔드포인트</th>
<th>역할</th>
<th>언제 쓰나</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Writer Endpoint</strong></td>
<td>항상 현재 마스터를 가리킴</td>
<td>INSERT/UPDATE/DELETE</td>
</tr>
<tr>
<td><strong>Reader Endpoint</strong></td>
<td>읽기 복제본들에 자동 분산</td>
<td>SELECT</td>
</tr>
<tr>
<td><strong>Custom Endpoint</strong></td>
<td>특정 인스턴스들만 묶어서 지정</td>
<td>분석 전용 대형 인스턴스 그룹</td>
</tr>
</tbody></table>
<p><strong>Writer Endpoint의 장점 예시</strong>: 마스터가 죽어서 복제본 하나가 승격돼도,
Writer Endpoint 주소는 그대로다. 애플리케이션은 아무것도 안 바꿔도 된다.</p>
<p><strong>Custom Endpoint 활용 예시</strong>: 일반 API용 인스턴스는 <code>db.r6g.large</code>,
데이터 분석팀용은 <code>db.r6g.8xlarge</code>로 두고, 분석팀에게만 큰 인스턴스를 가리키는
커스텀 엔드포인트를 준다. 무거운 쿼리가 일반 사용자에게 영향을 주지 않는다.</p>
<blockquote>
<p>⚠️ Reader Endpoint의 부하 분산은 <strong>연결(Connection) 단위</strong>이지 <strong>쿼리 단위가 아니다.</strong>
커넥션 풀을 쓰면 한 번 맺은 연결이 계속 유지되므로 분산이 고르지 않을 수 있다.</p>
</blockquote>
<h3 id="2-4-주요-기능">2-4. 주요 기능</h3>
<p><strong>Replica Auto Scaling</strong>
읽기 부하(CPU 사용률 등)에 따라 복제본 개수를 자동으로 늘리고 줄인다. 최대 15개.
→ 예: 평소 2개, 이벤트 때 8개, 끝나면 다시 2개.</p>
<p><strong>Global Database (글로벌 데이터베이스)</strong></p>
<ul>
<li>1개 주 리전(쓰기) + 최대 5개 보조 리전(읽기)</li>
<li>리전 간 복제 지연 <strong>1초 미만</strong></li>
<li>주 리전 장애 시 보조 리전 승격이 <strong>1분 미만</strong></li>
<li>예: 서비스 본진은 서울, 미국·유럽 사용자는 각 리전의 보조 클러스터에서 읽는다.
서울에 재해가 나면 미국 리전을 주 리전으로 승격한다.</li>
<li>👉 문제에 &quot;전 세계 사용자&quot;, &quot;읽기 지연 최소화&quot;, &quot;리전 장애 대비&quot;가 같이 나오면 정답</li>
</ul>
<p><strong>Backtrack (백트랙)</strong></p>
<ul>
<li>백업에서 복원하지 않고 <strong>DB를 과거 시점으로 되감기</strong> (Aurora MySQL)</li>
<li>복원은 새 클러스터를 만드는 거라 오래 걸리는데, 백트랙은 같은 클러스터를 되돌려서 훨씬 빠르다</li>
<li>예: 배포 스크립트가 잘못돼서 테이블을 날렸다 → 5분 전으로 되감기</li>
</ul>
<p><strong>Database Cloning (클로닝)</strong></p>
<ul>
<li><strong>Copy-on-Write</strong> 방식으로 클러스터를 복제한다<blockquote>
<p><strong>Copy-on-Write란?</strong> 처음엔 데이터를 복사하지 않고 원본을 같이 바라보다가,
<strong>수정이 일어난 블록만</strong> 그때 복사하는 기법. 초기 복사 비용이 거의 0이다.</p>
</blockquote>
</li>
<li>예: 1TB 운영 DB를 몇 분 만에 복제해 테스트 환경을 만든다. 안 건드린 데이터는 스토리지도 안 먹는다</li>
</ul>
<p><strong>Aurora Machine Learning</strong>
SQL 함수 호출만으로 SageMaker나 Comprehend 모델을 호출한다.
예: <code>SELECT sentiment(review_text) FROM reviews;</code> 형태로 감성 분석 결과를 바로 얻는다.</p>
<h3 id="2-5-aurora-serverless-v2">2-5. Aurora Serverless v2</h3>
<p><strong>ACU(Aurora Capacity Unit)</strong> 라는 단위로 용량을 표현한다.
1 ACU ≈ 2GiB 메모리 + 그에 상응하는 CPU/네트워크로 보면 된다.</p>
<ul>
<li>부하에 따라 <strong>초 단위로</strong> ACU를 올리고 내린다</li>
<li><strong>0 ACU까지 축소(scale-to-zero)</strong> 가능 → 아무도 안 쓰면 컴퓨팅 요금이 사실상 0</li>
<li>Aurora Serverless <strong>v1은 2025년 3월 31일 지원 종료</strong>, 현재는 v2가 표준</li>
<li>적합한 워크로드: 사내 관리자 페이지, 개발/스테이징 환경, 계절성이 큰 서비스</li>
</ul>
<p><strong>예시</strong>: 대학 수강신청 시스템. 1년 중 나흘만 트래픽이 폭발하고 나머지는 거의 놀고 있다.
프로비저닝 방식이면 피크에 맞춰 큰 인스턴스를 1년 내내 켜둬야 하지만,
Serverless v2는 그 나흘에만 확 올라간다.</p>
<h3 id="2-6-최신-동향-참고용-시험-비중은-낮음">2-6. 최신 동향 (참고용, 시험 비중은 낮음)</h3>
<ul>
<li><strong>Aurora Limitless Database</strong>: 샤딩(데이터를 여러 DB에 쪼개 넣는 것)을 자동화해 수평 확장 제공</li>
<li><strong>Aurora DSQL</strong>: PostgreSQL 호환 <strong>분산 SQL</strong> 데이터베이스.
여러 리전에서 <strong>동시에 쓰기(액티브-액티브)</strong> 가 가능하면서 강한 일관성을 유지한다.
Aurora와는 별개 제품이고 드롭인 대체재가 아니다(지원 안 되는 PostgreSQL 기능이 있음).</li>
</ul>
<hr>
<h2 id="3-amazon-dynamodb--서버리스-nosql">3. Amazon DynamoDB — 서버리스 NoSQL</h2>
<h3 id="3-1-nosql을-왜-쓰는가">3-1. NoSQL을 왜 쓰는가</h3>
<p>관계형 DB(RDS/Aurora)는 강력하지만 두 가지 한계가 있다.</p>
<ol>
<li><strong>수직 확장(Scale-Up) 위주</strong> — 더 큰 서버로 바꾸는 방식이라 한계가 있다</li>
<li><strong>스키마가 고정</strong> — 컬럼을 추가하려면 <code>ALTER TABLE</code>을 해야 하고, 큰 테이블에선 위험하다</li>
</ol>
<p>DynamoDB는 <strong>수평 확장(Scale-Out)</strong> 을 기본으로 한다. 서버를 계속 추가해 나가는 방식이라
이론상 용량과 처리량에 한계가 없다. 대신 <strong>JOIN이 없다.</strong></p>
<ul>
<li><strong>완전 서버리스</strong> — 인스턴스 타입을 고르지 않는다. 테이블만 만들면 끝</li>
<li>한 자릿수 <strong>밀리초</strong> 응답</li>
<li>기본적으로 <strong>3개 AZ에 자동 복제</strong> (설정할 필요조차 없다)</li>
<li>항목(Item) 최대 크기: <strong>400KB</strong></li>
</ul>
<p><strong>적합한 예시</strong>: 게임 사용자 프로필, 세션 데이터, IoT 센서 로그, 장바구니, 리더보드 원본 데이터
<strong>부적합한 예시</strong>: 여러 테이블을 JOIN해서 복잡한 집계를 내는 ERP, 회계 시스템</p>
<h3 id="3-2-데이터-모델--파티션-키와-정렬-키">3-2. 데이터 모델 — 파티션 키와 정렬 키</h3>
<p>DynamoDB의 기본 키는 두 가지 형태다.</p>
<p><strong>① 파티션 키만 (Simple Primary Key)</strong></p>
<pre><code>Users 테이블
PK: user_id
  &quot;u1001&quot; → { name: &quot;김동민&quot;, grade: 4 }
  &quot;u1002&quot; → { name: &quot;이철수&quot;, grade: 2, club: &quot;밴드부&quot; }   ← 속성이 달라도 됨</code></pre><p><strong>② 파티션 키 + 정렬 키 (Composite Primary Key)</strong></p>
<pre><code>Orders 테이블
PK: user_id, SK: order_date
  &quot;u1001&quot; / &quot;2026-08-01&quot; → { item: &quot;키보드&quot;, price: 89000 }
  &quot;u1001&quot; / &quot;2026-08-03&quot; → { item: &quot;마우스&quot;, price: 35000 }
  &quot;u1001&quot; / &quot;2026-08-05&quot; → { item: &quot;모니터&quot;, price: 320000 }</code></pre><p>이 구조면 &quot;u1001의 8월 주문 전체를 최신순으로&quot; 같은 <strong>범위 쿼리</strong>가 가능하다.</p>
<p><strong>파티션 키가 하는 일</strong>: 값을 해시 함수에 넣어서 <strong>데이터가 어느 물리 파티션(서버)에 저장될지</strong> 결정한다.
그래서 파티션 키가 곧 데이터 분산의 기준이 된다.</p>
<blockquote>
<p>⚠️ <strong>핫 파티션(Hot Partition)</strong>
특정 파티션 키에만 요청이 쏠리면 그 파티션 하나가 과부하돼 <strong>스로틀링</strong>이 난다.</p>
<p><strong>나쁜 예</strong>: 파티션 키를 <code>날짜</code>로 잡은 로그 테이블.
오늘 들어오는 모든 로그가 <code>&quot;2026-08-05&quot;</code> 하나에 몰린다. 다른 파티션은 놀고 하나만 죽는다.</p>
<p><strong>좋은 예</strong>: <code>날짜#기기ID</code> 처럼 카디널리티(값의 종류 수)가 높은 키를 쓴다.
요청이 여러 파티션에 고르게 흩어진다.</p>
</blockquote>
<!-- 📌 그림 자리 5 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/87ea58c5-19a2-4bbc-b272-bb73c08d130a/image.png" alt=""></p>
<blockquote>
<p><strong>그림 5.</strong> 파티션 키 해싱 → 여러 파티션으로 분산되는 구조.
잘 분산된 케이스와 핫 파티션 케이스를 좌우로 대비해서 그릴 것.</p>
</blockquote>
<h3 id="3-3-읽기-일관성-두-가지">3-3. 읽기 일관성 두 가지</h3>
<ul>
<li><strong>결과적 일관된 읽기 (기본값)</strong> — 방금 쓴 값이 아직 안 보일 수 있다 (보통 1초 이내에 반영)</li>
<li><strong>강력한 일관된 읽기</strong> — 항상 최신값을 보장한다</li>
</ul>
<p><strong>예시</strong>: 게시글 조회수는 결과적 일관성으로 충분하다. 잠깐 1 차이 나도 아무 문제없다.
반면 재고 수량 확인은 강력한 일관성이 필요하다. 재고가 0인데 1로 읽히면 초과 판매가 난다.</p>
<h3 id="3-4-보조-인덱스-secondary-index">3-4. 보조 인덱스 (Secondary Index)</h3>
<p><strong>왜 필요한가</strong>: DynamoDB는 기본적으로 <strong>파티션 키로만</strong> 조회할 수 있다.
<code>Users</code> 테이블의 PK가 <code>user_id</code>인데 &quot;이메일로 사용자 찾기&quot;를 하려면?
전체를 훑는 Scan을 해야 하는데 이건 느리고 비싸다. 그래서 <strong>다른 키로 조회할 인덱스</strong>를 만든다.</p>
<table>
<thead>
<tr>
<th></th>
<th>GSI (Global Secondary Index)</th>
<th>LSI (Local Secondary Index)</th>
</tr>
</thead>
<tbody><tr>
<td>파티션 키</td>
<td><strong>완전히 다른 속성 가능</strong></td>
<td>테이블과 <strong>반드시 동일</strong></td>
</tr>
<tr>
<td>정렬 키</td>
<td>자유롭게 지정</td>
<td>다르게 지정</td>
</tr>
<tr>
<td>생성 시점</td>
<td><strong>언제든 추가/삭제 가능</strong></td>
<td><strong>테이블 생성 시에만</strong></td>
</tr>
<tr>
<td>용량</td>
<td>자체 RCU/WCU를 따로 가짐</td>
<td>테이블 용량을 같이 씀</td>
</tr>
<tr>
<td>일관성</td>
<td>결과적 일관성만</td>
<td><strong>강력한 일관성 지원</strong></td>
</tr>
<tr>
<td>개수 제한</td>
<td>기본 20개</td>
<td>5개</td>
</tr>
<tr>
<td>크기 제한</td>
<td>없음</td>
<td>파티션 키당 10GB</td>
</tr>
</tbody></table>
<p><strong>GSI 예시</strong>: <code>Orders</code> 테이블(PK: user_id)에 &quot;상품별 주문 조회&quot;가 필요해졌다.
→ PK를 <code>product_id</code>로 한 GSI를 만든다. 운영 중에도 추가 가능.</p>
<p><strong>LSI 예시</strong>: <code>Orders</code> 테이블(PK: user_id, SK: order_date)에서
&quot;금액순 정렬&quot;도 필요하다 → PK는 그대로 <code>user_id</code>, SK만 <code>price</code>인 LSI.
<strong>단, 테이블 만들 때 같이 만들었어야 한다.</strong></p>
<blockquote>
<p>⚠️ <strong>시험 함정</strong>: &quot;이미 운영 중인 테이블에 새로운 조회 패턴이 필요해졌다&quot;
→ LSI는 나중에 못 만든다 → <strong>정답은 무조건 GSI</strong>.</p>
</blockquote>
<h3 id="3-5-부가-기능">3-5. 부가 기능</h3>
<p><strong>DAX (DynamoDB Accelerator)</strong></p>
<ul>
<li>DynamoDB <strong>전용</strong> 인메모리 캐시. 밀리초를 <strong>마이크로초</strong>로 줄인다</li>
<li>기본 TTL 5분</li>
<li>핵심: <strong>애플리케이션 코드를 안 고쳐도 된다.</strong> DynamoDB SDK 엔드포인트만 DAX로 바꾸면 끝</li>
<li>예시: 상품 상세 페이지가 같은 상품을 수천 번 조회 → DAX가 다 흡수</li>
</ul>
<p><strong>DynamoDB Streams</strong></p>
<ul>
<li>테이블의 항목 변경(추가/수정/삭제)을 <strong>시간순 이벤트 스트림</strong>으로 뽑아준다</li>
<li>Lambda를 트리거할 수 있고, 보존 기간은 24시간</li>
<li>예시: 신규 회원가입(항목 추가) → Streams → Lambda → 환영 이메일 발송</li>
</ul>
<p><strong>TTL (Time To Live)</strong></p>
<ul>
<li>항목에 만료 시각(Unix timestamp) 속성을 두면 지나서 자동 삭제된다</li>
<li>예시: 세션 데이터에 <code>expires_at = 지금+24시간</code>. 하루 뒤 알아서 사라진다</li>
</ul>
<p><strong>Global Tables</strong></p>
<ul>
<li>여러 리전에 <strong>액티브-액티브</strong>로 복제 (모든 리전에서 읽기·쓰기 다 가능)</li>
<li>사전 조건: <strong>DynamoDB Streams 활성화 필수</strong></li>
<li>Aurora Global Database는 주 리전만 쓰기가 되지만, 이건 양쪽 다 쓰기가 된다는 게 차이점</li>
</ul>
<p><strong>PITR / Transactions</strong></p>
<ul>
<li>PITR: 최대 35일 이내 임의 시점으로 복구</li>
<li>Transactions: 여러 항목·여러 테이블에 걸친 ACID 보장 (전부 성공 or 전부 실패)</li>
</ul>
<h4 id="dax-vs-elasticache--언제-무엇을">DAX vs ElastiCache — 언제 무엇을?</h4>
<table>
<thead>
<tr>
<th></th>
<th>DAX</th>
<th>ElastiCache</th>
</tr>
</thead>
<tbody><tr>
<td>캐싱 대상</td>
<td>DynamoDB 쿼리 결과 <strong>전용</strong></td>
<td>아무 데이터나</td>
</tr>
<tr>
<td>코드 변경</td>
<td><strong>불필요</strong> (드롭인)</td>
<td>필요 (캐시 조회/저장 로직 직접 구현)</td>
</tr>
<tr>
<td>적합</td>
<td>개별 항목 조회, Query/Scan 결과</td>
<td>집계 결과, 세션, 리더보드</td>
</tr>
</tbody></table>
<blockquote>
<p>문제에 &quot;DynamoDB인데 <strong>코드 수정 없이</strong> 읽기를 더 빠르게&quot;가 나오면 <strong>DAX</strong>다.</p>
</blockquote>
<hr>
<h2 id="4-amazon-elasticache--인메모리-캐시">4. Amazon ElastiCache — 인메모리 캐시</h2>
<h3 id="4-1-캐시가-하는-일">4-1. 캐시가 하는 일</h3>
<p><strong>문제 상황</strong>: 쇼핑몰 메인 페이지가 &quot;이번 주 인기 상품 TOP 10&quot;을 보여준다.
이걸 계산하려면 주문 테이블 수백만 건을 집계해야 한다. 그런데 이 결과는 1분에 한 번 바뀌어도 충분하다.
사용자가 10,000명이면 같은 계산을 10,000번 반복하는 셈이다.</p>
<p><strong>해결</strong>: 계산 결과를 메모리에 넣어두고 1분간 그냥 꺼내 쓴다. 이게 캐시다.</p>
<p>ElastiCache는 <strong>관리형 인메모리 데이터 스토어</strong>다. 지원 엔진은 3가지.</p>
<ul>
<li><strong>Valkey</strong> — Redis에서 갈라져 나온 오픈소스. 현재 AWS 권장 기본값</li>
<li><strong>Redis OSS</strong> — 오랫동안 표준이었던 엔진</li>
<li><strong>Memcached</strong> — 가장 단순한 순수 캐시</li>
</ul>
<blockquote>
<p><strong>Valkey가 왜 생겼나</strong>: Redis가 2024년에 라이선스를 바꾸면서 커뮤니티가 포크했고,
Linux Foundation이 관리하는 프로젝트가 Valkey다. <strong>Redis와 API가 완전 호환</strong>이라
클라이언트 코드를 안 고치고 바꿔 끼울 수 있다.</p>
</blockquote>
<p>캐시를 쓰는 이유는 크게 두 가지다.</p>
<ol>
<li><strong>DB 부하 감소</strong> → 읽기 성능 향상 (그리고 DB 인스턴스를 줄일 수 있어 비용도 절감)</li>
<li><strong>애플리케이션을 무상태(Stateless)로 만들기</strong><blockquote>
<p><strong>무상태란?</strong> 서버가 사용자별 정보를 자기 메모리에 안 들고 있는 상태.
세션을 EC2 메모리에 두면, 그 EC2가 죽거나 로드밸런서가 다른 서버로 보낼 때 로그인이 풀린다.
세션을 ElastiCache에 빼두면 어느 서버로 가도 로그인이 유지되고, 서버를 마음대로 늘렸다 줄일 수 있다.</p>
</blockquote>
</li>
</ol>
<!-- 📌 그림 자리 6 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/37808622-1fef-40f6-88c6-09275a148a68/image.png" alt=""></p>
<blockquote>
<p><strong>그림 6.</strong> ELB → EC2(Auto Scaling) → ElastiCache(캐시 히트/미스 분기) → RDS 구조도.
Cache Hit 경로와 Cache Miss 경로를 다른 색 화살표로 구분.</p>
</blockquote>
<h3 id="4-2-캐싱-전략">4-2. 캐싱 전략</h3>
<table>
<thead>
<tr>
<th>전략</th>
<th>동작</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Lazy Loading (Cache-Aside)</strong></td>
<td>캐시에 없을 때만 DB 조회 → 캐시에 저장</td>
<td>실제 요청된 데이터만 캐싱</td>
<td>첫 요청은 느림, 오래된 데이터 가능</td>
</tr>
<tr>
<td><strong>Write Through</strong></td>
<td>DB에 쓸 때마다 캐시도 같이 갱신</td>
<td>캐시가 항상 최신</td>
<td>쓰기 지연↑, 안 읽을 데이터도 캐싱</td>
</tr>
<tr>
<td><strong>TTL 만료</strong></td>
<td>일정 시간 후 자동 삭제</td>
<td>관리가 단순</td>
<td>TTL 값 설계가 필요</td>
</tr>
</tbody></table>
<p><strong>Lazy Loading 코드 흐름 (의사코드)</strong></p>
<pre><code>data = cache.get(key)
if data is None:          # 캐시 미스
    data = db.query(...)
    cache.set(key, data, ttl=60)
return data</code></pre><p><strong>캐시 미스(Cache Miss)</strong> = 캐시에 없어서 DB까지 갔다 오는 것
<strong>캐시 히트(Cache Hit)</strong> = 캐시에서 바로 나오는 것. 히트율이 높을수록 좋은 캐시다</p>
<blockquote>
<p>실무에선 <strong>Lazy Loading + TTL</strong> 조합이 가장 흔하다.
Write Through 단독은 &quot;한 번도 안 읽힐 데이터까지 캐시에 채우는&quot; 낭비가 있다.</p>
</blockquote>
<h3 id="4-3-redisvalkey-vs-memcached-⭐-단골-비교">4-3. Redis/Valkey vs Memcached (⭐ 단골 비교)</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>Redis / Valkey</th>
<th>Memcached</th>
</tr>
</thead>
<tbody><tr>
<td>Multi-AZ + 자동 페일오버</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>읽기 복제본</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>데이터 영속성</td>
<td>✅ (AOF, 스냅샷)</td>
<td>❌ (재시작하면 다 날아감)</td>
</tr>
<tr>
<td>백업/복원</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>데이터 구조</td>
<td>Set, Sorted Set, List, Hash 등</td>
<td>단순 키-값만</td>
</tr>
<tr>
<td>멀티스레드</td>
<td>부분 지원</td>
<td>✅ 멀티스레드</td>
</tr>
<tr>
<td>구조</td>
<td>샤딩 + 복제</td>
<td>순수 샤딩(분할)만</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>AOF(Append Only File)란?</strong> 캐시에 들어온 모든 쓰기 명령을 파일에 순서대로 기록해두는 방식.
서버가 재시작해도 이 파일을 재생하면 데이터가 복구된다. Memcached엔 이런 게 없다.</p>
</blockquote>
<p><strong>시험 판별법</strong></p>
<ul>
<li>&quot;고가용성 / 영속성 / 백업 / 리더보드(정렬)&quot; → <strong>Redis or Valkey</strong></li>
<li>&quot;단순하고 순수한 캐시 / 멀티스레드 / 데이터 잃어도 무방&quot; → <strong>Memcached</strong></li>
</ul>
<h3 id="4-4-대표-유스케이스">4-4. 대표 유스케이스</h3>
<ul>
<li><strong>세션 스토어</strong> — EC2가 여러 대여도 어느 서버로 가든 로그인 유지 (SAA 최다 출제)</li>
<li><strong>리더보드</strong> — Redis의 Sorted Set은 점수순 정렬이 O(log N)이라 실시간 순위에 최적</li>
<li><strong>DB 쿼리 결과 캐싱</strong> — 위의 &quot;인기 상품 TOP 10&quot; 케이스</li>
<li><strong>Pub/Sub 메시징</strong> — 채팅, 실시간 알림</li>
<li><strong>속도 제한(Rate Limiting)</strong> — &quot;이 IP는 1분에 100회까지&quot; 카운터</li>
</ul>
<hr>
<h1 id="part-ii-비교편">Part II. 비교편</h1>
<hr>
<h2 id="5-종합-비교표-한-장-요약">5. 종합 비교표 (한 장 요약)</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>RDS</th>
<th>Aurora</th>
<th>DynamoDB</th>
<th>ElastiCache</th>
</tr>
</thead>
<tbody><tr>
<td>유형</td>
<td>관계형</td>
<td>관계형 (클라우드 네이티브)</td>
<td>NoSQL (키-값/문서)</td>
<td>인메모리 캐시</td>
</tr>
<tr>
<td>서버리스</td>
<td>❌</td>
<td>✅ (Serverless v2)</td>
<td>✅</td>
<td>✅ (Serverless 모드)</td>
</tr>
<tr>
<td>지연 시간</td>
<td>수 ms</td>
<td>수 ms</td>
<td>한 자릿수 ms</td>
<td><strong>마이크로초 ~ 1ms</strong></td>
</tr>
<tr>
<td>스토리지</td>
<td>최대 64TB</td>
<td>10GB→128TB 자동</td>
<td>무제한 자동</td>
<td>메모리 크기까지</td>
</tr>
<tr>
<td>읽기 확장</td>
<td>복제본 5개</td>
<td>복제본 15개</td>
<td>자동 파티셔닝</td>
<td>복제본 (Redis/Valkey)</td>
</tr>
<tr>
<td>다중 리전</td>
<td>리전 간 복제본</td>
<td><strong>Global Database</strong> (주 리전만 쓰기)</td>
<td><strong>Global Tables</strong> (전 리전 쓰기)</td>
<td>Global Datastore</td>
</tr>
<tr>
<td>스키마</td>
<td>고정</td>
<td>고정</td>
<td>유연</td>
<td>N/A</td>
</tr>
<tr>
<td>JOIN</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>주용도</td>
<td>기존 앱 마이그레이션</td>
<td>고성능/글로벌 관계형</td>
<td>대규모 서버리스 앱</td>
<td>캐시/세션</td>
</tr>
</tbody></table>
<!-- 📌 그림 자리 7 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/b0109e54-2a15-4462-8d0a-fcd10e99dfb2/image.png" alt=""></p>
<blockquote>
<p><strong>그림 7.</strong> 4개 서비스를 축(관계형↔비관계형 / 낮은지연↔높은처리량)에 배치한 2×2 매트릭스.
마지막 복습용 한 장 요약 이미지.</p>
</blockquote>
<h2 id="6-시험-시나리오-→-정답-매핑-암기용">6. 시험 시나리오 → 정답 매핑 (암기용)</h2>
<table>
<thead>
<tr>
<th>시나리오</th>
<th>정답</th>
<th>왜?</th>
</tr>
</thead>
<tbody><tr>
<td>분석 쿼리가 운영 DB 성능을 떨어뜨린다</td>
<td>RDS <strong>Read Replica</strong></td>
<td>읽기를 분리하는 게 목적</td>
</tr>
<tr>
<td>AZ 장애에도 DB가 살아 있어야 한다</td>
<td>RDS <strong>Multi-AZ</strong></td>
<td>자동 페일오버가 필요</td>
</tr>
<tr>
<td>전 세계 읽기 지연 최소화 + 리전 장애 대비 (관계형)</td>
<td><strong>Aurora Global Database</strong></td>
<td>복제 지연 1초 미만</td>
</tr>
<tr>
<td>여러 리전에서 <strong>동시에 쓰기</strong>가 필요하다</td>
<td><strong>DynamoDB Global Tables</strong></td>
<td>액티브-액티브는 여기만</td>
</tr>
<tr>
<td>Lambda가 커넥션을 폭주시킨다</td>
<td><strong>RDS Proxy</strong></td>
<td>커넥션 풀링</td>
</tr>
<tr>
<td>운영 DB와 같은 테스트 환경을 빠르게</td>
<td><strong>Aurora Cloning</strong></td>
<td>Copy-on-Write</td>
</tr>
<tr>
<td>잘못된 배포로 데이터를 날렸다, 빨리 되돌려라</td>
<td><strong>Aurora Backtrack</strong></td>
<td>복원보다 훨씬 빠름</td>
</tr>
<tr>
<td>트래픽이 간헐적이고 유휴가 길다 (관계형)</td>
<td><strong>Aurora Serverless v2</strong></td>
<td>0 ACU까지 축소</td>
</tr>
<tr>
<td>서버리스 + 밀리초 + 단순 GET/PUT</td>
<td><strong>DynamoDB</strong></td>
<td>JOIN이 필요 없는 케이스</td>
</tr>
<tr>
<td>DynamoDB 읽기를 <strong>코드 수정 없이</strong> 더 빠르게</td>
<td><strong>DAX</strong></td>
<td>드롭인 캐시</td>
</tr>
<tr>
<td>DynamoDB 항목 변경 시 후속 처리가 필요</td>
<td><strong>DynamoDB Streams</strong></td>
<td>Lambda 트리거</td>
</tr>
<tr>
<td>이미 운영 중인 테이블에 새 조회 패턴 추가</td>
<td><strong>GSI</strong></td>
<td>LSI는 나중에 못 만듦</td>
</tr>
<tr>
<td>여러 EC2 간 세션 공유</td>
<td><strong>ElastiCache</strong></td>
<td>무상태 아키텍처</td>
</tr>
<tr>
<td>실시간 게임 순위표</td>
<td><strong>ElastiCache (Sorted Set)</strong></td>
<td>정렬 자료구조 필요</td>
</tr>
<tr>
<td>RDS를 나중에 암호화해야 한다</td>
<td>스냅샷 → <strong>암호화 복사</strong> → 복원</td>
<td>생성 시에만 켤 수 있으므로</td>
</tr>
</tbody></table>
<hr>
<h1 id="part-iii-비용·최적화편">Part III. 비용·최적화편</h1>
<blockquote>
<p>여기부터는 <strong>같은 아키텍처를 유지하면서 요금을 줄이는 방법</strong>이다.
SAA 도메인 4(비용 최적화)에서 직접 출제되는 영역이라 개념편과 분리해 정리했다.</p>
</blockquote>
<hr>
<h2 id="7-서비스별-과금-모델--무엇에-돈을-내는가">7. 서비스별 과금 모델 — 무엇에 돈을 내는가</h2>
<p>클라우드 비용을 이해하는 첫 단계는 <strong>&quot;무엇에 과금되는가(과금 축)&quot;</strong> 를 아는 것이다.
축을 알아야 어디를 줄일지 판단할 수 있다.</p>
<table>
<thead>
<tr>
<th>서비스</th>
<th>과금 축</th>
</tr>
</thead>
<tbody><tr>
<td>RDS</td>
<td>인스턴스 시간 + 스토리지 + 백업 + 데이터 전송</td>
</tr>
<tr>
<td>Aurora</td>
<td>인스턴스 시간(또는 ACU) + 스토리지 + <strong>I/O 요청 수</strong></td>
</tr>
<tr>
<td>DynamoDB</td>
<td>RCU/WCU(프로비저닝) 또는 요청 건당(온디맨드) + 스토리지</td>
</tr>
<tr>
<td>ElastiCache</td>
<td>노드 시간(노드 기반) 또는 <strong>GB-시간 + ECPU</strong>(서버리스)</td>
</tr>
</tbody></table>
<p><strong>핵심 구분: 프로비저닝형 vs 사용량 기반</strong></p>
<ul>
<li><strong>프로비저닝형</strong> — 미리 잡아둔 만큼 계속 낸다. 안 써도 낸다. 대신 단가가 싸다</li>
<li><strong>사용량 기반</strong> — 쓴 만큼만 낸다. 유휴 시간엔 거의 0. 대신 단가가 비싸다</li>
</ul>
<p>그래서 <strong>트래픽이 안정적이면 프로비저닝, 변동이 크면 사용량 기반</strong>이 유리하다.
이 교차점을 판단하는 게 비용 문제의 본질이다.</p>
<!-- 📌 그림 자리 8 -->
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/2f097163-3b5c-4a2d-a679-d05ef462ee80/image.png" alt=""></p>
<blockquote>
<p><strong>그림 8.</strong> x축 &quot;트래픽 변동성 / 가동률&quot;, y축 &quot;월 비용&quot;으로 두고
프로비저닝형 vs 서버리스형의 비용 곡선이 교차하는 지점(손익분기점)을 표시.</p>
</blockquote>
<h2 id="8-rds-비용-최적화">8. RDS 비용 최적화</h2>
<p><strong>① 개발/테스트 DB는 중지한다</strong>
스냅샷을 만들고 인스턴스를 중지하면 <strong>인스턴스 요금이 멈춘다</strong>.
(스토리지 요금은 계속 나가고, 최대 7일까지 중지 가능하다는 점은 주의)
→ 예: 팀 개발 DB를 퇴근 시간에 중지하는 Lambda 스케줄을 걸어둔다.</p>
<p><strong>② 리전 간(Cross-Region) 복제본 주의</strong>
같은 리전 AZ 간 복제 트래픽은 무료지만, <strong>리전 간 복제는 데이터 전송 요금</strong>이 붙는다.
&quot;글로벌 서비스를 만들었는데 비용이 예상보다 크다&quot; 유형의 문제에 자주 등장한다.</p>
<p><strong>③ Reserved Instance (예약 인스턴스)</strong>
1년/3년 약정으로 큰 폭 할인. 상시 돌아가는 운영 DB에 적합하다.
→ 판단 기준: <strong>1년 내내 켜둘 게 확실한가?</strong> 그렇다면 RI, 아니면 온디맨드.</p>
<p><strong>④ 백업 보존 기간 조정</strong>
자동 백업 35일이 항상 정답은 아니다. 규제 요건이 없다면 7일로 줄여도 된다.</p>
<p><strong>⑤ Graviton(ARM) 인스턴스 전환</strong>
같은 성능을 더 싸게 낸다. <code>db.r6g</code>, <code>db.r7g</code> 계열.</p>
<h2 id="9-aurora-비용-최적화">9. Aurora 비용 최적화</h2>
<p><strong>① Serverless v2 + scale-to-zero(0 ACU)</strong>
유휴가 긴 개발/스테이징 환경에서 &quot;안 쓰는 시간의 요금&quot;을 사실상 없앨 수 있다.
→ 예: 개발 DB를 프로비저닝 인스턴스로 켜두면 밤 12시간도 요금이 나가지만,
Serverless v2는 트래픽이 없으면 0 ACU로 내려간다.</p>
<p><strong>② Database Savings Plans</strong>
Serverless v2와 DSQL은 <strong>Reserved Instance 대상이 아니다.</strong>
할인 경로가 Savings Plans뿐이라는 게 시험 포인트가 될 수 있다.</p>
<p><strong>③ Cloning 활용</strong>
Copy-on-Write라 초기 스토리지 비용이 거의 안 든다.
1TB 운영 DB를 통째로 복사하는 것과 비교하면 차이가 크다.</p>
<p><strong>④ I/O-Optimized 구성</strong>
Aurora 표준 구성은 <strong>I/O 요청 수에 따라 과금</strong>된다.
읽기·쓰기가 아주 많은 워크로드라면 I/O 요금이 따로 안 붙는 구성이 총액에서 더 쌀 수 있다.
→ 판단 기준: 전체 비용에서 I/O가 차지하는 비중이 대략 25%를 넘어가는지.</p>
<h2 id="10-dynamodb-비용-최적화-⭐-출제-빈도-높음">10. DynamoDB 비용 최적화 (⭐ 출제 빈도 높음)</h2>
<h3 id="10-1-용량-모드-선택">10-1. 용량 모드 선택</h3>
<table>
<thead>
<tr>
<th></th>
<th>프로비저닝 모드</th>
<th>온디맨드 모드</th>
</tr>
</thead>
<tbody><tr>
<td>과금</td>
<td>확보한 RCU/WCU × 시간</td>
<td>실제 요청 건당</td>
</tr>
<tr>
<td>적합</td>
<td>예측 가능한 꾸준한 트래픽</td>
<td>예측 불가 / 급격한 스파이크</td>
</tr>
<tr>
<td>확장</td>
<td>Auto Scaling을 따로 설정</td>
<td>즉시 자동</td>
</tr>
<tr>
<td>비용</td>
<td>안정적 부하에서 크게 저렴</td>
<td>유휴 시 0에 가까움</td>
</tr>
<tr>
<td>리스크</td>
<td>용량 초과 시 <strong>스로틀링</strong></td>
<td>예상 못한 폭탄 요금</td>
</tr>
</tbody></table>
<p><strong>판단 예시</strong></p>
<ul>
<li>사내 근태 시스템: 평일 9~18시에 일정한 트래픽 → <strong>프로비저닝 + Auto Scaling</strong></li>
<li>신규 게임 런칭: 얼마나 올지 아무도 모름 → <strong>온디맨드</strong>로 시작, 패턴 파악 후 전환</li>
</ul>
<h3 id="10-2-rcuwcu-계산-규칙">10-2. RCU/WCU 계산 규칙</h3>
<p><strong>용량 단위(Capacity Unit)</strong> 는 DynamoDB의 처리량 화폐 단위다.</p>
<ul>
<li><strong>1 WCU</strong> = <strong>1KB</strong> 항목을 <strong>초당 1회 쓰기</strong></li>
<li><strong>1 RCU</strong> = <strong>4KB</strong> 항목을 <strong>초당 1회 강력한 일관된 읽기</strong>
= <strong>4KB 항목 초당 2회 결과적 일관된 읽기</strong></li>
<li>크기는 항상 <strong>올림</strong> 처리한다</li>
</ul>
<p><strong>계산 예제 1 — 쓰기</strong></p>
<blockquote>
<p>3KB짜리 항목을 초당 100회 써야 한다.
3KB ÷ 1KB = 3 → 올림해서 3 WCU × 100회 = <strong>300 WCU</strong></p>
</blockquote>
<p><strong>계산 예제 2 — 강력한 일관된 읽기</strong></p>
<blockquote>
<p>8KB짜리 항목을 초당 50회 강력한 일관성으로 읽는다.
8KB ÷ 4KB = 2 → 2 RCU × 50회 = <strong>100 RCU</strong></p>
</blockquote>
<p><strong>계산 예제 3 — 결과적 일관된 읽기 (같은 조건)</strong></p>
<blockquote>
<p>결과적 일관성은 절반만 쓴다 → 100 ÷ 2 = <strong>50 RCU</strong></p>
</blockquote>
<blockquote>
<p>💡 <strong>핵심</strong>: 강력한 일관성은 RCU를 <strong>2배</strong> 먹는다.
&quot;비용을 줄이려면?&quot; 하는 문제에서 요구사항이 허용한다면 <strong>결과적 일관된 읽기</strong>가 정답.</p>
</blockquote>
<h3 id="10-3-그-밖의-절감-수단">10-3. 그 밖의 절감 수단</h3>
<ul>
<li><strong>TTL 활성화</strong>: 만료 데이터를 자동 삭제해 스토리지 비용을 줄인다. 삭제에는 WCU가 안 든다</li>
<li><strong>DAX 도입</strong>: 반복 읽기를 캐시가 흡수 → <strong>RCU 소비 자체가 줄어든다</strong> (성능 + 비용 동시 개선)</li>
<li><strong>Scan 대신 Query</strong>: Scan은 테이블 전체를 읽어 RCU를 크게 소모한다.
100만 건 테이블에서 1건 찾자고 Scan을 쓰면 100만 건어치 RCU를 낸다</li>
<li><strong>GSI 남발 금지</strong>: GSI마다 별도 스토리지 + 별도 쓰기 용량이 발생한다.
테이블에 쓰기 1번 = GSI가 3개면 쓰기가 사실상 4번</li>
<li><strong>Standard-IA 테이블 클래스</strong>: 저장은 많이 하는데 조회가 드문 데이터(로그 아카이브 등)에 적합.
스토리지 단가가 싸고 요청 단가가 비싸다</li>
</ul>
<h2 id="11-elasticache-비용-최적화">11. ElastiCache 비용 최적화</h2>
<p><strong>① Valkey로 전환 — 사실상 무조건 이득</strong>
서버리스 기준 <strong>33%</strong>, 노드 기반 기준 <strong>20%</strong> 저렴하다.
Redis OSS와 API가 완전 호환이라 <strong>코드 수정 없이 무중단 전환</strong>이 된다.
예약 노드를 쓰고 있었다면 기존 할인율도 유지된다.</p>
<p><strong>② 서버리스 진입 장벽이 낮아졌다</strong>
Valkey 서버리스는 최소 스토리지가 <strong>100MB</strong>(Redis OSS의 1/10)라
<strong>월 $6 수준</strong>부터 시작할 수 있다. 사이드 프로젝트나 소규모 서비스에 부담이 적다.</p>
<p><strong>③ 서버리스 vs 노드 기반 — 어느 쪽이 싼가</strong></p>
<ul>
<li>서버리스 과금 = <strong>저장 데이터(GB-시간)</strong> + <strong>ECPU</strong><blockquote>
<p><strong>ECPU란?</strong> ElastiCache Processing Unit. 서버리스 전용 처리량 단위다.
<strong>1KB 전송당 1 ECPU</strong>로 계산한다. 4KB짜리 GET 하나 = 4 ECPU.
SORT나 Lua 스크립트처럼 CPU를 더 쓰는 명령은 그만큼 더 소모한다.</p>
</blockquote>
</li>
<li>트래픽이 <strong>안정적이고 상시 높다면 노드 기반이 더 쌀 수 있다</strong></li>
<li>반대로 <strong>변동이 크거나 유휴가 길면 서버리스</strong>가 유리하다</li>
<li>참고로 Valkey 8.0 기준 서버리스는 13분 안에 0 → 500만 RPS까지 스케일된다</li>
</ul>
<p><strong>④ 캐시 자체가 비용 절감 수단이다</strong>
캐시를 붙이면 DB 부하가 줄어서 <strong>RDS/Aurora 인스턴스 크기를 낮출 수 있다.</strong>
캐시 요금보다 줄어든 DB 요금이 더 큰 경우가 흔하다.
→ 문제에서 &quot;성능은 유지하면서 DB 비용을 줄여라&quot;가 나오면 캐시를 떠올릴 것.</p>
<h2 id="12-비용-관점-시나리오-매핑">12. 비용 관점 시나리오 매핑</h2>
<table>
<thead>
<tr>
<th>시나리오</th>
<th>정답</th>
</tr>
</thead>
<tbody><tr>
<td>개발 DB가 밤에는 안 쓰이는데 요금이 계속 나간다</td>
<td>스냅샷 후 <strong>인스턴스 중지</strong> / <strong>Aurora Serverless v2</strong></td>
</tr>
<tr>
<td>트래픽이 예측 불가해 용량 산정이 어렵다</td>
<td><strong>DynamoDB 온디맨드</strong></td>
</tr>
<tr>
<td>안정적인 상시 트래픽인데 온디맨드 요금이 부담된다</td>
<td><strong>프로비저닝 + Auto Scaling</strong></td>
</tr>
<tr>
<td>DynamoDB 읽기 비용을 절반으로 줄이고 싶다</td>
<td><strong>결과적 일관된 읽기</strong>로 전환</td>
</tr>
<tr>
<td>오래된 데이터가 쌓여 스토리지 비용이 는다</td>
<td><strong>DynamoDB TTL</strong></td>
</tr>
<tr>
<td>캐시 비용을 33% 줄이면서 코드는 그대로 두고 싶다</td>
<td><strong>Valkey로 전환</strong></td>
</tr>
<tr>
<td>DB 인스턴스를 키우지 않고 읽기 성능을 올리고 싶다</td>
<td><strong>ElastiCache / DAX / Read Replica</strong></td>
</tr>
<tr>
<td>Serverless v2를 쓰는데 할인 옵션이 필요하다</td>
<td><strong>Database Savings Plans</strong> (RI 불가)</td>
</tr>
<tr>
<td>1년 내내 켜둘 운영 DB의 요금을 줄이고 싶다</td>
<td><strong>Reserved Instance</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="13-오늘의-3줄-요약">13. 오늘의 3줄 요약</h2>
<ol>
<li><strong>Read Replica는 성능, Multi-AZ는 가용성</strong> — 이 구분만 확실해도 문제 여러 개가 풀린다.</li>
<li><strong>Aurora는 스토리지 계층이 다르다</strong> — 3개 AZ에 6개 복제본, 4/6 쓰기 쿼럼이 모든 장점의 근거다.</li>
<li><strong>DynamoDB는 접근 패턴을 먼저 설계</strong>한다 — 파티션 키 설계 실패 = 핫 파티션 = 스로틀링.</li>
</ol>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>AWS 공식 문서 — Amazon RDS / Aurora User Guide</li>
<li>AWS 공식 문서 — Amazon DynamoDB Developer Guide</li>
<li>AWS 공식 문서 — Amazon ElastiCache User Guide</li>
<li>AWS What&#39;s New — Announcing Amazon ElastiCache for Valkey (2024.10)</li>
<li>AWS Database Blog — ElastiCache Serverless 비용 최적화</li>
</ul>
<!-- 📌 이미지 폴더 구조 안내
images/
├── 01-db-decision-tree.png
├── 02-rds-shared-responsibility.png
├── 03-readreplica-vs-multiaz.png
├── 04-aurora-architecture.png
├── 05-dynamodb-partitioning.png
├── 06-elasticache-architecture.png
├── 07-db-comparison-summary.png
└── 08-cost-model-comparison.png
-->]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS SAA 스터디 2주차] VPC와 Subnet: 트래픽은 어디로 흐르는가]]></title>
            <link>https://velog.io/@dongmin_0204/AWS-SAA-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-VPC%EC%99%80-Subnet-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%9D%80-%EC%96%B4%EB%94%94%EB%A1%9C-%ED%9D%90%EB%A5%B4%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@dongmin_0204/AWS-SAA-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-VPC%EC%99%80-Subnet-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%9D%80-%EC%96%B4%EB%94%94%EB%A1%9C-%ED%9D%90%EB%A5%B4%EB%8A%94%EA%B0%80</guid>
            <pubDate>Wed, 08 Jul 2026 13:03:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>이 글의 목표</strong>
1주차에서 배운 Region과 AZ 위에, 실제로 리소스를 올릴 <strong>네트워크 공간</strong>을 직접 설계해 본다.</p>
<ul>
<li>VPC가 왜 필요한가, Default VPC로는 왜 부족한가</li>
<li>Subnet에서 IP 주소 5개가 사라지는 이유</li>
<li>Public Subnet과 Private Subnet을 나누는 진짜 기준은 무엇인가</li>
<li>Route Table, Internet Gateway, NAT Gateway가 만들어내는 트래픽 흐름</li>
<li>SAA-C03 실전 문제로 확인하는 출제 포인트</li>
</ul>
</blockquote>
<p>1주차에서 IAM이 &quot;누가 무엇을 할 수 있는가&quot;를 통제했다면, 2주차 VPC는 <strong>&quot;트래픽이 어디로 갈 수 있는가&quot;</strong> 를 통제합니다. 권한과 경로, 이 두 축이 AWS 보안의 뼈대입니다.</p>
<hr>
<h2 id="1-vpc는-내가-빌린-부지다">1. VPC는 내가 빌린 부지다</h2>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/5c964501-f951-420d-8ffa-9dcab33db917/image.png" alt=""></p>
<p>VPC(Virtual Private Cloud)는 AWS 안에 만드는 나만의 가상 네트워크입니다. 흔히 쓰는 비유가 잘 들어맞습니다.</p>
<ul>
<li><strong>AWS</strong> = 거대한 도시</li>
<li><strong>VPC</strong> = 내가 빌린 부지</li>
<li><strong>Subnet</strong> = 부지를 용도별로 나눈 구획</li>
<li><strong>EC2</strong> = 그 위에 짓는 건물</li>
</ul>
<p>중요한 건 EC2든 RDS든 <strong>반드시 VPC 안에서만 생성된다</strong>는 점입니다. 지금까지 네트워크 설정 없이 EC2를 잘 만들 수 있었던 이유는, AWS가 계정 생성 시 만들어 둔 <strong>Default VPC</strong>를 쓰고 있었기 때문입니다.</p>
<p>문제는 Default VPC가 <strong>Public Subnet만 가지고 있다</strong>는 것입니다. 여기에 웹 서버, 앱 서버, DB를 다 올려두면 셋 다 인터넷에서 도달 가능한 위치에 놓입니다. DB가 인터넷에 노출되어 있다는 뜻이고, 이건 사고 나기 딱 좋은 구조입니다.</p>
<p>그래서 <strong>Custom VPC</strong>를 직접 만들고, 인터넷이 닿아도 되는 영역과 닿으면 안 되는 영역을 갈라야 합니다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>Default VPC</th>
<th>Custom VPC</th>
</tr>
</thead>
<tbody><tr>
<td>생성</td>
<td>계정 생성 시 자동</td>
<td>직접 만듦</td>
</tr>
<tr>
<td>CIDR</td>
<td>172.31.0.0/16 고정</td>
<td>원하는 대로 (/16 ~ /28)</td>
</tr>
<tr>
<td>Subnet</td>
<td>각 AZ에 Public Subnet만</td>
<td>Public / Private / DB 등 자유 설계</td>
</tr>
<tr>
<td>IGW</td>
<td>이미 연결됨</td>
<td>직접 만들어 attach</td>
</tr>
<tr>
<td>Public IP 자동 할당</td>
<td>켜짐</td>
<td>기본 꺼짐</td>
</tr>
<tr>
<td>용도</td>
<td>빠른 실험</td>
<td>실제 서비스</td>
</tr>
</tbody></table>
<hr>
<h2 id="2-cidr-24인데-왜-251개인가">2. CIDR: /24인데 왜 251개인가</h2>
<p>이번 주에 가장 확실하게 잡고 넘어간 부분입니다.
<img src="https://velog.velcdn.com/images/dongmin_0204/post/47fd5ed8-6a4a-4de5-8736-6b511cded59d/image.png" alt=""></p>
<p><code>10.0.0.0/24</code> 서브넷을 만들면 IP는 총 <strong>256개</strong>입니다. 그런데 실제로 인스턴스에 붙일 수 있는 건 <strong>251개</strong>뿐입니다. 5개가 사라집니다.</p>
<p>전통적인 네트워크 상식으로는 예약 IP가 2개입니다. 네트워크 주소 하나, 브로드캐스트 주소 하나. 그런데 AWS는 여기에 <strong>3개를 더 가져갑니다.</strong></p>
<table>
<thead>
<tr>
<th>IP</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.0.0.0</code></td>
<td>Network address (전통)</td>
</tr>
<tr>
<td><code>10.0.0.1</code></td>
<td>VPC Router</td>
</tr>
<tr>
<td><code>10.0.0.2</code></td>
<td>Amazon DNS Server (base + 2)</td>
</tr>
<tr>
<td><code>10.0.0.3</code></td>
<td>AWS 예약 (future use)</td>
</tr>
<tr>
<td><code>10.0.0.255</code></td>
<td>Broadcast address (전통)</td>
</tr>
</tbody></table>
<p>즉 <strong>앞의 4개 + 마지막 1개 = 5개</strong>입니다. 여기서 두 가지 감각을 얻었습니다.</p>
<p><strong>첫째, VPC는 브로드캐스트를 지원하지 않는데도 <code>.255</code>를 예약합니다.</strong> 쓰지도 않을 걸 예약해 두는 이유는 기존 IP 설계 관행과 충돌하지 않기 위해서입니다. 호환성을 위한 자리 비움에 가깝습니다.</p>
<p><strong>둘째, <code>.1</code>과 <code>.2</code>가 예약이라는 건 VPC 라우터와 DNS가 실체를 가진 존재라는 뜻입니다.</strong> 서브넷 어디에 인스턴스를 띄우든 게이트웨이는 항상 <code>x.x.x.1</code>이고, DNS resolver는 항상 <code>VPC CIDR base + 2</code>입니다. 이건 나중에 EC2 안에서 <code>/etc/resolv.conf</code>를 열어 보면 그대로 확인됩니다.</p>
<h3 id="작은-서브넷일수록-손해가-크다">작은 서브넷일수록 손해가 크다</h3>
<p>서브넷 크기는 최소 <code>/28</code>, 최대 <code>/16</code>입니다.</p>
<table>
<thead>
<tr>
<th>Prefix</th>
<th>전체 IP</th>
<th>사용 가능</th>
<th>손실률</th>
</tr>
</thead>
<tbody><tr>
<td><code>/28</code></td>
<td>16</td>
<td>11</td>
<td>31%</td>
</tr>
<tr>
<td><code>/26</code></td>
<td>64</td>
<td>59</td>
<td>8%</td>
</tr>
<tr>
<td><code>/24</code></td>
<td>256</td>
<td>251</td>
<td>2%</td>
</tr>
<tr>
<td><code>/20</code></td>
<td>4,096</td>
<td>4,091</td>
<td>0.1%</td>
</tr>
</tbody></table>
<p><code>/28</code>을 쓰면 세 개 중 하나가 날아갑니다. 아껴 쓰겠다고 서브넷을 잘게 쪼개면 오히려 낭비가 커집니다. 실무에서 <code>/24</code>를 기본 단위로 많이 잡는 이유가 여기 있습니다.</p>
<hr>
<h2 id="3-subnet-az에-묶인다">3. Subnet: AZ에 묶인다</h2>
<p>Subnet은 VPC의 IP 대역을 잘라낸 조각입니다. 여기서 반드시 기억할 제약이 하나 있습니다.</p>
<blockquote>
<p><strong>하나의 Subnet은 반드시 하나의 AZ 안에만 존재한다.</strong></p>
</blockquote>
<p>Subnet은 AZ 경계를 넘을 수 없습니다. 그래서 고가용성을 원한다면 <strong>같은 역할의 서브넷을 AZ마다 하나씩</strong> 만들어야 합니다. Public 두 개, Private 두 개, DB 두 개, 이런 식으로 짝을 맞추는 구성이 표준처럼 굳어진 이유입니다.</p>
<pre><code>VPC (10.0.0.0/16)
├─ AZ-a
│  ├─ Public  Subnet 1  (10.0.0.0/24)
│  ├─ Private Subnet 1  (10.0.2.0/24)
│  └─ DB      Subnet 1  (10.0.4.0/24)
└─ AZ-c
   ├─ Public  Subnet 2  (10.0.1.0/24)
   ├─ Private Subnet 2  (10.0.3.0/24)
   └─ DB      Subnet 2  (10.0.5.0/24)</code></pre><h3 id="public과-private을-나누는-기준">Public과 Private을 나누는 기준</h3>
<p>여기서 오해하기 쉬운 지점입니다. 서브넷을 만들 때 &quot;Public&quot;이라는 체크박스를 켜는 게 아닙니다.</p>
<blockquote>
<p><strong>Route Table에 Internet Gateway로 가는 경로가 있으면 Public Subnet, 없으면 Private Subnet입니다.</strong></p>
</blockquote>
<p>서브넷의 성격은 <strong>라우팅으로 결정되는 결과</strong>이지, 서브넷 자체의 속성이 아닙니다. 이름을 <code>public-subnet</code>이라고 지어도 IGW 경로가 없으면 그건 Private Subnet입니다.</p>
<hr>
<h2 id="4-route-table-정보가-없으면-드롭된다">4. Route Table: 정보가 없으면 드롭된다</h2>
<p>Route Table은 &quot;이 목적지로 가는 트래픽은 저기로 보내라&quot;는 규칙 목록입니다. 모든 서브넷은 정확히 하나의 Route Table에 연결됩니다. 명시적으로 연결하지 않으면 VPC의 <strong>Main Route Table</strong>이 자동 적용됩니다.</p>
<h3 id="local-route는-지울-수-없다">Local route는 지울 수 없다</h3>
<p>VPC를 만들면 Route Table에 이 규칙이 자동으로 박힙니다.</p>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.0.0.0/16</code></td>
<td><code>local</code></td>
</tr>
</tbody></table>
<p>이 <code>local</code> 경로는 <strong>삭제할 수 없습니다.</strong> VPC 안의 모든 서브넷은 서로 통신 가능하다는 뜻이고, 이건 라우팅으로 막을 수 없다는 뜻이기도 합니다. 서브넷 사이의 접근 통제는 Security Group이나 NACL로 해야 합니다. (뒤의 SAA 문제에서 그대로 나옵니다.)</p>
<h3 id="public-route-table">Public Route Table</h3>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.0.0.0/16</code></td>
<td><code>local</code></td>
</tr>
<tr>
<td><code>0.0.0.0/0</code></td>
<td><code>igw-xxxx</code></td>
</tr>
</tbody></table>
<p><code>0.0.0.0/0</code>은 &quot;나머지 전부&quot;라는 뜻입니다. 그런데 <code>10.0.0.0/16</code>도 <code>0.0.0.0/0</code>에 포함되지 않나요? 여기서 <strong>LPM(Longest Prefix Match)</strong> 이 등장합니다.</p>
<p>라우팅은 <strong>prefix가 긴 규칙, 즉 범위가 좁은 규칙을 먼저 적용</strong>합니다. <code>/16</code>이 <code>/0</code>보다 길기 때문에, VPC 내부로 가는 트래픽은 먼저 <code>local</code>로 처리되고, 거기 걸리지 않은 나머지만 IGW로 나갑니다. 그래서 <code>0.0.0.0/0</code>은 &quot;모든 트래픽&quot;이 아니라 실질적으로 &quot;그 외 전부&quot;로 동작합니다.</p>
<h3 id="private-route-table">Private Route Table</h3>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.0.0.0/16</code></td>
<td><code>local</code></td>
</tr>
</tbody></table>
<p><code>0.0.0.0/0</code> 규칙이 없습니다. 목적지를 모르는 트래픽은 어디로 보낼지 결정할 수 없으므로 <strong>그냥 드롭됩니다.</strong></p>
<p><strong>&quot;정보가 없으면 드롭&quot;</strong> 이게 AWS 망 분리의 핵심입니다. Security Group이나 NACL로 막는 게 아니라, 애초에 나갈 길 자체를 만들어 주지 않는 것입니다.</p>
<hr>
<h2 id="5-internet-gateway-문을-다는-것과-길을-내는-것">5. Internet Gateway: 문을 다는 것과 길을 내는 것</h2>
<p>IGW는 VPC와 인터넷을 잇는 관문입니다. VPC당 하나만 붙일 수 있습니다.</p>
<p>여기서 초보자가 흔히 막히는 지점이 있습니다.</p>
<blockquote>
<p><strong>IGW를 VPC에 attach했다고 해서 인터넷이 연결되는 게 아니다.</strong></p>
</blockquote>
<p>인스턴스가 인터넷과 통신하려면 세 가지가 <strong>전부</strong> 갖춰져야 합니다.</p>
<ol>
<li>IGW가 VPC에 attach되어 있을 것</li>
<li>서브넷의 Route Table에 <code>0.0.0.0/0 → IGW</code> 경로가 있을 것</li>
<li>인스턴스에 <strong>Public IP 또는 Elastic IP</strong>가 붙어 있을 것</li>
</ol>
<p>문(IGW)을 달았다고 길(Route)이 생기는 게 아니고, 길이 있다고 주소(Public IP)가 생기는 것도 아닙니다. 셋 중 하나라도 빠지면 통신은 실패합니다. 트러블슈팅할 때 이 세 가지를 순서대로 확인하는 습관을 들이면 좋습니다.</p>
<hr>
<h2 id="6-nat-gateway-private-subnet이-인터넷을-쓰는-법">6. NAT Gateway: Private Subnet이 인터넷을 쓰는 법</h2>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/12bfa309-e15e-4edb-a609-2fa1dfbfd766/image.png" alt=""></p>
<p>Private Subnet의 인스턴스도 인터넷이 필요합니다. <code>apt update</code>도 해야 하고, 외부 API도 호출해야 합니다. 하지만 인터넷에서 <strong>들어오는</strong> 접근은 막아야 합니다.</p>
<p>즉 필요한 건 <strong>outbound만 열린 단방향 통로</strong>이고, 그게 NAT Gateway입니다.</p>
<h3 id="흐름을-따라가-보면">흐름을 따라가 보면</h3>
<p>이번 주에 가장 헷갈렸던 부분인데, 정리하니 명확해졌습니다. <strong>Private Subnet의 트래픽은 결국 Public Subnet의 라우팅을 타고 나갑니다.</strong></p>
<pre><code>[Private Subnet의 EC2]
        │  ① Private Route Table: 0.0.0.0/0 → NAT Gateway
        ▼
[NAT Gateway]  ← 이 친구는 Public Subnet에 산다
        │  ② Source IP를 NAT의 Elastic IP로 변환
        │  ③ Public Route Table: 0.0.0.0/0 → IGW
        ▼
[Internet Gateway]
        ▼
     [Internet]</code></pre><p>핵심은 두 가지입니다.</p>
<p><strong>첫째, NAT Gateway는 Public Subnet에 배치합니다.</strong> Private Subnet에 두면 NAT 자신이 인터넷으로 나갈 길이 없습니다. 처음엔 이게 직관에 반하는 느낌인데, &quot;NAT는 Private을 위한 장비지만, NAT 자신은 인터넷에 닿아야 한다&quot;고 생각하면 자연스럽습니다.</p>
<p><strong>둘째, NAT Gateway는 Elastic IP를 하나 물고 있습니다.</strong> Private 인스턴스가 보낸 패킷의 출발지 주소를 이 EIP로 바꿔 치기 때문에, 외부 서버는 응답을 NAT로 돌려보냅니다. 반대로 외부에서 먼저 연결을 시작할 방법은 없습니다. 여기서 <strong>단방향</strong>이 성립합니다.</p>
<h3 id="고가용성-함정">고가용성 함정</h3>
<p>NAT Gateway는 <strong>AZ 단위 리소스</strong>입니다. AZ-a에 NAT 하나만 두고 두 AZ의 Private Subnet이 모두 그걸 바라보게 하면, AZ-a가 죽는 순간 <strong>AZ-c의 인스턴스도 인터넷이 끊깁니다.</strong></p>
<p>프로덕션에서는 <strong>AZ마다 NAT Gateway를 하나씩</strong> 두고, 각 AZ의 Private Route Table이 자기 AZ의 NAT를 가리키게 합니다. 가용성이 올라가는 대신 비용도 올라갑니다.</p>
<h3 id="비용-감각">비용 감각</h3>
<p>NAT Gateway는 두 가지 요금이 붙습니다.</p>
<ul>
<li><strong>시간당 요금</strong> (인스턴스처럼 켜져 있는 동안 계속)</li>
<li><strong>처리 데이터 GB당 요금</strong></li>
</ul>
<p>AZ마다 하나씩 두면 시간당 요금도 그만큼 곱해집니다. 그리고 S3나 DynamoDB로 나가는 트래픽까지 NAT를 태우면 데이터 처리 요금이 눈덩이처럼 불어납니다. <strong>이 지점이 SAA 비용 최적화 문제의 단골 소재</strong>이고, 정답은 대개 VPC Endpoint입니다. 바로 아래에서 봅니다.</p>
<h3 id="nat-gateway-vs-nat-instance">NAT Gateway vs NAT Instance</h3>
<table>
<thead>
<tr>
<th></th>
<th>NAT Gateway</th>
<th>NAT Instance</th>
</tr>
</thead>
<tbody><tr>
<td>관리</td>
<td>AWS 관리형</td>
<td>직접 EC2 운영</td>
</tr>
<tr>
<td>대역폭</td>
<td>최대 100 Gbps 자동 확장</td>
<td>인스턴스 타입에 종속</td>
</tr>
<tr>
<td>가용성</td>
<td>AZ 내 자동 이중화</td>
<td>직접 구성</td>
</tr>
<tr>
<td>Security Group</td>
<td>붙일 수 없음</td>
<td>붙일 수 있음</td>
</tr>
<tr>
<td>Source/Dest Check</td>
<td>해당 없음</td>
<td><strong>반드시 비활성화</strong></td>
</tr>
<tr>
<td>Bastion 겸용</td>
<td>불가</td>
<td>가능</td>
</tr>
</tbody></table>
<p>시험에서 NAT Instance가 정답인 경우는 거의 없습니다. &quot;운영 부담 최소화&quot;라는 문구가 보이면 NAT Gateway입니다.</p>
<hr>
<h2 id="7-db-subnet은-route-table을-건드리지-않는다">7. DB Subnet은 Route Table을 건드리지 않는다</h2>
<p>Public, Private까지 만들었으면 DB 전용 서브넷이 남습니다. 결론부터 말하면 <strong>DB Subnet은 Route Table을 따로 설정할 필요가 없습니다.</strong></p>
<p>&quot;RDS도 패치와 업데이트가 필요할 텐데?&quot;라는 의문이 드는데, RDS는 관리형 서비스라서 <strong>AWS 내부 인프라를 통해 스스로 업데이트</strong>합니다. 사용자의 VPC 라우팅을 거치지 않습니다.</p>
<p>그래서 DB Subnet은 아무 Route Table에도 연결하지 않고 놔두면 됩니다. 그러면 Main Route Table이 적용되고, 거기엔 <code>10.0.0.0/16 → local</code> 하나만 있습니다. VPC 내부 통신만 되고 인터넷은 완전히 차단된 상태, 딱 원하던 그림입니다.</p>
<hr>
<h2 id="8-vpc-endpoint-nat를-우회하는-지름길">8. VPC Endpoint: NAT를 우회하는 지름길</h2>
<p>Private Subnet의 EC2가 S3에 접근해야 한다고 해봅시다. 순진하게 생각하면 NAT Gateway를 태워야 합니다. 그런데 S3는 AWS 서비스인데, 굳이 인터넷 밖으로 나갔다 들어와야 할까요?</p>
<p><strong>VPC Endpoint</strong>는 VPC에서 AWS 서비스로 가는 <strong>프라이빗 경로</strong>를 만들어 줍니다. 인터넷도 NAT도 거치지 않습니다.</p>
<table>
<thead>
<tr>
<th></th>
<th>Gateway Endpoint</th>
<th>Interface Endpoint (PrivateLink)</th>
</tr>
</thead>
<tbody><tr>
<td>대상</td>
<td><strong>S3, DynamoDB만</strong></td>
<td>대부분의 AWS 서비스</td>
</tr>
<tr>
<td>동작 방식</td>
<td>Route Table에 경로 추가</td>
<td>서브넷에 ENI 생성</td>
</tr>
<tr>
<td>요금</td>
<td><strong>무료</strong></td>
<td>시간당 + 데이터 처리 요금</td>
</tr>
</tbody></table>
<p>Gateway Endpoint가 시험에서 특히 자주 나옵니다. <strong>S3나 DynamoDB + 프라이빗 접근 + 비용 최적화</strong> 조합이 보이면 거의 정답입니다. 무료인데다 NAT 데이터 처리 요금까지 없애 주니 이길 수가 없습니다.</p>
<hr>
<h2 id="9-security-group-vs-nacl-한-장-정리">9. Security Group vs NACL 한 장 정리</h2>
<p>라우팅이 &quot;길&quot;이라면, 이 둘은 &quot;검문소&quot;입니다.</p>
<table>
<thead>
<tr>
<th></th>
<th>Security Group</th>
<th>Network ACL</th>
</tr>
</thead>
<tbody><tr>
<td>적용 단위</td>
<td>인스턴스(ENI)</td>
<td>Subnet</td>
</tr>
<tr>
<td>규칙</td>
<td><strong>Allow만</strong></td>
<td>Allow + Deny</td>
</tr>
<tr>
<td>상태</td>
<td><strong>Stateful</strong> (응답 자동 허용)</td>
<td><strong>Stateless</strong> (양방향 명시)</td>
</tr>
<tr>
<td>평가</td>
<td>모든 규칙 종합</td>
<td>번호 낮은 순서대로, 먼저 매칭되면 종료</td>
</tr>
<tr>
<td>기본값</td>
<td>inbound 전부 차단, outbound 전부 허용</td>
<td>Default는 전부 허용, Custom은 전부 차단</td>
</tr>
</tbody></table>
<p>여기서 시험에 직결되는 두 가지가 있습니다.</p>
<p><strong>Security Group은 Deny 규칙을 쓸 수 없습니다.</strong> &quot;특정 대상만 차단&quot;은 SG로 구현할 수 없고 NACL을 써야 합니다.</p>
<p><strong>Security Group은 다른 Security Group을 source로 지정할 수 있습니다.</strong> IP 대역이 아니라 <code>sg-app-server</code>를 소스로 걸면, 그 SG를 단 인스턴스만 통과합니다. 인스턴스가 늘어나든 IP가 바뀌든 규칙을 고칠 필요가 없습니다. 이걸 <strong>Security Group Referencing</strong>이라고 부르는데, AWS가 가장 권장하는 패턴입니다.</p>
<hr>
<h2 id="시험에서-이렇게-나온다-saa-c03-실전">시험에서 이렇게 나온다 (SAA-C03 실전)</h2>
<p>교재로 쓰는 SAA-C03 샘플에서 이번 주 범위에 걸리는 문제들을 뽑았습니다.</p>
<h3 id="q1-private-접근-경로-만들기">Q1. Private 접근 경로 만들기</h3>
<blockquote>
<p>VPC 안의 EC2가 S3 버킷의 로그를 처리해야 한다. <strong>인터넷을 거치지 않고</strong> S3에 접근하려면?</p>
<p>A. S3용 Gateway VPC Endpoint 생성
B. CloudWatch Logs로 스트리밍 후 S3로 export
C. EC2에 Instance Profile(IAM Role) 생성
D. PrivateLink를 쓰는 API Gateway 구성</p>
</blockquote>
<p><strong>정답: A</strong></p>
<p><strong>함정은 C입니다.</strong> IAM Role은 <strong>권한</strong>을 주지, <strong>경로</strong>를 만들지 않습니다. Role이 있어도 네트워크 경로가 없으면 패킷은 목적지에 닿지 못합니다.</p>
<blockquote>
<p><strong>권한(IAM)과 경로(Routing)는 별개의 축이다.</strong></p>
</blockquote>
<p>1주차 IAM과 2주차 VPC가 여기서 정확히 맞물립니다. 이 문제 하나만 제대로 이해해도 두 주차를 다 소화한 셈입니다.</p>
<hr>
<h3 id="q2-nat-gateway-비용-최적화">Q2. NAT Gateway 비용 최적화</h3>
<blockquote>
<p>여러 AZ에 걸친 EC2들이 <strong>단일 NAT Gateway</strong>를 통해 S3와 이미지를 주고받는다. 데이터 전송 요금을 줄이려면?</p>
<p>A. AZ마다 NAT Gateway 생성
B. NAT Gateway를 NAT Instance로 교체
C. <strong>S3용 Gateway VPC Endpoint 배포</strong>
D. EC2 Dedicated Host 프로비저닝</p>
</blockquote>
<p><strong>정답: C</strong></p>
<p><strong>A가 매력적인 오답입니다.</strong> AZ 간 데이터 전송 요금은 줄어들지만 NAT Gateway 시간당 요금과 GB당 요금이 배로 늘어납니다. 비용을 줄이려다 늘리는 셈입니다.</p>
<p>Gateway Endpoint는 <strong>무료</strong>이고, S3 트래픽을 AWS 내부 네트워크로 직접 흘려보냅니다. NAT를 아예 거치지 않으니 데이터 처리 요금 자체가 사라집니다.</p>
<blockquote>
<p><strong>&quot;S3 + NAT + 비용&quot;이 한 문제에 같이 등장하면 Gateway Endpoint를 먼저 의심하라.</strong></p>
</blockquote>
<hr>
<h3 id="q3-subnet-사이-접근-통제">Q3. Subnet 사이 접근 통제</h3>
<blockquote>
<p>2개 AZ에 Public, Private, DB 서브넷이 각각 있어 총 6개. <strong>Private Subnet의 EC2만</strong> RDS에 접근하게 하려면?</p>
<p>A. Public Subnet CIDR로 가는 경로를 뺀 Route Table을 DB Subnet에 연결
B. Public Subnet 인스턴스의 SG를 <strong>Deny</strong>하는 SG를 DB에 연결
C. <strong>Private Subnet 인스턴스의 SG를 source로 Allow하는 SG를 DB에 연결</strong>
D. Subnet 사이에 VPC Peering 연결 생성</p>
</blockquote>
<p><strong>정답: C</strong></p>
<p>세 개의 오답이 각각 앞에서 짚은 개념을 정확히 저격합니다.</p>
<ul>
<li><strong>A가 틀린 이유</strong>: <code>local</code> 경로는 <strong>삭제할 수 없습니다.</strong> VPC 내부 통신을 Route Table로 막는 건 원천적으로 불가능합니다. (4장)</li>
<li><strong>B가 틀린 이유</strong>: <strong>Security Group은 Deny 규칙이 없습니다.</strong> Allow만 가능합니다. (9장)</li>
<li><strong>D가 틀린 이유</strong>: VPC Peering은 <strong>VPC 사이</strong>를 잇는 기능이지, 같은 VPC 안의 서브넷용이 아닙니다.</li>
</ul>
<p>정답 C는 <strong>Security Group Referencing</strong>입니다. SG는 stateful이므로 응답 트래픽은 자동으로 허용됩니다.</p>
<hr>
<h2 id="2주차-핵심-정리">2주차 핵심 정리</h2>
<ol>
<li><strong>VPC는 내가 설계하는 가상 네트워크.</strong> Default VPC는 Public Subnet뿐이라 실서비스에 부적합하다.</li>
<li><strong>Subnet의 IP는 5개가 예약된다.</strong> 앞 4개(<code>.0</code> <code>.1</code> <code>.2</code> <code>.3</code>)와 마지막 1개(broadcast). <code>/24</code>는 251개만 쓸 수 있다.</li>
<li><strong>Subnet은 AZ를 넘을 수 없다.</strong> 고가용성을 원하면 AZ마다 짝을 맞춰 만든다.</li>
<li><strong>Public / Private을 가르는 건 Route Table이다.</strong> IGW 경로가 있으면 Public, 없으면 Private.</li>
<li><strong><code>local</code> 경로는 삭제할 수 없다.</strong> 서브넷 사이 통제는 Security Group과 NACL의 몫.</li>
<li><strong>인터넷 연결의 3요소</strong>: IGW attach + Route + Public IP. 하나라도 빠지면 안 된다.</li>
<li><strong>NAT Gateway는 Public Subnet에 산다.</strong> Private 트래픽은 NAT를 거쳐 IGW로 나간다. AZ 단위 리소스이므로 AZ마다 배치해야 진짜 HA다.</li>
<li><strong>S3, DynamoDB는 Gateway Endpoint.</strong> 무료이고, NAT 데이터 처리 요금을 없앤다.</li>
<li><strong>Security Group은 Allow만, stateful.</strong> NACL은 Deny 가능, stateless.</li>
</ol>
<hr>
<h2 id="다음-주-예고">다음 주 예고</h2>
<p>3주차는 <strong>EC2와 EBS</strong>입니다. 이번 주에 만든 Subnet 위에 실제로 인스턴스를 올리고, 인스턴스 타입과 구매 옵션(On-Demand, Reserved, Spot), 스토리지 선택으로 넘어갑니다.</p>
<p>돌아보면 흐름이 이렇게 이어집니다.</p>
<blockquote>
<p><strong>1주차 Part 1</strong>: 리소스를 어디에 둘 것인가 (Region, AZ)
<strong>1주차 Part 2</strong>: 누구에게 열어 줄 것인가 (IAM)
<strong>2주차</strong>: 트래픽이 어떻게 흐를 것인가 (VPC)
<strong>3주차</strong>: 그 위에 무엇을 올릴 것인가 (EC2)</p>
</blockquote>
<p>AWS의 모든 서비스가 결국 이 네 질문의 반복이라는 걸, 2주차를 지나며 조금씩 체감하고 있습니다.</p>
<hr>
<p><strong>참고 자료</strong></p>
<ul>
<li><a href="https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/subnet-sizing.html">AWS 공식 문서 — 서브넷 CIDR 블록</a></li>
<li><a href="https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/configure-subnets.html">AWS 공식 문서 — VPC의 서브넷</a></li>
<li><a href="https://wikidocs.net/269540">wikidocs — AWS 직원이 알려주는 AWS 클라우드 아키텍처, 08장 VPC 사용하기</a></li>
<li>AWS SAA-C03 Korean Sample (Q004, Q042, Q055)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS SAA 스터디 1주차 – Part 2] IAM과 계정 보안: 누가 무엇을 할 수 있는가]]></title>
            <link>https://velog.io/@dongmin_0204/AWS-SAA-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-Part-2-IAM%EA%B3%BC-%EA%B3%84%EC%A0%95-%EB%B3%B4%EC%95%88-%EB%88%84%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%84-%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94%EA%B0%80-4bexvyi1</link>
            <guid>https://velog.io/@dongmin_0204/AWS-SAA-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-Part-2-IAM%EA%B3%BC-%EA%B3%84%EC%A0%95-%EB%B3%B4%EC%95%88-%EB%88%84%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%84-%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94%EA%B0%80-4bexvyi1</guid>
            <pubDate>Wed, 01 Jul 2026 12:54:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>이 글의 목표</strong>
Part 1에서 만든 인프라 위에서 <strong>&quot;권한&quot;을 통제하는 방법</strong>을 이해한다.</p>
<ul>
<li>루트 사용자 vs IAM 사용자 — 왜 루트를 쓰면 안 되는가</li>
<li>IAM 4대 구성요소: 사용자 / 그룹 / 정책 / 역할</li>
<li><strong>사용자 vs 역할</strong>을 언제 쓰는가 (SAA 단골 주제)</li>
<li>AWS를 제어하는 3가지 방법(콘솔·CLI·SDK)과 액세스 키</li>
<li>2024~2025년에 강화된 루트 MFA 의무화 등 최신 보안 정책</li>
</ul>
<p>IAM은 <strong>글로벌 서비스</strong>입니다(Part 1 복습). 리전에 묶이지 않고 계정 전체에 적용됩니다.</p>
</blockquote>
<hr>
<h2 id="1-출발점-공동-책임-모델과-iam의-위치">1. 출발점: 공동 책임 모델과 IAM의 위치</h2>
<p>AWS 보안은 <strong>공동 책임 모델(Shared Responsibility Model)</strong> 로 나뉩니다.</p>
<ul>
<li><strong>AWS의 책임 (&quot;of the cloud&quot;)</strong>: 데이터센터, 하드웨어, 물리 네트워크 등 클라우드 <strong>자체</strong>의 보안.</li>
<li><strong>고객의 책임 (&quot;in the cloud&quot;)</strong>: 그 위에 올린 데이터·설정·<strong>접근 권한</strong>의 보안.</li>
</ul>
<p>이 &quot;고객의 책임&quot; 중 가장 근본이 되는 것이 <strong>IAM(Identity and Access Management)</strong> 입니다. IAM은 한 문장으로 요약하면:</p>
<blockquote>
<p><strong>&quot;어떤 주체(누가)가 · 어떤 리소스(무엇)에 · 어떤 행동(어떻게)을 할 수 있는지 정의하는 시스템&quot;</strong></p>
</blockquote>
<hr>
<h2 id="2-루트-사용자-vs-iam-사용자">2. 루트 사용자 vs IAM 사용자</h2>
<h3 id="2-1-루트-사용자root-user란">2-1. 루트 사용자(Root User)란</h3>
<p>AWS 계정을 처음 만들 때 쓴 <strong>이메일 + 비밀번호</strong>의 주인이 루트 사용자입니다. 루트는 <strong>계정 안의 모든 것에 무제한 권한</strong>을 가집니다. 말 그대로 신(神)입니다.</p>
<p>문제는 바로 이 무제한성입니다. 루트 자격 증명이 유출되면 <strong>계정 전체가 그대로 넘어갑니다</strong> — 리소스 삭제, 청구 정보 변경, 심지어 계정 폐쇄까지.</p>
<h3 id="2-2-그래서-루트는-봉인한다">2-2. 그래서 루트는 &quot;봉인&quot;한다</h3>
<p><strong>모범 사례(Best Practice)는 루트를 일상 업무에 절대 쓰지 않는 것</strong>입니다. 대신:</p>
<ol>
<li>루트로 최초 로그인 → <strong>관리자(Admin) 권한을 가진 IAM 사용자를 하나 만든다</strong>.</li>
<li>이후 모든 작업은 그 IAM 사용자로 한다.</li>
<li>루트는 <strong>MFA를 걸고 봉인</strong>한 뒤, 정말 루트만 할 수 있는 일이 있을 때만 꺼낸다.</li>
</ol>
<p><strong>루트만 할 수 있는 일 (일부):</strong></p>
<ul>
<li>계정 설정 변경(이메일, 루트 비밀번호)</li>
<li>지원 플랜 변경, 계정 폐쇄</li>
<li>특정 결제/세금 설정</li>
<li>일부 예외적 S3/GovCloud 작업</li>
</ul>
<blockquote>
<p>💡 이 목록은 통째로 외우기보다 &quot;<strong>아주 예외적인, 계정 자체에 관한 일</strong>&quot;이라는 감각으로 기억하면 됩니다.</p>
</blockquote>
<h3 id="2-3-🔒-최신-보안-정책-루트-mfa-의무화-20242025">2-3. 🔒 최신 보안 정책: 루트 MFA 의무화 (2024~2025)</h3>
<p>여기는 최근에 크게 바뀐 부분이라 발표 때 짚어주면 좋습니다.</p>
<p>AWS는 <strong>모든 계정 유형의 루트 사용자에게 MFA를 의무화</strong>했습니다.</p>
<ul>
<li>2024년 5월: AWS Organizations <strong>관리 계정</strong> 루트부터 시작</li>
<li>2024년 6월: <strong>독립(standalone) 계정</strong> 루트로 확대</li>
<li>2025년: <strong>멤버 계정</strong> 루트까지 확대 → 결과적으로 <strong>모든 계정 유형</strong>에 적용</li>
</ul>
<p>또한 콘솔 최초 로그인 후 <strong>35일 이내에 MFA를 등록하지 않으면 접근이 제한</strong>됩니다. 즉 이제 &quot;루트 MFA는 선택&quot;이 아니라 <strong>사실상 강제</strong>입니다.</p>
<blockquote>
<p>💡 <strong>SAA/실무 시사점</strong>: &quot;루트 계정을 안전하게 보호하려면?&quot;의 정답은 항상 <strong>MFA 활성화 + 루트 사용 자제 + 액세스 키 미발급</strong>입니다.</p>
</blockquote>
<hr>
<h2 id="3-iam-4대-구성요소">3. IAM 4대 구성요소</h2>
<p>IAM을 이해하는 핵심 뼈대는 다음 4가지입니다.</p>
<pre><code>사용자(User) ──┐
그룹(Group) ───┼── 에 → 정책(Policy)을 붙여 권한을 부여
역할(Role) ────┘</code></pre><h3 id="3-1-사용자-user">3-1. 사용자 (User)</h3>
<p><strong>사람 또는 애플리케이션 하나에 대응하는 고정된 자격 증명</strong>입니다.</p>
<ul>
<li>로그인 자격(콘솔용 비밀번호)이나 프로그래밍 자격(액세스 키)을 가질 수 있습니다.</li>
<li>각 사용자는 독립적입니다. 개발자 A, 개발자 B는 각각의 IAM 사용자.</li>
</ul>
<h3 id="3-2-그룹-group">3-2. 그룹 (Group)</h3>
<p><strong>여러 사용자를 묶어 권한을 한꺼번에 관리하는 단위</strong>입니다.</p>
<ul>
<li>예: <code>Developers</code> 그룹에 개발 권한 정책을 붙이고, 신입 개발자를 그 그룹에 넣으면 끝.</li>
<li><strong>그룹에는 정책을 붙일 수 있지만, 그룹 자체는 로그인할 수 없습니다.</strong> (그룹은 &quot;권한 묶음&quot;일 뿐, 주체가 아님)</li>
<li>그룹은 사용자만 담을 수 있습니다. <strong>그룹 안에 그룹을 중첩할 수 없습니다.</strong></li>
</ul>
<blockquote>
<p>💡 실무 원칙: <strong>개별 사용자에게 직접 정책을 붙이지 말고, 그룹으로 관리</strong>하세요. 사람이 늘어나도 관리가 깔끔합니다.</p>
</blockquote>
<h3 id="3-3-정책-policy">3-3. 정책 (Policy)</h3>
<p><strong>&quot;무엇을 할 수 있는지&quot;를 정의한 JSON 문서</strong>입니다. IAM의 실질적 권한은 전부 정책에서 나옵니다.</p>
<p>정책의 기본 구조:</p>
<pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [&quot;s3:GetObject&quot;, &quot;s3:PutObject&quot;],
      &quot;Resource&quot;: &quot;arn:aws:s3:::my-study-bucket/*&quot;
    }
  ]
}</code></pre>
<p>핵심 필드 4가지:</p>
<ul>
<li><strong>Effect</strong>: <code>Allow</code>(허용) 또는 <code>Deny</code>(거부)</li>
<li><strong>Action</strong>: 어떤 행동인가 (예: <code>s3:GetObject</code>)</li>
<li><strong>Resource</strong>: 어떤 리소스에 대해서인가 (ARN으로 지정)</li>
<li><strong>Condition</strong>(선택): 어떤 조건에서만 (예: MFA를 썼을 때만, 특정 IP에서만)</li>
</ul>
<p><strong>정책 평가의 철칙 — 명시적 거부(Explicit Deny) 우선:</strong></p>
<ul>
<li>기본값은 <strong>모든 것이 거부(Deny)</strong> 입니다.</li>
<li><code>Allow</code>가 하나라도 있으면 허용됩니다.</li>
<li>그러나 <strong><code>Deny</code>가 하나라도 있으면, 다른 데서 Allow해도 무조건 거부</strong>됩니다.</li>
</ul>
<blockquote>
<p>💡 <strong>시험 단골</strong>: &quot;사용자에게 Allow와 Deny가 동시에 적용되면?&quot; → <strong>Deny가 이긴다.</strong></p>
</blockquote>
<p>정책 종류:</p>
<ul>
<li><strong>관리형 정책(Managed Policy)</strong>: AWS가 미리 만들어 둔 것(예: <code>AmazonS3ReadOnlyAccess</code>) 또는 고객이 만든 재사용 가능한 정책.</li>
<li><strong>인라인 정책(Inline Policy)</strong>: 특정 사용자/그룹/역할에 1:1로 박아넣는 정책. 재사용 불가.</li>
</ul>
<h3 id="3-4-역할-role--가장-중요하고-가장-헷갈리는-것">3-4. 역할 (Role) — 가장 중요하고 가장 헷갈리는 것</h3>
<p><strong>역할은 &quot;특정 권한 묶음을, 필요할 때 임시로 빌려 쓰는 신분&quot;</strong> 입니다. 사용자처럼 고정된 비밀번호나 액세스 키가 <strong>없고</strong>, 대신 필요할 때 <strong>임시 자격 증명(temporary credentials)</strong> 을 발급받아 사용합니다.</p>
<p>역할이 왜 중요한가? <strong>자격 증명을 코드나 서버에 심지 않아도 되기 때문</strong>입니다.</p>
<p>대표적인 3가지 사용 패턴:</p>
<ol>
<li><p><strong>AWS 서비스에 권한 주기 (서비스 역할)</strong></p>
<ul>
<li>예: EC2 인스턴스가 S3에 접근해야 한다.</li>
<li>❌ 나쁜 방법: 인스턴스 안에 액세스 키를 하드코딩 (유출 위험)</li>
<li>✅ 좋은 방법: <strong>S3 접근 역할을 만들어 EC2에 부여</strong>. EC2는 임시 자격 증명을 자동으로 받아 S3에 접근.</li>
</ul>
</li>
<li><p><strong>계정 간 접근 (Cross-Account Role)</strong></p>
<ul>
<li>예: 운영 계정의 사용자가 개발 계정의 리소스에 접근해야 한다.</li>
<li>개발 계정에 역할을 만들고, 운영 계정이 그 역할을 <strong>AssumeRole</strong>(맡기)해서 잠깐 권한을 빌린다.</li>
</ul>
</li>
<li><p><strong>외부 사용자에게 임시 권한 주기 (연합, Federation)</strong></p>
<ul>
<li>회사 사내 계정(예: Google, Active Directory)으로 로그인한 사람에게, IAM 사용자를 따로 만들지 않고 역할로 임시 접근을 준다.</li>
</ul>
</li>
</ol>
<blockquote>
<p>💡 <strong>핵심 감각</strong>: 역할 = <strong>&quot;빌려 쓰는 임시 권한&quot;</strong>. 누가/무엇이 이 역할을 맡을 수 있는지는 <strong>신뢰 정책(Trust Policy)</strong> 이 정의합니다.</p>
</blockquote>
<hr>
<h2 id="4-사용자-vs-역할--언제-무엇을-쓰나-saa-최다-출제">4. 사용자 vs 역할 — 언제 무엇을 쓰나 (SAA 최다 출제)</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>정답</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>사람이 콘솔에 로그인해서 일한다</td>
<td><strong>IAM 사용자</strong></td>
<td>고정 자격 증명이 필요</td>
</tr>
<tr>
<td><strong>EC2/Lambda 같은 서비스</strong>가 다른 서비스(S3 등)에 접근한다</td>
<td><strong>역할(Role)</strong></td>
<td>키를 심지 않고 임시 자격 증명 사용</td>
</tr>
<tr>
<td><strong>다른 AWS 계정</strong>의 리소스에 접근한다</td>
<td><strong>역할(Cross-Account Role)</strong></td>
<td>계정 간 신뢰로 임시 권한 위임</td>
</tr>
<tr>
<td>사내 로그인(SSO) 사용자에게 AWS 접근을 준다</td>
<td><strong>역할(Federation)</strong></td>
<td>IAM 사용자 대량 생성 회피</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>한 줄 규칙</strong>: &quot;EC2가 S3에 접근하려면?&quot; 같은 문제에서 <strong>액세스 키를 심는 선택지는 거의 항상 오답</strong>이고, <strong>역할 부여가 정답</strong>입니다. 이거 하나만 확실히 잡아도 문제 여러 개를 맞힙니다.</p>
</blockquote>
<hr>
<h2 id="5-aws를-제어하는-3가지-방법과-액세스-키">5. AWS를 제어하는 3가지 방법과 액세스 키</h2>
<p>같은 IAM 권한이라도, AWS를 조작하는 <strong>경로는 3가지</strong>입니다.</p>
<table>
<thead>
<tr>
<th>방법</th>
<th>대상</th>
<th>필요한 자격 증명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>관리 콘솔 (Console)</strong></td>
<td>사람 (웹 UI)</td>
<td>사용자명 + 비밀번호 (+ MFA)</td>
</tr>
<tr>
<td><strong>CLI (Command Line)</strong></td>
<td>사람/스크립트 (터미널)</td>
<td><strong>액세스 키 ID + 비밀 액세스 키</strong></td>
</tr>
<tr>
<td><strong>SDK</strong></td>
<td>애플리케이션 코드 (Python <code>boto3</code>, JS 등)</td>
<td><strong>액세스 키</strong> 또는 <strong>역할의 임시 자격 증명</strong></td>
</tr>
</tbody></table>
<p><strong>액세스 키(Access Key)</strong> 는 CLI/SDK가 프로그래밍 방식으로 AWS를 호출할 때 쓰는 자격 증명입니다.</p>
<ul>
<li><strong>액세스 키 ID</strong> (공개된 식별자) + <strong>비밀 액세스 키(Secret)</strong> 한 쌍.</li>
<li>비밀 키는 <strong>발급 시 딱 한 번만</strong> 보여줍니다. 놓치면 재발급해야 합니다.</li>
<li><strong>절대 코드/깃허브에 커밋하면 안 됩니다.</strong> (유출 사고의 최다 원인)</li>
</ul>
<blockquote>
<p>💡 <strong>모범 사례</strong>: 서버·앱이 AWS를 호출해야 한다면, 장기 액세스 키 대신 <strong>역할(Role)의 임시 자격 증명</strong>을 쓰세요. 키 유출 위험 자체를 없앨 수 있습니다. 루트에는 <strong>액세스 키를 아예 발급하지 마세요.</strong></p>
</blockquote>
<hr>
<h2 id="6-계정-보안-베스트-프랙티스-체크리스트">6. 계정 보안 베스트 프랙티스 체크리스트</h2>
<ul>
<li><input disabled="" type="checkbox"> <strong>루트 사용자에 MFA 활성화</strong> (2024~2025년부터 사실상 의무)</li>
<li><input disabled="" type="checkbox"> <strong>루트 일상 사용 금지</strong> — Admin IAM 사용자를 만들어 그걸로 작업</li>
<li><input disabled="" type="checkbox"> <strong>루트에 액세스 키 미발급</strong></li>
<li><input disabled="" type="checkbox"> 모든 IAM 사용자/그룹에 <strong>최소 권한 원칙(Least Privilege)</strong> 적용</li>
<li><input disabled="" type="checkbox"> 권한은 <strong>그룹 단위</strong>로 관리</li>
<li><input disabled="" type="checkbox"> 서비스 간 접근은 <strong>키가 아니라 역할(Role)</strong> 로</li>
<li><input disabled="" type="checkbox"> 액세스 키는 <strong>정기적으로 교체(rotate)</strong>, 코드에 하드코딩 금지</li>
<li><input disabled="" type="checkbox"> 활동 추적을 위해 <strong>CloudTrail</strong> 활성화 (누가 무엇을 했는지 기록)</li>
</ul>
<hr>
<h2 id="7-1주차-part-2-핵심-정리">7. 1주차 Part 2 핵심 정리</h2>
<ul>
<li><strong>IAM은 글로벌 서비스</strong>. &quot;누가 · 무엇에 · 어떻게&quot;를 통제한다.</li>
<li><strong>루트는 무제한 권한 → 봉인</strong>. MFA 걸고, 일상은 Admin IAM 사용자로.</li>
<li><strong>4대 요소</strong>: 사용자(주체) · 그룹(권한 묶음) · 정책(JSON으로 권한 정의) · 역할(임시로 빌려 쓰는 권한).</li>
<li><strong>정책은 기본 거부, Allow로 열고, Deny가 최우선</strong>.</li>
<li><strong>서비스/계정 간 접근은 역할</strong> — 키 하드코딩은 오답.</li>
<li><strong>제어 경로 3가지</strong>: 콘솔(사람) · CLI(터미널) · SDK(코드). 프로그래밍 접근엔 액세스 키 또는 역할.</li>
</ul>
<h3 id="✅-시험에서-이렇게-나온다-연습용-질문">✅ 시험에서 이렇게 나온다 (연습용 질문)</h3>
<ol>
<li>&quot;EC2 인스턴스가 S3 버킷을 읽어야 한다. 가장 안전한 방법은?&quot; → <strong>IAM 역할을 EC2에 부여</strong> (액세스 키 하드코딩은 오답)</li>
<li>&quot;한 사용자에게 S3 Allow 정책과 S3 Deny 정책이 동시에 적용된다. 결과는?&quot; → <strong>거부(Deny 우선)</strong></li>
<li>&quot;루트 계정을 보호하는 첫 번째 조치는?&quot; → <strong>MFA 활성화</strong></li>
<li>&quot;운영 계정 사용자가 개발 계정 리소스에 접근해야 한다.&quot; → <strong>Cross-Account Role + AssumeRole</strong></li>
<li>&quot;개발자 20명에게 동일한 개발 권한을 효율적으로 주려면?&quot; → <strong>그룹을 만들어 정책을 붙이고 사용자를 그룹에 추가</strong></li>
</ol>
<hr>
<p>📌 
<strong>1주차 전체 흐름 요약</strong>: 
Part 1에서 <strong>인프라(리전·AZ·엣지)</strong> 라는 &quot;배경&quot;을 , Part 2에서 <strong>권한(IAM)</strong> 이라는 &quot;출입 통제&quot;를 이해했습니다. </p>
<p>2주차부터 EC2·VPC·S3 같은 개별 서비스로 들어가면, 결국 전부 &quot;<strong>어느 리전/AZ에 두고, 누구에게 어떤 권한으로 열어줄 것인가</strong>&quot;의 반복이라는 걸 알게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS SAA 스터디 1주차 : Part 1] 클라우드 기본 개념과 AWS 글로벌 인프라]]></title>
            <link>https://velog.io/@dongmin_0204/AWS-SAA-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-Part-1-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-AWS-%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%9D%B8%ED%94%84%EB%9D%BC</link>
            <guid>https://velog.io/@dongmin_0204/AWS-SAA-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-Part-1-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-AWS-%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%9D%B8%ED%94%84%EB%9D%BC</guid>
            <pubDate>Wed, 01 Jul 2026 12:54:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>이 글의 목표</strong>
AWS를 &quot;서비스 이름 암기&quot;로 접근하지 않기 위한 베이스를 만든다.</p>
<ul>
<li>왜 클라우드를 쓰는가 (온프레미스와 뭐가 다른가)</li>
<li>리전 / 가용 영역(AZ) / 엣지 로케이션이 각각 무엇이고 왜 나뉘어 있는가</li>
<li>리전 서비스 vs 글로벌 서비스의 차이 — SAA에서 은근히 헷갈리는 포인트</li>
</ul>
<p>SAA 시험은 &quot;이 상황에서 어떤 구조가 맞는가&quot;를 묻습니다. 그 판단의 90%는 결국 <strong>어디에(리전/AZ) 무엇을 배치하느냐</strong>로 연결되기 때문에, 인프라 개념이 1주차의 핵심이라 생각합니다.</p>
</blockquote>
<hr>
<h2 id="1-왜-클라우드인가-온프레미스의-단점">1. 왜 클라우드인가: 온프레미스의 단점</h2>
<p>클라우드를 이해하는 가장 빠른 길은 &quot;직접 서버를 사서 운영하면 뭐가 힘든가&quot;를 떠올리는 것입니다.</p>
<p>쇼핑몰 서버를 회사 전산실(온프레미스)에 직접 뒀다고 해봅시다. 다음과 같은 문제들이 생깁니다.</p>
<ul>
<li><strong>선투자 부담</strong>: 트래픽이 늘어날지 확신도 없는데 서버·네트워크 장비를 미리 사야 한다.</li>
<li><strong>용량 예측 실패</strong>: 너무 적게 사면 트래픽 몰릴 때 터지고, 너무 많이 사면 대부분 놀린다.</li>
<li><strong>확장 속도</strong>: 서버 한 대 더 늘리는 데 발주 → 배송 → 설치 → 세팅으로 수 주가 걸린다.</li>
<li><strong>장애 대응</strong>: 정전, 냉방 고장, 화재 같은 물리적 사고 한 번에 서비스 전체가 죽는다.</li>
<li><strong>운영 인력</strong>: 하드웨어 교체, 백업, 보안 패치를 전부 사람이 직접 챙겨야 한다.</li>
</ul>
<p>클라우드는 이 문제들을 <strong>&quot;필요한 만큼 빌려 쓰고, 쓴 만큼 낸다&quot;</strong>로 뒤집습니다.</p>
<h3 id="capex---opex-전환">CapEx -&gt; OpEx 전환</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>온프레미스 (CapEx)</th>
<th>클라우드 (OpEx)</th>
</tr>
</thead>
<tbody><tr>
<td>비용 성격</td>
<td>자본적 지출 (미리 크게 투자)</td>
<td>운영 비용 (사용량 기반 과금)</td>
</tr>
<tr>
<td>확장</td>
<td>물리 장비 구매 필요</td>
<td>클릭/API로 수 분 내</td>
</tr>
<tr>
<td>리스크</td>
<td>과잉/과소 투자</td>
<td>수요에 맞춰 조절</td>
</tr>
<tr>
<td>관리 책임</td>
<td>전기·냉방·하드웨어까지 전부</td>
<td>AWS가 물리 계층 담당</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>SAA 관점</strong>: 시험에서 &quot;초기 비용을 줄이고 싶다&quot;, &quot;수요가 불규칙하다&quot;, &quot;빠르게 글로벌 출시해야 한다&quot; 같은 키워드가 나오면 클라우드의 <strong>탄력성(Elasticity)</strong> 과 <strong>종량제(Pay-as-you-go)</strong> 를 떠올리면 됩니다.</p>
</blockquote>
<hr>
<h2 id="2-aws-글로벌-인프라의-3층-구조">2. AWS 글로벌 인프라의 3층 구조</h2>
<p>AWS의 물리적 세계는 크게 <strong>리전(Region) → 가용 영역(AZ) → 데이터센터</strong>로 내려가는 계층으로 되어 있고, 여기에 사용자와 가까운 곳에 두는 <strong>엣지 로케이션(Edge Location)</strong> 이 별도로 붙습니다.</p>
<pre><code>지구
 └─ 리전 (예: 서울 ap-northeast-2)
     ├─ 가용 영역 A (AZ) ── 데이터센터 1..N
     ├─ 가용 영역 B (AZ) ── 데이터센터 1..N
     └─ 가용 영역 C (AZ) ── 데이터센터 1..N

엣지 로케이션 (전 세계 수백 곳) ── CloudFront 캐시 등</code></pre><h3 id="2-1-리전-region">2-1. 리전 (Region)</h3>
<p><strong>리전은 지리적으로 완전히 분리된 하나의 영역</strong>입니다. 서울, 도쿄, 버지니아 북부, 프랑크푸르트처럼 도시 단위로 존재하고, 각 리전은 <code>ap-northeast-2</code>(서울) 같은 고유 코드를 가집니다.</p>
<p>핵심 성질:</p>
<ul>
<li>리전끼리는 <strong>물리적으로 독립</strong>입니다. 한 리전에 장애가 나도 다른 리전엔 영향이 없습니다.</li>
<li>리전 간 데이터는 <strong>명시적으로 복제하지 않는 한 자동으로 넘어가지 않습니다.</strong> (데이터 주권/규정 준수와 직결)</li>
<li>AWS는 2026년 기준 <strong>약 39개의 리전</strong>을 운영 중이고, 계속 늘어납니다. (정확한 수치는 <a href="https://aws.amazon.com/about-aws/global-infrastructure/">AWS 글로벌 인프라 공식 페이지</a>에서 확인)</li>
</ul>
<h3 id="2-2-가용-영역-availability-zone-az">2-2. 가용 영역 (Availability Zone, AZ)</h3>
<p><strong>AZ는 리전 안에 있는, 서로 물리적으로 떨어진 하나 이상의 데이터센터 묶음</strong>입니다.</p>
<ul>
<li>각 리전은 <strong>최소 3개의 AZ</strong>로 구성됩니다.</li>
<li>각 AZ는 <strong>독립된 전력·냉방·네트워크</strong>를 가집니다. 그래서 한 AZ가 정전으로 죽어도 다른 AZ는 멀쩡합니다.</li>
<li>하지만 같은 리전의 AZ끼리는 <strong>초저지연 전용 네트워크</strong>로 연결돼 있어서, 마치 한 곳처럼 빠르게 통신합니다.</li>
</ul>
<blockquote>
<p>이 &quot;떨어져 있지만 빠르게 연결됨&quot;이 AWS 고가용성 설계의 핵심입니다.
웹 서버를 AZ-A와 AZ-B에 나눠서 띄우면, A가 통째로 죽어도 B가 서비스를 계속합니다. 이게 뒤에 배울 <strong>Multi-AZ 아키텍처</strong>의 원리입니다.</p>
</blockquote>
<h3 id="2-3-엣지-로케이션-edge-location">2-3. 엣지 로케이션 (Edge Location)</h3>
<p><strong>엣지 로케이션은 리전·AZ보다 훨씬 촘촘하게, 사용자 가까이에 배치된 캐시 거점</strong>입니다.</p>
<ul>
<li>리전/AZ가 &quot;연산과 저장의 본진&quot;이라면, 엣지는 &quot;사용자 코앞의 배달 창고&quot;입니다.</li>
<li><strong>CloudFront(CDN)</strong> 가 대표적으로 이걸 씁니다. 서울 사용자가 미국 리전의 이미지를 요청해도, 서울 근처 엣지에 캐시가 있으면 거기서 바로 응답해 빠릅니다.</li>
<li>Route 53(DNS)도 엣지 네트워크를 활용합니다.</li>
</ul>
<h3 id="참고-local-zones--wavelength">(참고) Local Zones / Wavelength</h3>
<p>시험 비중은 낮지만 개념만 알아두면 좋습니다.</p>
<ul>
<li><strong>Local Zones</strong>: 리전보다 더 사용자 가까운 곳에 컴퓨팅/스토리지 일부를 두어, 초저지연(한 자릿수 ms)이 필요한 워크로드(실시간 게임, 미디어 편집 등)를 지원.</li>
<li><strong>Wavelength</strong>: 통신사 5G 네트워크 안에 AWS 인프라를 넣어 모바일 초저지연을 지원.</li>
</ul>
<hr>
<h2 id="3-리전은-어떻게-고르는가">3. 리전은 어떻게 고르는가</h2>
<p>리전 선택은 SAA에서 자주 나오는 실무 판단입니다. 기준은 4가지입니다.</p>
<ol>
<li><strong>지연 시간(Latency)</strong>: 사용자와 가까운 리전일수록 빠릅니다. 한국 사용자 → 서울 리전.</li>
<li><strong>규정 준수(Compliance)</strong>: &quot;데이터가 국경을 넘으면 안 된다&quot;는 규제(GDPR 등)가 있으면 특정 리전에 묶어야 합니다.</li>
<li><strong>서비스 가용성(Service Availability)</strong>: 모든 리전이 모든 서비스를 제공하진 않습니다. 신규/특수 서비스는 특정 리전에만 있을 수 있습니다.</li>
<li><strong>비용(Cost)</strong>: 같은 서비스라도 리전마다 가격이 다릅니다. 일반적으로 버지니아 북부(us-east-1)가 저렴한 편입니다.</li>
</ol>
<blockquote>
<p>💡 <strong>시험 팁</strong>: &quot;유럽 사용자 데이터는 유럽 밖으로 나가면 안 된다&quot; → 규정 준수 → 유럽 리전 고정.
&quot;지연 시간을 줄이고 싶다&quot; → 사용자와 가까운 리전 + 필요하면 CloudFront(엣지).</p>
</blockquote>
<hr>
<h2 id="4-리전-서비스-vs-글로벌-서비스--헷갈리면-손해-보는-포인트">4. 리전 서비스 vs 글로벌 서비스 — 헷갈리면 손해 보는 포인트</h2>
<p>AWS 서비스는 &quot;어느 범위에서 동작하느냐&quot;에 따라 나뉩니다. SAA에서 조용히 함정으로 나옵니다.</p>
<table>
<thead>
<tr>
<th>범위</th>
<th>의미</th>
<th>대표 서비스</th>
</tr>
</thead>
<tbody><tr>
<td><strong>글로벌(Global)</strong></td>
<td>리전에 묶이지 않고 계정 전체에서 하나로 동작</td>
<td><strong>IAM, Route 53, CloudFront, WAF(CloudFront용), AWS Organizations</strong></td>
</tr>
<tr>
<td><strong>리전(Regional)</strong></td>
<td>특정 리전 안에서 동작, 리전마다 별도</td>
<td><strong>EC2, VPC, S3, DynamoDB, Lambda, RDS</strong></td>
</tr>
<tr>
<td><strong>가용 영역(AZ)</strong></td>
<td>특정 AZ에 묶임</td>
<td><strong>EC2 인스턴스, EBS 볼륨, 서브넷</strong></td>
</tr>
</tbody></table>
<p>여기서 자주 나오는 오해 몇 가지:</p>
<ul>
<li><strong>IAM은 글로벌</strong>입니다. 사용자·역할·정책은 리전을 가리지 않고 계정 전체에 적용됩니다.</li>
<li><strong>S3는 리전 서비스</strong>입니다. 버킷은 특정 리전에 만들어집니다. 다만 <strong>버킷 이름(네임스페이스)은 전 세계에서 유일</strong>해야 해서 &quot;글로벌&quot;처럼 느껴질 뿐입니다. 이 둘을 구분하세요.</li>
<li><strong>EBS 볼륨은 AZ에 묶입니다.</strong> 그래서 AZ-A의 EBS를 AZ-B의 인스턴스에 그냥 붙일 수 없습니다. (스냅샷을 떠서 옮겨야 함)</li>
</ul>
<blockquote>
<p>💡 <strong>한 줄 요약</strong>: &quot;이 리소스가 죽으면 영향 범위가 어디까지인가?&quot;를 생각하면 범위가 잡힙니다.
AZ 서비스는 AZ 단위로, 리전 서비스는 리전 단위로 격리됩니다.</p>
</blockquote>
<hr>
<h2 id="5-1주차-part-1-핵심-정리">5. 1주차 Part 1 핵심 정리</h2>
<ul>
<li>클라우드는 온프레미스의 <strong>선투자·확장 지연·물리 장애</strong> 문제를 <strong>종량제·탄력성</strong>으로 해결한다. (CapEx → OpEx)</li>
<li><strong>리전</strong> = 지리적으로 독립된 영역, 리전 간 데이터는 자동 복제 안 됨 → 규정 준수의 핵심.</li>
<li><strong>AZ</strong> = 리전 안의 물리적으로 분리된 데이터센터 묶음, 최소 3개, 초저지연 연결 → 고가용성의 핵심.</li>
<li><strong>엣지 로케이션</strong> = 사용자 코앞의 캐시 거점 → CloudFront/Route 53, 지연 시간 개선의 핵심.</li>
<li>서비스는 <strong>글로벌 / 리전 / AZ</strong> 범위로 나뉜다. <strong>IAM·Route 53·CloudFront는 글로벌</strong>, <strong>S3는 리전(단 버킷 이름은 전역 유일)</strong>, <strong>EBS는 AZ</strong>.</li>
</ul>
<h3 id="✅-시험에서-이렇게-나온다-연습용-질문">✅ 시험에서 이렇게 나온다 (연습용 질문)</h3>
<ol>
<li>&quot;한 데이터센터에 정전이 나도 웹 서비스가 계속 동작해야 한다.&quot; → <strong>여러 AZ에 분산 배치 (Multi-AZ)</strong></li>
<li>&quot;미국에 있는 원본 이미지를 아시아 사용자에게 빠르게 전달하고 싶다.&quot; → <strong>CloudFront(엣지 로케이션 캐시)</strong></li>
<li>&quot;유럽 고객 데이터는 반드시 유럽 안에 머물러야 한다.&quot; → <strong>유럽 리전 선택 + 리전 간 복제 금지</strong></li>
<li>&quot;IAM 사용자를 서울 리전에서만 쓸 수 있게 제한하고 싶다.&quot; → <strong>함정. IAM은 글로벌이라 리전에 묶이지 않는다.</strong></li>
</ol>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[React를 React답게, Thinking in React(1):  UI는 function]]></title>
            <link>https://velog.io/@dongmin_0204/React%EB%A5%BC-React%EB%8B%B5%EA%B2%8C-Thinking-in-React1-UI%EB%8A%94-function</link>
            <guid>https://velog.io/@dongmin_0204/React%EB%A5%BC-React%EB%8B%B5%EA%B2%8C-Thinking-in-React1-UI%EB%8A%94-function</guid>
            <pubDate>Sat, 10 Jan 2026 11:34:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/160dc5c8-b14a-4dca-b885-db4725222c1c/image.png" alt=""></p>
<h4 id="디자인도-이쁘고-코드도-이쁘게-짤-순-없을까">디자인도 이쁘고 코드도 이쁘게 짤 순 없을까??</h4>
<p>이 단순한 생각에서 이 글 시리즈를 작성하게 된 계기가 되었습니다!
<em>(대 바이브코딩 시대에, 문서보고 코드짜는 멋쟁이가 되고 싶어...)</em></p>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/34b95a7e-8e45-439b-9150-c2e4ae9fc931/image.png" alt=""></p>
<h3 id="리액트를-직접-만든-사람들은-어떻게-생각하고-만들었을까">리액트를 직접 만든 사람들은 어떻게 생각하고 만들었을까?</h3>
<p>React 공식 문서 <a href="https://ko.react.dev/learn/thinking-in-react"><em>Thinking in React</em></a>를 단순히 ‘컴포넌트 나누는 방법’으로 보는 것이 아니라,</p>
<p>제가 열심히 해체 분석해보려 합니다! 
<del>shout out to sAewoo</del></p>
<p><strong>React의 본질적 철학(순수 함수 UI, 책임 분리, 단방향 데이터 흐름, 예측 가능성)</strong> 
관점에서 알아보려고 합니다.</p>
<p>이 관점을 기반으로 다음을 얻을 수 있습니다!:</p>
<ul>
<li><p>React가 왜 이런 방식으로 UI를 구성하도록 가이드를 제시하는지 <strong>철학적 이유</strong></p>
</li>
<li><p>컴포넌트 설계가 ‘감’에서 ‘원리 기반 판단’으로 전환하기.(추구했던 바!!)</p>
</li>
</ul>
<hr>
<h1 id="목차">목차</h1>
<ol>
<li><strong>Thinking in React 5단계의 나만의 해석</strong></li>
<li><strong>상태 배치(State Architecture)를 판단하는 기준</strong></li>
<li><strong>단방향 데이터 흐름과 예측 가능성 확보</strong></li>
</ol>
<hr>
<h1 id="1-ui--fstate-왜-이-식이-필요할까">1. UI = f(state): 왜 이 식이 필요할까?</h1>
<p>React를 진지하게 이해하려면, 먼저 이 한 줄을 이해해야 한다.</p>
<pre><code class="language-txt">UI = f(state)</code></pre>
<p>React 제작자 들이 하고 싶은 말은 단순하다.</p>
<blockquote>
<p>“UI는 직접 조작하는 대상이 아니라,
상태(state)를 넣으면 계산되어 나오는 <strong>결과물</strong>이어야 한다.”</p>
</blockquote>
<p>이 식이 왜 필요한지, 그리고 이게 무너지면 어떤 지옥이 펼쳐지는지부터 짚고 가자.</p>
<hr>
<h2 id="11-기존-ui-패턴이-왜-망가지는가-상태-지옥">1.1 기존 UI 패턴이 왜 망가지는가: “상태 지옥”</h2>
<p>전통적인 웹 UI는 대부분 이렇게 동작했었다.</p>
<ul>
<li>DOM에서 요소를 찾고</li>
<li>이벤트가 발생할 때마다</li>
<li>class를 붙였다 떼었다,</li>
<li>텍스트를 바꾸고,</li>
<li>여러 상태나 데이터를 “수동으로” 맞춘다.</li>
</ul>
<p>문제는 <strong>상태가 한 군데에 모여 있지 않다</strong>는 점이다.</p>
<ul>
<li>서버 응답 결과 일부는 전역 변수에,</li>
<li>일부는 DOM attribute에,</li>
<li>일부는 컴포넌트 인스턴스 필드에,</li>
<li>심지어 유저가 뭔가 조작하면 또 다른 곳에 복사본이 생긴다.</li>
</ul>
<p>이렇게 되면 필연적으로 이런 상황이 온다.</p>
<ul>
<li>어떤 버튼은 <code>활성화</code>인데, 다른 영역은 여전히 <code>비활성화</code>로 보인다.</li>
<li>모달은 닫혔는데, 오버레이는 남아 있다.</li>
<li>토글 버튼 텍스트는 <code>ON</code>인데 실제 로직 값은 <code>false</code>다.</li>
</ul>
<p>즉, <strong>“앱의 진짜 상태”와 “눈에 보이는 UI”가 서로 어긋나는 순간</strong>부터
디버깅은 감과 운에 의존하는 게임이 된다.</p>
<p>이게 React가 해결하려는 문제의 본질이다.</p>
<hr>
<h2 id="12-react의-관점-ui를-단순-화면이-아니라-함수로-취급하자">1.2 React의 관점: “UI를 단순 화면이 아니라 함수로 취급하자”</h2>
<p>React는 여기서 접근을 통째로 바꾼다.</p>
<ul>
<li>더 이상 “UI를 어떻게 바꿀지(how)”를 작성하지 말고,</li>
<li>“지금 상태라면 UI가 어떻게 생겨야 하는지(what)”를 정의하라고 한다.</li>
</ul>
<p>즉,</p>
<pre><code class="language-txt">UI = f(state)</code></pre>
<ul>
<li>입력: 현재 앱 상태(state)</li>
<li>출력: 그 상태에서의 UI 트리(React element tree)</li>
</ul>
<p>우리는 더 이상 <code>DOM 조작 절차</code>를 나열하지 않는다.
대신 <code>state를 인자로 받아 UI를 반환하는 함수</code>를 설계한다.</p>
<p>이게 “선언형 UI(Declarative UI)”.</p>
<blockquote>
<p>“상태가 이렇게 생겼으면, 화면은 이렇게 보여야 한다”를
<strong>함수 정의로</strong> 고정해 두는 것.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/3929aae5-4829-495d-b401-f21c87698cb0/image.png" alt=""></p>
<hr>
<h2 id="13-순수-함수-ui의-세-가지-조건">1.3 순수 함수 UI의 세 가지 조건</h2>
<p>React가 진짜로 원하는 건 <strong>“순수 함수처럼 보이는 UI”</strong>다.
순수 함수 UI는 최소한 아래 세 가지를 만족해야 한다.</p>
<ol>
<li><p><strong>같은 state == 항상 같은 UI</strong></p>
<ul>
<li>랜덤, 시간, 전역 mutable 변수 등에 의해 결과가 바뀌지 않는다.</li>
</ul>
</li>
<li><p><strong>UI는 오직 props와 state로만 결정된다</strong></p>
<ul>
<li>컴포넌트 내부에서 몰래 전역 싱글톤을 직접 바꾸고 참조하는 패턴은 독이다.</li>
</ul>
</li>
<li><p><strong>렌더링 과정에는 side effect가 없다</strong></p>
<ul>
<li>DOM 직접 조작, 네트워크 요청, 로깅 등은 <code>useEffect</code> 같은 별도 단계에서 처리해야 한다.</li>
</ul>
</li>
</ol>
<p>이게 지켜져야 다음이 가능해진다.</p>
<ul>
<li>상태만 보면 UI가 어떻게 생겼는지 <strong>예측</strong> 가능</li>
<li>특정 state를 주입해서 화면을 <strong>테스트</strong> 가능 (그래서 TDD를?)</li>
<li>UI 변경 이력을 <strong>추론</strong> 가능(“왜 이렇게 보이는지” 설명 가능)</li>
</ul>
<p>즉, <strong>테스트 가능하고, 예측 가능하고, 리팩터링 가능한 UI</strong>의 최소 조건이 바로 이거다.</p>
<hr>
<h2 id="14-이-식이-설계에-강제하는-구조적-결과">1.4 이 식이 설계에 강제하는 구조적 결과</h2>
<p>UI = f(state)를 진지하게 받아들이면,
설계는 어쩔 수 없이 다음과 같이 바뀐다.</p>
<ol>
<li><p><strong>상태는 함수 밖에서 관리하고, arg로 주입한다.</strong></p>
<ul>
<li>컴포넌트는 “현재 상태를 기반으로 UI를 그리는 기계”가 된다.</li>
</ul>
</li>
<li><p><strong>렌더링 로직은 side effect에서 분리된다.</strong></p>
<ul>
<li>그리는 것(render)과 “반응하는 것(effect)”을 분리해야 순수성이 유지된다.</li>
</ul>
</li>
<li><p><strong>자식 컴포넌트는 함수 합성처럼 다뤄진다.</strong></p>
<ul>
<li>부모가 state → UI로 계산한 결과 일부를 props로 넘긴다.</li>
<li>자식은 받은 props를 다시 UI로 매핑하는 작은 함수다.</li>
</ul>
</li>
</ol>
<p>즉, 전체 React 앱은 거대한:</p>
<pre><code class="language-txt">AppUI = f(globalState)</code></pre>
<p>를 작은 함수 여러 개의 합성으로 분해한 것에 불과하다.</p>
<p>이 관점이 잡히면, 컴포넌트 나누기, props 구조, state 위치 선정은
전부 이 식을 깨지 않기 위한 <strong>아키텍처 작업</strong>으로 이해할 수 있다.</p>
<hr>
<h2 id="15-ui--fstate가-깨질-때-벌어지는-사고들">1.5 UI = f(state)가 깨질 때 벌어지는 사고들</h2>
<p>반대로, 이 원칙을 무시하면 어떤 일이 생길까?
대표적인 실패 패턴 몇 가지를 보자.</p>
<ol>
<li><p><strong>렌더링 중 전역으로 읽고/수정하는 컴포넌트</strong></p>
<ul>
<li>테스트 환경에서 상태 주입이 안 된다.</li>
<li>어디서 값이 바뀌는지 추적이 불가능해진다.</li>
</ul>
</li>
<li><p><strong>derived state를 또 다른 state로 중복 저장하는 패턴</strong></p>
<ul>
<li>예: <code>items</code>와 <code>filteredItems</code>를 둘 다 state로 들고 있기</li>
<li>둘의 동기화를 계속 맞춰야 한다는 건, 결국 언젠가 틀어진다.(디버깅시 매우 까다로워진다.)</li>
<li>사실 <code>filteredItems = f(items, filter)</code>로 매번 계산하는 게 맞다.</li>
</ul>
</li>
<li><p><strong>렌더링 안에서 DOM을 직접 건드리는 코드</strong></p>
<ul>
<li>React의 렌더링 사이클과 충돌한다.</li>
<li>“분명히 상태는 false인데, DOM은 여전히 열려 있음” 같은 모순 발생.</li>
</ul>
</li>
</ol>
<p>이 모든 문제는 한 줄로 정리된다.</p>
<blockquote>
<p>“UI가 state의 함수가 아닐 때, UI와 state는 언젠가 반드시 어긋난다.”</p>
</blockquote>
<hr>
<h2 id="16-thinking-in-react">1.6 Thinking in React</h2>
<p>이제 <strong>“왜 UI = f(state)가 필요한가”</strong>는 어느 정도 명확해졌다.
그럼 공식 문서의 5단계는 어디에 위치할까?</p>
<ul>
<li>UI를 컴포넌트로 나누기</li>
<li>정적 버전 만들기</li>
<li>최소한의 상태 찾기</li>
<li>상태를 어디 둘지 정하기</li>
<li>단방향 데이터 흐름 구성하기</li>
</ul>
<p>이건 전부 메타 수준에서 보면 한 가지 목적.</p>
<blockquote>
<p><strong>“앱 전체를 UI = f(state) 구조에 맞게 재조직하는 절차”</strong></p>
</blockquote>
<ul>
<li>정적 버전 만들기는, <strong>순수 함수 UI</strong> 먼저 확보</li>
<li>최소 상태 찾기는, f의 입력을 <strong>필요 최소한으로 압축</strong></li>
<li>상태 위치 정하기는, f가 어디서 정의되는지, <strong>책임 단위로 배치</strong></li>
<li>단방향 데이터 흐름는, <strong>입력이 어디서 왔는지 추적가능</strong>하게 만들기</li>
</ul>
<p>그래서 이 글 전체의 구조도 이렇게 잡아보자.</p>
<ol>
<li><strong>1장: UI = f(state) — 왜 이 식이 필요한가 (지금 읽고 있는 부분)</strong></li>
<li><strong>2장: 상태관리와 책임 단위 — f(state)에서 state를 어디서 관리할 것인가</strong></li>
<li><strong>3장: 단방향 데이터 흐름 — state -&gt; UI -&gt; 이벤트 -&gt; state로 이어지는 루프를 어떻게 닫을 것인가</strong></li>
</ol>
<p>이제 다음 장에서 다룰 질문은 명확하다.</p>
<blockquote>
<p>“좋다, UI = f(state)인 건 알겠다.
<strong>그럼 그 state는 어디에 둬야 예측 가능한 시스템이 되나?</strong>”</p>
</blockquote>
<p>그게 바로 2장의 주제,
<strong>“상태관리 및 책임 단위(State Architecture)”</strong>이다.</p>
<p>출처 : 
<a href="https://overreacted.io/the-two-reacts/">https://overreacted.io/the-two-reacts/</a>
<a href="https://www.youtube.com/watch?v=x7cQ3mrcKaY">https://www.youtube.com/watch?v=x7cQ3mrcKaY</a>
<a href="https://overreacted.io/a-complete-guide-to-useeffect/">https://overreacted.io/a-complete-guide-to-useeffect/</a>
<a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0">https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스] 1주차 - 1 : TDD를 통해 포인트의 동시성 락 개선기]]></title>
            <link>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 26 Oct 2025 14:58:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/5564b83d-059e-4361-aa8f-f0a0d5ca8f19/image.png" alt=""></p>
<h1 id="문제">문제</h1>
<p>이번 주엔 
<strong>1. TDD를 활용 하기 (RED-GREEN-REFATOR)</strong>
<strong>2. 포인트에서의 동시성 락을 어떻게 해결할까?
(non-blocking, single-thread 기반의 nest.js)</strong></p>
<h2 id="tdd와의-첫-만남-시험-2일-전">TDD와의 첫 만남... (시험 2일 전)</h2>
<p>사실 첫만남은 매우 비호감입니다; 중간고사와 겹치는 불상사!어쩔 수 없습니다.
해내는게 저이고 말고요.</p>
<p>일단 TDD의 기본 베이스는 이렇습니다.</p>
<blockquote>
<p><strong>Red</strong> :  실패하는 테스트 코드를 먼저 작성
<strong>Green</strong> :  테스트 코드를 성공시키기 위한 실제 코드를 작성
<strong>Refactor</strong> : 테스트 결과에 맞추어 (커버리지나 중복 코드 및 읽기 쉬운 코드 등) 리팩토링을 수행하는 것</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/d3e785bd-3e13-4dda-b3c9-ed224ac6edb5/image.png" alt=""></p>
<p>개인적으로 느낀 장점은 이렇습니다. </p>
<h3 id="한-번에-조금씩-커밋하고-메시지는-claude로-자동화"><strong>한 번에 조금씩 커밋하고, 메시지는 Claude로 자동화</strong></h3>
<p>이렇게 개발하는 방법은 처음 겪어 봤기에 테스트 코드를 작성을 먼저 해봤습니다.</p>
<pre><code class="language-javascript">// 동시에 충전과 사용 요청
            const promises = [
                service.chargePoint(userId, chargeAmount), // +3000
                service.usePoint(userId, useAmount),       // -2000  
                service.chargePoint(userId, chargeAmount)  // +3000
            ];

            const results = await Promise.all(promises);

            // 최종 잔고는 15000 + 3000 - 2000 + 3000 = 19000이어야 함
            const expectedFinalBalance = 
                initialBalance + chargeAmount - useAmount + chargeAmount;

            // 모든 연산이 순차적으로 적용되었는지 확인
            // 현재는 동시성 제어가 없어서 예상과 다를 것
            const balances = results.map(result =&gt; result.point);
            const maxBalance = Math.max(...balances);

            expect(maxBalance).toBe(expectedFinalBalance);
        }, 10000);</code></pre>
<p>이렇게 spec.ts를 기준으로 가설을 세우고, 동시에 충전과 사용 요청을 만들어 봤습니다. </p>
<hr>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/ddf6140e-5c24-4107-bd85-15933e548784/image.png" alt=""></p>
<blockquote>
<p>초기값과 최종값에만 의존하는 상태함수 개념 (간단한 아이디어)</p>
</blockquote>
<p>그래도 아직 테스트 코드 자체의 문법이 익숙하지 않아 학습할 필요가 있어
숙달해야할 듯 합니다.</p>
<p>사실 제대로 했을까?</p>
<h4 id="현재-테스트는-최종-상태를-보지-않고-중간-응답의-최댓값으로-판정하므로-신뢰할-수-없움">현재 테스트는 최종 상태를 보지 않고 중간 응답의 최댓값으로 판정하므로 신뢰할 수 없움</h4>
<p><a href="https://docs.nestjs.com/fundamentals/testing#unit-testing">nest.js의 튜토리얼을 참고해보기!</a></p>
<h3 id="그렇게-커버리지-100퍼-달성은-refactoring-과정에서-달성을-할-수-있었다-좋은거-맞아">그렇게 커버리지 100퍼 달성은 refactoring 과정에서 달성을 할 수 있었다? 좋은거 맞아?</h3>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/c07aff8e-169a-40e9-9124-bde5e2c33dec/image.png" alt=""></p>
<p><em>마치 문제를 정의하고 틀리게 해결하면 무슨 소용일까요.</em></p>
<p><a href="https://toss.tech/article/test-strategy-server">가치있는 테스트를 위한 전략과 구현
</a></p>
<p>위의 테스트 코드의 전략과 구현의 글을 보고 좋은 테스트 코드는 무엇인지 잘 숙지를 해야한다는 점입니다.</p>
<blockquote>
<p><strong>Fast</strong>: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 함
<strong>Independent</strong>: 각각의 테스트는 독립적이며 서로 의존해서는 안 됨
<strong>Repeatable</strong>: 어느 환경에서도 반복 가능해야 함
<strong>Self-Validating</strong>: 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 자체적으로 검증되어야 함
<strong>Timely</strong>: 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 함</p>
</blockquote>
<h3 id="✨-핵심-테스트-코드를-목적에-맞게-적절히-구현하는-것이-중요">✨ 핵심 테스트 코드를 목적에 맞게 적절히 구현하는 것이 중요</h3>
<hr>
<h2 id="동시성-락-싸우지마-애들아">동시성 락, 싸우지마 애들아!</h2>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/5cd3b2b2-32be-40a2-916e-fb1a0bca53db/image.png" alt=""></p>
<p>위 그림을 본다면 덮어쓰기가 2번 동작하네??? 어라,,, 그러면 115달러가 아니고 105달러가 최종 답이네...? John과 Alice는 계좌 금액에 대해 동시 접근을 해...</p>
<blockquote>
<h3 id="경쟁-상태race-condition란">경쟁 상태(race condition)란?</h3>
<p>여러 개의 프로세스가 공유 자원에 <strong>동시 접근</strong>할 때 실행 순서에 따라 결과값이 달라질 수 있는 현상</p>
</blockquote>
<p>그렇다면 어떻게 해결하는 것이 좋을까?</p>
<h3 id="누가-먼저-할래-그럼-줄-서"><strong>누가 먼저 할래?</strong> 그럼 줄 서</h3>
<ul>
<li>선택한 접근 방식: Promise 기반 Lock 시스템</li>
</ul>
<h4 id="왜-이-방식을-선택했는가">왜 이 방식을 선택했는가?</h4>
<ul>
<li><ol>
<li>Node.js 싱글 스레드 특성 활용하기</li>
</ol>
</li>
</ul>
<pre><code class="language-javascript">// 전통적인 Mutex 대신 Promise 기반 접근
private readonly locks = new Map&lt;number, Promise&lt;any&gt;&gt;();</code></pre>
<pre><code>Node.js는 싱글 스레드 이벤트 루프 기반
메모리 내 Map을 통한 간단하고 효율적인 구현
별도 라이브러리 의존성 없음</code></pre><ul>
<li><ol start="2">
<li>사용자별 독립적 Lock 관리</li>
</ol>
</li>
</ul>
<pre><code class="language-javascript">async withLock&lt;T&gt;(userId: number, operation: () =&gt; Promise&lt;T&gt;): Promise&lt;T&gt; {
    const existingLock = this.locks.get(userId);
    // 사용자별 개별 Lock 처리
}</code></pre>
<p>다음과 같은 플로우 차트를 기반으로 기획을 했었다.
<img src="https://velog.velcdn.com/images/dongmin_0204/post/2b17cf56-a8d5-4a20-b989-25fbc3ef3c91/image.png" alt=""></p>
<p>사실 이건 거짓 락에 불과한 건데, 제출하고 나서 스스로 분석도 하고 피드백을 참고해보니까...</p>
<ul>
<li><h3 id="1-허들thundering-herd문제--이벤트가-발생하면-모두-동시에-깨어나-하나의-자원을-차지하려고-경쟁하는-현상">1. 허들(Thundering herd)문제 : 이벤트가 발생하면 모두 동시에 깨어나 하나의 자원을 차지하려고 경쟁하는 현상</h3>
</li>
</ul>
<pre><code>if (existingLock) await Promise.race([existingLock, timeout]);
const newLock = operation();
this.locks.set(userId, newLock);</code></pre><p>여러 호출이 같은 기존 락이 끝나길 기다린 뒤, 동시에 operation()을 시작할 수 있습니다. 마치 <strong>오픈런</strong>...</p>
<ul>
<li><h3 id="2-해제-제대로-안된다">2. 해제 제대로 안된다...</h3>
<pre><code class="language-javascript">  operation().finally(() =&gt; this.locks.delete(userId));</code></pre>
</li>
</ul>
<p>나보다 나중에 들어온 작업이 this.locks.set(userId, next)로 <strong>새   작업(tail)</strong>을 세팅해도, 먼저 끝난 작업의 finally가 <strong>무조건   delete</strong>를 호출하면 현재 진행 중인 끝 부분도 같이 지워진다.</p>
<ul>
<li><h3 id="3-이전-작업-실패-전파">3. 이전 작업 실패 전파</h3>
</li>
</ul>
<p>existingLock이 reject 되면 다음 호출도 await에서 <strong>같이 터짐</strong>
-&gt; 연쇄 실패. (보통 직렬화 큐는 “이전 실패와 무관하게 다음
 작업은 진행”이 자연스러움)</p>
<ul>
<li><h3 id="4-공정성대기열-없음">4. 공정성/대기열 없음</h3>
</li>
</ul>
<p>FIFO 보장 없음. <strong>“먼저 도착한 요청이 먼저 실행”</strong>이 깨지기 쉽습니다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/446afe1e-1e56-4b86-bca8-8e432f3a3b81/image.png" alt="">
<em>가차없는 피드백을 받았습니다,,, 사실 map말고 queue를 썼다면 좋을텐데~</em></p>
<hr>
<h2 id="다음-목표">다음 목표</h2>
<h3 id="시간에-쫓기지-않고-좀-더-cs기반으로-판단해서-구현을-하는-것을-목적으로">시간에 쫓기지 않고 좀 더 CS기반으로 판단해서 구현을 하는 것을 목적으로!</h3>
<p><em>플로우 차트를 그리면서 고민 또 고민을 했어야했고, 왜 맵을 썼는지 이유를 근거해서 구현을 하면 좋을 것 같아.</em></p>
<h3 id="내가-맞게-했는지-의심하고-또-의심해라">내가 맞게 했는지 의심하고 또 의심해라</h3>
<p><em>구현을 하면서 테스트코드 자체가 맞다고 생각하면서 구현을 하니 아무것도 성공할 수 없었습니다. 잘못된 테스트는 잘못된 리팩토링을 불러옵니다</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스] 시작하는 마음]]></title>
            <link>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EB%A7%88%EC%9D%8C</link>
            <guid>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EB%A7%88%EC%9D%8C</guid>
            <pubDate>Mon, 20 Oct 2025 07:46:39 GMT</pubDate>
            <description><![CDATA[<h2 id="항해-플러스-참여-계기">항해 플러스 참여 계기</h2>
<p>사실 프론트엔드 개발만 진행하다 보고, 어쩌다 내일 일경험에서 백엔드도 다뤄야 했어서 조금씩 경험했던 게, 계기가 되었다. 좋은 사람들과 함께 성장할 수 있다는 점도 매우 크게 작용했다. 학교 밖에서의 사람들과 협업할 수 있다니 너무 기쁘다.</p>
<h2 id="10주간의-목표">10주간의 목표</h2>
<h4 id="1-tdd를-앞으로의-프로젝트에서-다룰-수-있을-정도">1. TDD를 앞으로의 프로젝트에서 다룰 수 있을 정도</h4>
<h4 id="2-대용량-트래픽을-활용한-나만의-프로젝트-개발">2. 대용량 트래픽을 활용한 나만의 프로젝트 개발</h4>
<h4 id="3-여러-선배-개발자들의-지식-및-철학-흡수하기">3. 여러 선배 개발자들의 지식 및 철학 흡수하기</h4>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/f0e58de6-ef7c-4182-8477-d8f7ec6e78be/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2018 KAKAO BLIND RECRUITMENT
[1차] 뉴스 클러스터링]]></title>
            <link>https://velog.io/@dongmin_0204/2018-KAKAO-BLIND-RECRUITMENT1%EC%B0%A8-%EB%89%B4%EC%8A%A4-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@dongmin_0204/2018-KAKAO-BLIND-RECRUITMENT1%EC%B0%A8-%EB%89%B4%EC%8A%A4-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81</guid>
            <pubDate>Tue, 07 Oct 2025 09:47:42 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래머스-뉴스-클러스터링-풀이">프로그래머스 뉴스 클러스터링 풀이</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/17677">프로그래머스 뉴스 클러스터링</a></p>
<h2 id="문제-정의">문제 정의</h2>
<p>두 문자열에서 연속된 2글자씩 끊어 만든 다중집합을 구성하고, 두 다중집합의 자카드 유사도 J(A, B) = 교집합(A,B) / 합집합(A,B) 를 계산해 
65536을 곱해 소수점 아래를 버린 값을 구해야 한다.</p>
<ul>
<li>알파벳만 유효(숫자/특수문자 포함 쌍은 제외)</li>
<li>대소문자 구분 없음(모두 소문자로 처리)</li>
<li>다중집합(원소 중복 허용) 기준의 교집합/합집합 계산</li>
<li>두 집합 모두 공집합이면 유사도 1로 간주, 65536 반환</li>
</ul>
<h2 id="접근-방법">접근 방법</h2>
<ul>
<li><p>문자열을 순회하며 2글자 쌍을 만들고, 둘 다 알파벳인 경우만 선택</p>
</li>
<li><p>소문자로 변환하여 정규화 : tolower() 사용</p>
</li>
<li><p>다중집합 구현은 2차원 카운팅 배열 사용: 알파벳 26×26 (a~z)</p>
<ul>
<li><code>count1[a][b]</code>, <code>count2[a][b]</code></li>
</ul>
</li>
<li><p>교집합 크기: 각 키에 대해 <code>min(count1, count2)</code>의 합</p>
</li>
<li><p>합집합 크기: 각 키에 대해 <code>max(count1, count2)</code>의 합</p>
<ul>
<li>예시의 힌트를 활용했다!
<img src="https://velog.velcdn.com/images/dongmin_0204/post/39a8528d-62b0-4121-8a8c-a45dc073dbcd/image.png" alt=""></li>
</ul>
</li>
<li><p><em>개꿀*</em></p>
</li>
<li><p>합집합이 0이면 65536, 아니면 <code>floor((inter/union) * 65536)</code> 반환</p>
</li>
</ul>
<h2 id="구현-코드-c">구현 코드 (C++)</h2>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt;

// 어 문자열 문제? 
// 유사도를 구하는데 2개씩 비교 (교집합) / (합집합)
// 집합 A와 집합 B가 모두 공집합일 경우 -&gt; 1

// 확장하니 원소의 중복을 허용하는 다중집합
// (교집합-중복x) / (합집합-중복o)
// 알파벳 + 빈도 수 -&gt; 2차원 &lt;키(a,b), 나오는 빈도&gt; 배열 활용
using namespace std;


int solution(string str1, string str2) {
    int answer = 0;

    int ch_map1[26][26];
    int ch_map2[26][26];

    fill(&amp;ch_map1[0][0], &amp;ch_map1[0][0]+26 * 26, 0);
    fill(&amp;ch_map2[0][0], &amp;ch_map2[0][0]+26 * 26, 0);
    //알바벳 first, second 에 저장되는 cnt -&gt; 0으로 초기화

    for(int i = 0; i &lt; str1.size()-1; i++){
        char a = tolower(str1[i]);
        char b = tolower(str1[i+1]);
        if (isalpha(a) &amp;&amp; isalpha(b)) {
            ch_map1[a - &#39;a&#39;][b - &#39;a&#39;]++;
        }
    }
    for(int i = 0; i &lt; str2.size()-1; i++){
        char a = tolower(str2[i]);
        char b = tolower(str2[i+1]);
        if (isalpha(a) &amp;&amp; isalpha(b)) {
            ch_map2[a - &#39;a&#39;][b - &#39;a&#39;]++;
        }
    }



    int interCnt = 0; //교집합
    int unionCnt = 0; //합집합
    for (int i = 0; i &lt; 26; i++) {
        for (int j = 0; j &lt; 26; j++) {
            interCnt += min(ch_map1[i][j], ch_map2[i][j]); 
            //설명에서 다중집합 확장한 방법
            unionCnt += max(ch_map1[i][j], ch_map2[i][j]);
        }
    }

    if (unionCnt == 0) return 65536; //공집합이면 유사도 1

    //자카드 유사도 계산
    answer = floor( (interCnt*1.0 / unionCnt) * 65536);//소수점 버림

    return answer;
}</code></pre>
<h2 id="핵심-포인트">핵심 포인트</h2>
<ol>
<li><strong>정규화</strong>: 대소문자 무시 -&gt; 모두 소문자 변환</li>
<li><strong>유효 문자 필터링</strong>: 2글자 모두 알파벳일 때만 선택하기</li>
<li><strong>다중집합 계산</strong>: 교집합은 min, 합집합은 max의 합으로 구현 : 설명 내에 답이 있었다.</li>
<li><strong>엣지 케이스</strong>: 합집합이 0이면 65536 반환</li>
<li><strong>상수 크기 맵</strong>: 26×26 카운팅 배열로 빠르게 처리 (메모리/속도 유리)</li>
</ol>
<h2 id="시간-복잡도">시간 복잡도</h2>
<ul>
<li>문자열 전처리: O(|str1| + |str2|)</li>
<li>교집합/합집합 계산: O(26×26) = O(1)</li>
<li>전체: O(N)</li>
</ul>
<h2 id="학습-포인트">학습 포인트</h2>
<ul>
<li>다중집합 교집합/합집합을 빈도 맵으로 구현하는 패턴 이해</li>
<li>문자열 전처리와 유효성 검사(isalpha, tolower) 순서 중요</li>
<li>자카드 유사도 정의 및 엣지 케이스 처리(공집합)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[
2020 카카오 인턴십
경주로 건설
]]></title>
            <link>https://velog.io/@dongmin_0204/2020-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%9D%B8%ED%84%B4%EC%8B%AD%EA%B2%BD%EC%A3%BC%EB%A1%9C-%EA%B1%B4%EC%84%A4</link>
            <guid>https://velog.io/@dongmin_0204/2020-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%9D%B8%ED%84%B4%EC%8B%AD%EA%B2%BD%EC%A3%BC%EB%A1%9C-%EA%B1%B4%EC%84%A4</guid>
            <pubDate>Sat, 04 Oct 2025 20:33:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/74ce5190-c79c-4fce-8d9a-ba6f25dde7d9/image.png" alt="프로그래머스 경주로 건설">
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/67259">프로그래머스 경주로 건설</a></p>
<h2 id="문제-정의">문제 정의</h2>
<ul>
<li><p><strong>입력</strong>: N×N 보드 (0=빈칸, 1=벽)</p>
</li>
<li><p><strong>시작점</strong>: (0,0)</p>
</li>
<li><p><strong>목표점</strong>: (N-1, N-1)</p>
</li>
<li><p><strong>이동</strong>: 상하좌우 한 칸씩 이동</p>
</li>
<li><p><strong>비용</strong>: </p>
<ul>
<li>직선 이동: 100원</li>
<li>코너 발생 시: 100 + 500 = 600원
<img src="https://velog.velcdn.com/images/dongmin_0204/post/4312de4e-32fc-41e3-8592-99736dcb56d6/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>목표</strong>: 목표칸까지 누적 비용의 최솟값</p>
</li>
</ul>
<h2 id="핵심-인사이트">핵심 인사이트</h2>
<h3 id="문제의-핵심">문제의 핵심</h3>
<p>같은 좌표라도 <strong>&quot;들어온 방향&quot;</strong>에 따라 이후 코너 비용이 달라지므로, <code>dist[x][y]</code>만으로는 최적성 보장이 불가능.</p>
<h3 id="해결-방안">해결 방안</h3>
<p>좌표 상황을 <code>(x, y, dir)</code>로 확장하여 <code>dist[x][y][dir]</code>에 <strong>&quot;dir로 진입했을 때의 최소비용&quot;</strong>을 저장.</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>비용이 균일하지 않으므로 BFS는 부적합, <strong>다익스트라 알고리즘</strong>을 사용해야 한다.</p>
<h2 id="알고리즘-개요">알고리즘 개요</h2>
<h3 id="자료구조">자료구조</h3>
<pre><code class="language-cpp">// 3차원 거리 배열: dist[x][y][dir]
vector&lt;vector&lt;vector&lt;int&gt;&gt;&gt; dist(
    size, vector&lt;vector&lt;int&gt;&gt;(size, vector&lt;int&gt;(4, INF))
);

// 우선순위 큐 (최소 힙)
priority_queue&lt;block, vector&lt;block&gt;, Cmp&gt; pq;</code></pre>
<h3 id="초기화">초기화</h3>
<ul>
<li>시작점 (0,0)은 방향이 없음</li>
<li>(0,0) 인접 유효 칸으로 첫 이동을 직선 비용(100) push</li>
</ul>
<h3 id="전개-과정">전개 과정</h3>
<ol>
<li>pq에서 최소 cost를 꺼냄 (꺼낸 cost != dist이면 스킵)</li>
<li>4방향으로 확장<ul>
<li>다음 방향이 현재 dir과 같으면 +100 (직선)</li>
<li>다르면 +600 (코너)</li>
</ul>
</li>
<li>더 작게 갱신되면 dist 갱신 후 pq에 푸시</li>
</ol>
<h3 id="종료-조건">종료 조건</h3>
<p>정답 = <code>min(dist[N-1][N-1][0..3])</code></p>
<h2 id="구현-포인트">구현 포인트</h2>
<h3 id="방향-벡터-설정">방향 벡터 설정</h3>
<pre><code class="language-cpp">int dir[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}}; 
// 상좌하우 방향, 세로 이동 0,2 / 가로이동 1,3</code></pre>
<h3 id="우선순위-큐-설정">우선순위 큐 설정</h3>
<pre><code class="language-cpp">struct Cmp {
    bool operator()(const block&amp; a, const block&amp; b) const {
        return a.cost &gt; b.cost; // 비용 작은 게 top
    }
};</code></pre>
<h3 id="중요한-구현-세부사항">중요한 구현 세부사항</h3>
<ul>
<li>첫 이동만 직선 비용 처리 (시작에 방향 X)</li>
</ul>
<h2 id="자주-나오는-버그와-수정">자주 나오는 버그와 수정</h2>
<table>
<thead>
<tr>
<th>버그</th>
<th>수정 방법</th>
</tr>
</thead>
<tbody><tr>
<td><code>dist</code>를 0 크기로 생성한 뒤 인덱싱</td>
<td>반드시 N×N×4로 초기화</td>
</tr>
<tr>
<td><code>priority_queue</code>에서 <code>front()</code> 사용</td>
<td><code>top()</code>이 정석</td>
</tr>
<tr>
<td>방향 배열을 2×4로 선언</td>
<td>인덱싱 오류 방지</td>
</tr>
<tr>
<td>코너 비용 해석 혼동</td>
<td>&quot;이번 이동이 코너인가?&quot; 기준으로 +600 처리</td>
</tr>
</tbody></table>
<h2 id="복잡도-분석">복잡도 분석</h2>
<ul>
<li><strong>블럭 상태의 수</strong>: O(4N^2) = O(N^2)</li>
<li><strong>시간복잡도</strong>: O(N^2 log N) (각 상태에서 최대 4개의 간선, 힙 시간복잡도 포함)</li>
<li><strong>공간복잡도</strong>: O(N^2)</li>
</ul>
<h2 id="코드-구조">코드 구조</h2>
<pre><code class="language-cpp">typedef struct Block {
    int cost; // 지금까지 누적 cost
    int x;    // 현재 x(행)
    int y;    // 현재 y(열)
    int dir;  // 이 칸으로 들어온 방향(0~3)
} block;

#define L_COST 100  // 직선 비용
#define C_COST 600  // 코너 비용 (100 + 500)
#define INF 999999  // 무한대</code></pre>
<h2 id="핵심-로직">핵심 로직</h2>
<pre><code class="language-cpp">// 코너 판별 및 비용 계산
int add = (i == cur.dir) ? L_COST : C_COST;
int nc = cur.cost + add;

// 최적화: 이미 더 좋은 비용으로 갱신되었다면 스킵
if (cur.cost != dist[cur.x][cur.y][cur.dir]) continue;</code></pre>
<p>이 알고리즘은 <strong>블럭 상태의 추가</strong>와 <strong>다익스트라</strong>를 결합하여 경로의 방향성을 고려한 최적 경로를 찾는 문제.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준]10828번 스택]]></title>
            <link>https://velog.io/@dongmin_0204/%EB%B0%B1%EC%A4%8010828%EB%B2%88-%EC%8A%A4%ED%83%9D</link>
            <guid>https://velog.io/@dongmin_0204/%EB%B0%B1%EC%A4%8010828%EB%B2%88-%EC%8A%A4%ED%83%9D</guid>
            <pubDate>Sun, 14 Jul 2024 07:58:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/10828">문제 주소</a></p>
<p>이번 기회에 백준 문제풀었던 걸 복기하기 위해 작성을 시작했습니다!!
같이 열심히 백준 풀어봅시다!</p>
<p>이 문제에 대해 간단히 알아봅시다:)</p>
<p>다음과 같은 기능을 구현하는 스택입니다.</p>
<p><em>push X</em>: 정수 X를 스택에 넣는 연산
<em>pop</em>: 스택에서 가장 위에 있는 정수를 빼고, 그 수를 출력한다. 
만약 스택에 들어있는 정수가 없는 경우에는 -1을 출력
<em>size</em>: 스택에 들어있는 정수의 개수를 출력
<em>empty</em>: 스택이 비어있으면 1, 아니면 0을 출력한다.
<em>top</em>: 스택의 가장 위에 있는 정수를 출력한다. 만약 스택에 들어있는 정수가 없는 경우에는 -1을 출력한다.</p>
<h1 id="1-스택-구조란">1. 스택 구조란?</h1>
<p>스택 구조는 흔히 말하는 FILO(First In, Last Out), 즉 선입후출 구조입니다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/8efeb47e-4f8f-4149-bc3a-0dad060303c2/image.png" alt=""></p>
<p>위와 같은 스택 구조는 기본적으로 일직선 적인 구조라 선형구조입니다.
그래서 스택을 배열과 연결 리스트 두 가지 방식으로 짤 수 있습니다.</p>
<pre><code>typedef struct StackNode {
    element data;
    struct StackNode* link;
}stack;</code></pre><p>위의 코드는 간단한 스택의 리스트형 구조입니다.
노드끼리 연결해주는 포인터와 데이터입니다. <em><strong>연결리스트의 기본 요소니 기억해주세요</strong></em></p>
<p>마지막 부분은 TOP으로 잡겠습니다. <em><strong>나가는 출구 및 입구!</strong></em></p>
<h1 id="2-스택-각-기능-구현">2. 스택 각 기능 구현</h1>
<p>스택에서 필요한 것은 딱 두가지 선형 구조와 어디가 마지막인지 알려주는 탑 입니다.</p>
<p><em>push</em> : 
새로운 노드를 생성하고 데이터를 저장합니다.
새 노드의 link를 현재 TOP으로 설정!
<em>pop:</em> 
    1. 스택이 비어있는 경우 -1을 반환합니다.
    2. 스택의 모든 노드를 순회.
<em>size</em>: 스택에 들어있는 정수의 개수를 출력
<em>empty</em>: 스택이 비어있으면 1, 아니면 0을 출력
<em>top</em>: 스택이 비어있으면 -1을, top인덱스가 가르키는 노드의 데이터를 반환</p>
<h1 id="3-답안-코드">3. 답안 코드</h1>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;ctype.h&gt;

typedef int element;

typedef struct StackNode {
    element data;
    struct StackNode* link;
}stack;

void push(element x);
element pop(void); // no top = -1
int size(void);
int empty(void); //empty == 1, not empty == 0
element top(void); // no top = -1

stack* TOP;

int main()
{
    int n;
    char cmd[6];
    int cnt = 0;

    TOP = NULL;

    scanf(&quot;%d&quot;, &amp;n);

    for (int i = 0; i &lt; n; i++) {
        scanf(&quot;%s&quot;, cmd);
        getchar();

        if (!strcmp(cmd, &quot;push&quot;)) {

            element input;
            scanf(&quot;%d&quot;, &amp;input);

            push(input);
            continue;
        }

        else if (!strcmp(cmd, &quot;pop&quot;)) {
            printf(&quot;%d\n&quot;, pop());
            continue;
        }

        else if (!strcmp(cmd, &quot;size&quot;)) {
            printf(&quot;%d\n&quot;, size());
            continue;
        }

        else if (!strcmp(cmd, &quot;empty&quot;)) {
            printf(&quot;%d\n&quot;, empty());
            continue;
        }

        else if (!strcmp(cmd, &quot;top&quot;)) {
            printf(&quot;%d\n&quot;, top());
            continue;
        }
    }

    return 0;
}

void push(element x) {
    stack* tmp = (stack*)malloc(sizeof(stack));

    tmp-&gt;link = TOP;
    tmp-&gt;data = x;
    TOP = tmp;

    return;
}
element pop(void) {
    if (empty()) return -1;
    else {
        stack* old = TOP;

        TOP = old-&gt;link;
        element tmp = old-&gt;data;
        free(old);
        return tmp;
    }
}
int size(void) {
    stack* tmp = TOP;
    int cnt = 0;

    while (tmp) {
        cnt++;
        tmp = tmp-&gt;link;
    }
    return cnt;
}
int empty(void) {
    if (TOP == NULL) {
        return 1;
    }
    else {
        return  0;
    }
}
element top(void) {
    if(empty()) return -1;
    else {
        return (TOP-&gt;data);
    }
}</code></pre>]]></description>
        </item>
    </channel>
</rss>