<?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, 08 Jul 2026 13:03:20 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 스터디 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>