<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>🐑☘️</title>
        <link>https://velog.io/</link>
        <description>양치기소녀</description>
        <lastBuildDate>Sun, 06 Sep 2026 14:58:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>🐑☘️</title>
            <url>https://velog.velcdn.com/images/cold_as_hell/profile/87e205f1-414b-41e7-8c14-d813733434bc/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 🐑☘️. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cold_as_hell" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Refresh Token 저장위치와 갱신방법]]></title>
            <link>https://velog.io/@cold_as_hell/Refresh-Token-%EC%A0%80%EC%9E%A5%EC%9C%84%EC%B9%98%EC%99%80-%EA%B0%B1%EC%8B%A0%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@cold_as_hell/Refresh-Token-%EC%A0%80%EC%9E%A5%EC%9C%84%EC%B9%98%EC%99%80-%EA%B0%B1%EC%8B%A0%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 06 Sep 2026 14:58:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/96f76ad8-ee3b-4735-b04f-3425162c19fb/image.png" alt=""></p>
<p>Refresh Token(리프레시 토큰) 이란, 만료된 Access Token(액세스 토큰)을 새로 발급받기 위해 사용하는 장기 인증용 토큰이다.
보안성강화와 사용자 편의성을 위해, 유효기간이 정해져 있고 이를 갱신해야 사용할 수 있다.</p>
<hr>
<h1 id="서버">서버</h1>
<p>Access Token은 서버측에서 관리할 수 있다. 
그치만 오늘의 주제는 프론트엔드 관점이므로, 자세히 살펴보지 않는다.</p>
<h1 id="인메모리-in-memory">인메모리 (In-Memory)</h1>
<p>가장 단순한 방법. Refresh Token을 그냥 자바스크립트 변수(또는 React state/context)에 담아두는 것이다.</p>
<pre><code class="language-js">const [refreshToken, setRefreshToken] = useState(null);
</code></pre>
<p><strong>장점</strong></p>
<ul>
<li><strong>XSS 공격에 가장 안전</strong>: 브라우저 저장소(Storage)나 쿠키에 기록되지 않고 메모리 상에만 존재하므로, 스크립트를 통한 토큰 스크래핑(Direct Extraction)을 방지할 수 있다.</li>
<li><strong>구현이 간단함</strong>: 별도의 브라우저 API 설정 없이 앱 내부 상태로 손쉽게 관리 가능하다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>휘발성(Persistence 부족)</strong>: 페이지를 새로고침(F5)하거나 탭을 닫으면 메모리가 초기화되어 토큰이 사라진다.</li>
<li><strong>Refresh Token 본연의 역할 수행 불가</strong>: 새로고침 시 토큰이 소멸하므로, 재발급을 통한 로그인 상태 유지라는 목적을 달성할 수 없어 단독 저장은 사실상 불가능하다.</li>
</ul>
<p><strong>갱신 방법</strong></p>
<ul>
<li><strong>In-Memory 업데이트</strong>: API 인터셉터(axios interceptor 등)가 401 응답을 수신하면, 기존 메모리의 Refresh Token을 API 요청 바디(<code>{ refreshToken }</code>)에 담아 재발급 엔드포인트로 전송한다. 응답으로 전달받은 신규 Refresh Token을 상태 업데이트 함수(<code>setRefreshToken</code>)를 통해 메모리 변수에 재할당한다.</li>
</ul>
<hr>
<h1 id="쿠키-cookie">쿠키 (Cookie)</h1>
<p>서버가 <code>Set-Cookie</code> 헤더를 통해 브라우저 쿠키에 토큰을 저장하도록 지시하는 방식이다. <strong>(가장 권장됨)</strong></p>
<p><strong>장점</strong></p>
<ul>
<li><strong><code>HttpOnly</code> 설정 시 XSS 방어</strong>: JavaScript(<code>document.cookie</code>)를 통한 접근이 차단되어 악성 스크립트로 토큰을 탈취할 수 없다.</li>
<li><strong>자동 전송의 편리함</strong>: 설정된 경로/도메인으로 요청을 보낼 때 브라우저가 쿠키를 자동으로 첨부한다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>CSRF 공격에 취약</strong>: 자동 전송 특성 때문에 타 사이트에서의 요청에 악용될 수 있으므로, 보안 옵션 설정이 필수적이다.</li>
</ul>
<table>
<thead>
<tr>
<th>속성</th>
<th>방어 대상 및 역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>HttpOnly</code></td>
<td><strong>XSS 방어</strong>: JavaScript 접근 차단</td>
</tr>
<tr>
<td><code>Secure</code></td>
<td><strong>중간자 공격(MITM) 방어</strong>: HTTPS 암호화 통신 채널에서만 전송 허용</td>
</tr>
<tr>
<td><code>SameSite=Strict</code> / <code>Lax</code></td>
<td><strong>CSRF 방어</strong>: 타 사이트에서의 교차 요청 제어 (<code>Strict</code>: 전면 차단, <code>Lax</code>: GET 등 안전한 탐색 시 일부 허용)</td>
</tr>
</tbody></table>
<p><strong>갱신 방법</strong></p>
<ul>
<li><strong>쿠키 자동 전송 &amp; 서버측 교체</strong>: 클라이언트가 재발급 API를 호출할 때 브라우저가 쿠키에 저장된 Refresh Token을 <code>credentials: &#39;include&#39;</code>(또는 <code>withCredentials: true</code>) 옵션에 따라 자동으로 함께 전송한다. 서버는 검증 후 새로 생성된 Refresh Token을 응답의 <code>Set-Cookie</code> 헤더로 재발급하여 브라우저 내 쿠키를 덮어쓴다. (프론트엔드 코드에서 쿠키 값을 직접 조작하지 않음)</li>
</ul>
<hr>
<h1 id="sessionstorage">SessionStorage</h1>
<p>웹 스토리지 API를 활용하여 브라우저의 단일 탭(세션) 내에만 토큰을 보관하는 방식이다.</p>
<p><strong>장점</strong></p>
<ul>
<li><strong>격리된 세션 관리</strong>: 같은 도메인이더라도 탭 단위로 데이터가 독립되어 있어, 동일 브라우저 내 탭별 개별 로그인이나 단기 세션 데이터 관리에 유용하다.</li>
<li><strong>자동 브라우저 정돈</strong>: 탭을 닫는 즉시 저장된 데이터가 자동으로 소멸하여 잔여 데이터가 남지 않는다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>XSS 공격에 완전히 노출</strong>: <code>localStorage</code>와 동일하게 JavaScript로 접근 가능하므로 <code>sessionStorage.getItem()</code>을 통한 XSS 탈취 위협을 방지하지 못한다.</li>
<li><strong>Refresh Token 본연의 역할 수행 저해</strong>: 탭을 닫으면 토큰이 즉시 소멸하므로, 브라우저 재시작 후에도 로그인 상태를 유지해 주는 Refresh Token의 목적을 달성하기 어렵다.</li>
<li><strong>탭 간 공유 불가</strong>: 새로운 탭을 열면 로그인 상태가 이어지지 않고 다시 인증을 거쳐야 한다.</li>
</ul>
<p><strong>갱신 방법</strong></p>
<ul>
<li><strong>세션 내 스토리지 키-값 덮어쓰기</strong>: <code>sessionStorage.getItem(&#39;refreshToken&#39;)</code>으로 토큰을 불러와 재발급 API 요청을 보낸 후, 응답으로 수신한 신규 토큰을 <code>sessionStorage.setItem(&#39;refreshToken&#39;, newRefreshToken)</code>으로 현재 탭 내 스토리지에 덮어써서 업데이트한다.</li>
</ul>
<hr>
<h1 id="localstorage">LocalStorage</h1>
<p>웹 스토리지 API를 활용하여 브라우저 내 영구 저장 공간에 토큰을 보관하는 방식이다.</p>
<p><strong>장점</strong></p>
<ul>
<li><strong>자동 로그인 구현 용이</strong>: 브라우저를 닫거나 컴퓨터를 재부팅해도 데이터가 유지되므로, 지속적인 로그인 상태를 손쉽게 유지할 수 있다.</li>
<li><strong>탭 간 상태 공유</strong>: 동일한 도메인이라면 여러 탭/창에서 데이터가 동기화되어 어느 탭에서든 동일한 인증 상태를 유지한다.</li>
<li><strong>구현이 간단함</strong>: 백엔드의 별도 쿠키/CORS 설정 없이 JavaScript API(<code>getItem</code>, <code>setItem</code>)로 손쉽게 제어 가능하다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>XSS 공격에 완전히 노출</strong>: JavaScript로 자유롭게 접근할 수 있어, 악성 스크립트 실행 시 <code>localStorage.getItem(&#39;refreshToken&#39;)</code>으로 토큰이 즉시 탈취된다.</li>
<li><strong>높은 탈취 위험성</strong>: 영구 보관 특성상 유효기간이 긴 Refresh Token이 탈취될 경우 장기간 악용될 위험이 매우 크다.</li>
</ul>
<p><strong>갱신 방법</strong></p>
<ul>
<li><strong>스토리지 키-값 덮어쓰기</strong>: API 인터셉터가 401 응답 수신 시 <code>localStorage.getItem(&#39;refreshToken&#39;)</code>으로 기존 토큰을 추출해 재발급 API 요청 바디/헤더로 전송한다. 서버 응답으로 전달받은 신규 토큰을 <code>localStorage.setItem(&#39;refreshToken&#39;, newRefreshToken)</code> 명령어로 덮어써서 업데이트한다.</li>
</ul>
<hr>
<h1 id="sisterdy-결론">sisterdy 결론</h1>
<blockquote>
<p>크로스 플랫폼이나 모바일 호환성을 고려하지 않아도 되는 웹 프론트엔드에서 JWT 토큰을 이용한 로그인을 구현한다면, 액세스 토큰과 리프레시 토큰은 쿠키에 저장할 것입니다.
 JavaScript를 통한 직접적인 접근을 차단하여 악성 스크립트 주입 등 XSS 공격을 통한 토큰 탈취 위험을 최소화할 수 있고, 브라우저에서 쿠키를 자동으로 관리해주기 때문에 별도의 토큰 저장 로직을 구현할 필요가 없기 때문입니다.</p>
</blockquote>
<p>클라이언트가 로그인을 요청하면, 서버는 먼저 회원가입 여부와 비밀번호를 검증합니다. 인증에 성공하면 짧은 수명의 Access Token과 긴 수명의 Refresh Token을 동시에 생성해 쿠키로 응답하며, 이때 Refresh Token은 서버의 Redis 등에도 함께 저장해둡니다. 이후 API 요청 시 브라우저가 쿠키를 자동으로 첨부하고, Access Token이 만료되어 서버로부터 401 응답을 받으면, 프론트엔드는 저장되어 있던 Refresh Token 쿠키를 이용해 Access Token을 재발급받습니다. 다만 두 토큰을 모두 HttpOnly 쿠키에 저장할 경우 프론트엔드 JS 코드가 Access Token의 만료 여부를 직접 확인할 수 없기 때문에, 401 응답을 감지해 자동으로 재발급을 요청하는 Axios Interceptor 로직과 함께 서버 측에서 재발급 API(/refresh)를 Access Token 검증 대상에서 제외해 주는 설정이 필요합니다. 또한 로그인 상태를 화면에 표시하기 위해서는, 로그인 성공 시 HttpOnly가 아닌 별도의 상태 확인용 쿠키나 응답 값을 함께 내려주는 방식을 고려할 수 있습니다.</p>
<p>이와 같은 방식으로 JWT 로그인을 구현하면, HttpOnly 쿠키로 XSS를 통한 직접적인 토큰 탈취는 막을 수 있지만, CSRF, 네트워크 스니핑, 기기 탈취 등 다른 경로를 통한 토큰 유출을 완벽히 차단할 수는 없으며, 로그인·재발급 API에 대한 무차별 대입 공격이나 비정상적인 반복 요청도 방어할 수 없다는 한계가 있습니다. 참고로 JWT는 서명 검증 구조상 클라이언트가 임의로 값을 변조해도 서버의 서명 검증 단계에서 즉시 거부되기 때문에, 쿠키 저장 방식이 막아주는 것은 &quot;변조&quot;가 아니라 &quot;탈취&quot;라는 점을 정확히 짚고 넘어갈 필요가 있습니다. 따라서 토큰 자체의 보관 보안뿐만 아니라, 토큰이 탈취되었을 때의 대응 체계와 발급·재발급 API에 대한 보호도 함께 필요합니다.</p>
<p>이를 위해 HttpOnly, Secure 속성과 함께 CSRF 방어 및 일반 탐색 호환성을 위한 SameSite=Lax 속성을 지정하고, Access Token의 만료 시간은 짧게 설정해 탈취 시 피해 범위를 줄이겠습니다. Refresh Token은 서버에 별도로 저장해 탈취가 의심되는 경우 즉시 무효화할 수 있도록 하고, 재발급 시마다 새로운 Refresh Token으로 교체하는 로테이션(RTR, Refresh Token Rotation) 방식을 적용해, 이미 사용된 구 토큰으로 재발급이 시도될 경우 탈취로 간주하고 해당 계정의 모든 세션을 즉시 차단하겠습니다. 또한 로그인 및 토큰 재발급 요청에 Rate Limiting을 적용하고, 특정 IP에서 요청이 급증하거나 비정상적인 경로 탐색이 발생하는 경우 이를 탐지해 차단하는 방식으로 보안을 강화하겠습니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>저장</strong></td>
<td>쿠키 / HttpOnly</td>
</tr>
<tr>
<td><strong>발급절차</strong></td>
<td>로그인 시 Access + Refresh 동시 발급 / Access 만료 시 Refresh로 Access만 재발급</td>
</tr>
<tr>
<td><strong>한계</strong></td>
<td>탈취 위험, 로그인·재발급 API에 대한 무차별 대입(Brute Force) / DoS성 공격</td>
</tr>
<tr>
<td><strong>극복 ①</strong></td>
<td><code>SameSite=Lax</code> + <code>Secure</code>로 CSRF 방어 (필요시 Anti-CSRF 토큰 병행)</td>
</tr>
<tr>
<td><strong>극복 ②</strong></td>
<td>RTR(Refresh Token Rotation)로 탈취 탐지 및 무효화</td>
</tr>
<tr>
<td><strong>극복 ③</strong></td>
<td>Rate Limiting으로 API 공격(무차별 대입/DoS) 방어</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[파워포즈]]></title>
            <link>https://velog.io/@cold_as_hell/POWER-POSE</link>
            <guid>https://velog.io/@cold_as_hell/POWER-POSE</guid>
            <pubDate>Thu, 03 Sep 2026 03:50:03 GMT</pubDate>
            <description><![CDATA[<p>8월에는 좋은 분들의 권유로 생애 첫 개인 공모전에 도전했고, 파워 포즈(Power posing) 라는 멋진 것도 배웠다.</p>
<hr>
<h1 id="power-posing">Power posing</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/1fb645c5-aa87-4d65-8364-d162f4cec540/image.png" alt=""></p>
<p>갤럽에 따르면 의사결정의 약 70%는 감정적 요인에, 나머지 30%만 이성적 요인에 기반한다고 한다.</p>
<p>그렇다면 사람의 감정적 판단에 영향을 주는 것에는 무엇이 있을까?
보여지는 외적인 부분, 즉 얼마나 자신감 있어 보이는지도 영향을 줄 수 있다.
이때 활용되는 자세가 바로 파워포즈(Power Pose)이다.</p>
<p>타인에게 자신감 있는 인상을 주고, 스스로의 심리에도 영향을 줄 수 있는 파워포즈에 대해 알아보자.</p>
<hr>
<h1 id="자신감-없는-자세">자신감 없는 자세</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/18573fd1-0740-4643-b667-8667d58fddf1/image.png" alt=""></p>
<blockquote>
<p>몸을 움츠리는 자세는 스트레스 호르몬 분비를 촉진 해서 만성 피로와 불안감을 유발한다.</p>
</blockquote>
<p>구체적으로 피해야 할 자세는 이런 것들이다.</p>
<ul>
<li>상체를 감싸거나 배꼽에 손을 모으고 어깨를 구부린 자세</li>
<li>목 뒤를 만지는 자세</li>
<li>의자에서 등을 뗀 채 책상에 팔을 기대는 자세</li>
</ul>
<hr>
<h1 id="위풍당당-파워포즈">위풍당당 파워포즈</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/3e4d3067-a649-48fe-87e9-0df00e634b7c/image.png" alt=""></p>
<blockquote>
<p>테스토스테론 분비를 촉진시켜 기억력과 자신감을 향상시키는 포즈.</p>
</blockquote>
<p>구체적인 자세는 </p>
<ul>
<li>(1) 양손을 허리에 얹기</li>
<li>(2) 다리를 어깨너비로 벌리기</li>
<li>(3) 가슴과 허리를 펼치기</li>
</ul>
<p>이렇게 2분간 파워포즈를 취하면 긴장감을 완화하고 자신감을 얻을 수 있다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>자신감에 도움이 되는 자세를 취해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT Access Token 저장 위치]]></title>
            <link>https://velog.io/@cold_as_hell/Access-Token-Refresh-Token</link>
            <guid>https://velog.io/@cold_as_hell/Access-Token-Refresh-Token</guid>
            <pubDate>Thu, 03 Sep 2026 03:28:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/17f6f6a0-b89e-431c-924e-151a2169ac9d/image.png" alt=""></p>
<h1 id="이걸-왜-고민해야하나">이걸 왜 고민해야하나</h1>
<p>JWT 토큰의 장점은 무상태 방식으로 동작하기 때문에 이전 요청의 상태나 다른 데이터를 참조하지 않고 독립적으로 처리한다는 점이다. JWT를 이용하면 로그인 시 세션 불일치 문제를 걱정하거나 별도의 외부 저장소를 만들 필요가 없어진다.</p>
<p>하지만, JWT는 토큰 탈취에 취약하다.
공격자는 토큰이 만료될 때까지 정상 사용자처럼 서비스에 요청을 보낼 것인데,
사용자가 로그아웃하거나 관리자가 계정을 비활성화해도, 이미 발급된 Access Token이 만료되기 전까지는 계속 사용할 수 있다.</p>
<p>이를 해결하기 위해 클라이언트는 여러 방법을 모색할 수 있는데, 바로 </p>
<hr>
<h1 id="access-token의-저장">Access Token의 저장</h1>
<h2 id="서버">서버</h2>
<p>Access Token은 서버측에서 관리할 수 있다. 
그치만 오늘의 주제는 프론트엔드 관점이므로, 자세히 살펴보지 않는다.</p>
<h2 id="인메모리-in-memory">인메모리 (In-Memory)</h2>
<p>가장 단순한 방법. Access Token을 그냥 자바스크립트 변수(또는 React state/context)에 담아두는 것이다.</p>
<pre><code class="language-js">const [accessToken, setAccessToken] = useState(null);</code></pre>
<p><strong>장점</strong></p>
<ul>
<li><strong>XSS 공격에 가장 안전</strong>: 브라우저 저장소(Storage)나 쿠키에 기록되지 않고 메모리 상에만 존재하므로, 스크립트를 통한 토큰 스크래핑(Direct Extraction)을 방지할 수 있다.</li>
<li><strong>구현이 간단함</strong>: 별도의 브라우저 API 설정 없이 앱 내부 상태로 손쉽게 관리 가능하다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>휘발성(Persistence 부족)</strong>: 페이지를 새로고침(F5)하거나 탭을 닫으면 메모리가 초기화되어 토큰이 사라진다.</li>
<li><strong>Silent Refresh 필수</strong>: 로그인 상태를 유지하려면 <code>httpOnly</code> 쿠키에 저장된 Refresh Token을 이용해 새로고침 시마다 토큰을 재발급받는 로직이 반드시 수반되어야 한다.</li>
</ul>
<hr>
<h2 id="쿠키-cookie">쿠키 (Cookie)</h2>
<p>서버가 <code>Set-Cookie</code> 헤더를 통해 브라우저 쿠키에 토큰을 저장하도록 지시하는 방식이다.</p>
<p><strong>장점</strong></p>
<ul>
<li><strong><code>HttpOnly</code> 설정 시 XSS 방어</strong>: JavaScript(<code>document.cookie</code>)를 통한 접근이 차단되어 악성 스크립트로 토큰을 탈취할 수 없다.</li>
<li><strong>자동 전송의 편리함</strong>: 설정된 도메인으로 요청을 보낼 때 브라우저가 쿠키를 자동으로 첨부한다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>CSRF 공격에 취약</strong>: 자동 전송 특성 때문에 타 사이트에서의 요청에 악용될 수 있으므로, 보안 옵션 설정이 필수적이다.</li>
</ul>
<table>
<thead>
<tr>
<th>속성</th>
<th>방어 대상 및 역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>HttpOnly</code></td>
<td><strong>XSS 방어</strong>: JavaScript 접근 차단</td>
</tr>
<tr>
<td><code>Secure</code></td>
<td><strong>중간자 공격(MITM) 방어</strong>: HTTPS 암호화 통신 채널에서만 전송 허용</td>
</tr>
<tr>
<td><code>SameSite=Strict</code> / <code>Lax</code></td>
<td><strong>CSRF 방어</strong>: 타 사이트에서의 교차 요청 제어 (<code>Strict</code>: 전면 차단, <code>Lax</code>: GET 등 안전한 탐색 시 일부 허용)</td>
</tr>
</tbody></table>
<hr>
<h2 id="sessionstorage">SessionStorage</h2>
<p>웹 스토리지 API 중 하나로, 브라우저의 탭(세션) 단위로 독립된 저장 공간을 제공한다.</p>
<p><strong>장점</strong></p>
<ul>
<li>같은 도메인이라도 탭 간 데이터가 공유되지 않아 독립된 단기 세션 데이터 관리에 유용하며, 탭을 닫으면 데이터가 자동 삭제된다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><code>localStorage</code>와 동일하게 JavaScript로 자유롭게 접근 가능하여 <strong>XSS 공격에 완전히 노출</strong>된다. (<code>sessionStorage.getItem()</code>으로 쉽게 추출 가능)</li>
<li>탈취 위험이 크므로 Access Token이나 Refresh Token 등 민감한 인증 데이터 저장소로는 적합하지 않다.</li>
</ul>
<hr>
<h2 id="localstorage">LocalStorage</h2>
<p>브라우저를 닫거나 컴퓨터를 재부팅해도 데이터가 영구적으로 유지되는 키-값(Key-Value) 저장소이다.</p>
<p><strong>장점</strong></p>
<ul>
<li>탭과 세션을 넘어 로그인 상태가 계속 유지되며, 서버 통신 없이 즉시 조회 가능하여 구현이 매우 직관적이다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li><strong>XSS(Cross-Site Scripting) 공격에 무방비 상태로 노출</strong>된다.</li>
<li>악성 스크립트가 삽입되는 순간 <code>localStorage.getItem()</code>을 통해 토큰이 통째로 탈취될 수 있어, 인증 토큰 저장소로는 보안상 치명적이다.</li>
</ul>
<hr>
<h1 id="서버-vs-프론트">서버 vs 프론트</h1>
<p>Access Token은 저장 주체에 따라 관리 방식과 트레이드오프(:하나를 얻기 위해 다른 하나를 어느 정도 포기해야 하는 관계) 가 완전히 달라진다.</p>
<p>서버는 &quot;토큰을 저장하지 않아도&quot; 되는 게 JWT의 원래 장점이고, 프론트는 &quot;저장을 안 할 수가 없다&quot;는 게 차이다. 서버가 저장한다면 그건 주로 <strong>강제 무효화(로그아웃, 탈취 대응)</strong>를 위한 선택적 조치이고, 프론트가 저장하는 이유는 <strong>매 요청마다 인증 정보를 실어 보내야 하기 때문</strong>에 필수적인 것이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>서버</th>
<th>프론트엔드</th>
</tr>
</thead>
<tbody><tr>
<td><strong>저장 위치</strong></td>
<td>Redis, DB 등 (선택적, 필수는 아님)</td>
<td>브라우저 (인메모리 / 쿠키 / Storage)</td>
</tr>
<tr>
<td><strong>저장 목적</strong></td>
<td>무효화(블랙리스트), 세션 통제</td>
<td>매 요청마다 인증 헤더에 실어 보내기 위함</td>
</tr>
<tr>
<td><strong>JWT의 무상태성 유지 여부</strong></td>
<td>저장 안 하면 무상태 유지, 저장하면 사실상 세션 방식과 유사해짐</td>
<td>저장 여부와 무관하게 무상태성 유지</td>
</tr>
<tr>
<td><strong>주요 위협</strong></td>
<td>서버 침해 시 전체 토큰 노출 가능 (DB/Redis 해킹)</td>
<td>XSS(스크립트 탈취), CSRF(자동 전송 악용)</td>
</tr>
<tr>
<td><strong>통제권</strong></td>
<td>강함 — 즉시 무효화, 강제 로그아웃 가능</td>
<td>약함 — 만료 전까지 서버가 개입 불가</td>
</tr>
<tr>
<td><strong>확장성 영향</strong></td>
<td>저장할 경우 매 요청마다 조회 필요 → 병목 가능</td>
<td>클라이언트 로컬 처리라 서버 부하 없음</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[10]]></title>
            <link>https://velog.io/@cold_as_hell/%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@cold_as_hell/%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 30 Aug 2026 12:29:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/edba9867-4ed6-417b-b2eb-5328c2442d5b/image.png" alt=""></p>
<p>알림 시스템 설계 - 면접관을 맡게되었다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/d2b58103-de4c-44fc-9dc5-5151605e3da2/image.png" alt=""></p>
<p>이게 알림시스템이다.</p>
<hr>
<h1 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h1>
<p>오늘도 역시나 면접관과의 대화를 통해 1단계를 진행한다.
면접관과의 대화 예시는 아래와 같다.</p>
<pre><code>지원자: 이 시스템은 어떤 종류의 알림을 지원해야 하나요?
면접관: 푸시 알림, SMS 메시지, 그리고 이메일입니다.
지원자: 실시간(real-time) 시스템이어야 하나요?
면접관: 연성 실시간(soft real-time) 시스템이라고 가정합니다. 알림은 가능한
한 빨리 전달되어야 하지만 시스템에 높은 부하가 걸렸을 때 약간의
지 연은 무방합니다.
지원자: 어 떤 종류의 단말을 지원해야 하나요?
면접관: iOS 단말, 안드로이드(android) 단말, 그리고 랩톱/데스크톱을 지원해
야 합니다.
지원자: 사용자에게 보낼 알림은 누가 만들 수 있나요?
면접관: 클라이언트 애플리케이션 프로그램이 만들 수도 있구요. 서버 측에서
스케줄링 할 수도 있습니다.
지원자: 사용자가 알림을 받지 않도록(opt-out) 설정할 수도 있어야 하나요?
면접관: 네. 해당 설정을 마친 사용자는 더 이상 알림을 받지 않습니다.
지원자: 하루에 몇 건의 알림을 보낼 수 있어야 하나요?
면접관: 천만 건의 모바일 푸시 알림, 백만 건의 SMS 메시지, 5백만 건의 이메
일을 보낼 수 있어야 합니다.</code></pre><p><strong>요약</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>요구사항</th>
</tr>
</thead>
<tbody><tr>
<td><strong>지원 채널</strong></td>
<td>푸시 알림, SMS, 이메일</td>
</tr>
<tr>
<td><strong>지원 단말</strong></td>
<td>iOS, Android, 웹(랩톱/데스크톱)</td>
</tr>
<tr>
<td><strong>알림 생성 주체</strong></td>
<td>클라이언트 애플리케이션, 서버 측 스케줄링</td>
</tr>
<tr>
<td><strong>Opt-out</strong></td>
<td>사용자가 설정 시 이후 알림 미수신</td>
</tr>
<tr>
<td><strong>실시간성</strong></td>
<td>연성 실시간(soft real-time) — 최대한 빠르게 전달, 고부하 시 약간의 지연 허용</td>
</tr>
<tr>
<td><strong>일일 트래픽</strong></td>
<td>푸시 1,000만 건 + SMS 100만 건 + 이메일 500만 건 = 총 1,600만 건</td>
</tr>
</tbody></table>
<hr>
<h1 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h1>
<p>iOS 푸시 알림, 안드로이드 푸시 알림, SMS 메시지, 그리고 이메일을 지원하는 알림 시스템의 개략적 설계안은
주로 <strong>알림 유형별지원방안</strong>, <strong>연락처 정보 수집 절차</strong>, <strong>알림 전송 및 수신 절차</strong>로 나뉘어 설계된다.</p>
<h2 id="알림-유형별-지원-방안">알림 유형별 지원 방안</h2>
<p><strong>IOS</strong>
<img src="https://velog.velcdn.com/images/cold_as_hell/post/7299994a-9a55-4b00-9849-59722901e460/image.png" alt=""></p>
<ol>
<li>알림 제공자 (provider) :  알림 요청(notification request)을 만들어 애플 푸시 알림 서비스(APNS： Apple Push Notification Service)로 보내는 주체. 
알림 요청을 만들려면 다음과 같은 데이터가 필요하다.</li>
</ol>
<ul>
<li>단말 토큰 (device token) : 알림 요청을 보내는 데 필요한 고유 식별자</li>
<li>페이로드 (payload) : 알림 내용을 담은 JSON 딕셔너리</li>
</ul>
<pre><code class="language-json">{
  &quot;aps&quot;: {
    &quot;alert&quot;: {
      &quot;title&quot;: &quot;치이카와 극장판&quot;,
      &quot;body&quot;: &quot;9월 30일 개봉예정&quot;,
      &quot;action-loc-key&quot;: &quot;chikawa&quot;
    },
    &quot;badge&quot;: 5
  }
}</code></pre>
<ol start="2">
<li><p>APNS
애플이 제공하는 원격 서비스. 푸시 알림을 iOS 장치로 보내는 역할.</p>
</li>
<li><p>IOS 기기
푸시 알림을 수신하는 사용자 단말.</p>
</li>
</ol>
<p><strong>안드로이드</strong></p>
<p>안드로이드 푸시 알림도 비슷한 절차로 전송된다. APNS 대신 FCM(Firebase Cloud Messaging)을 사용한다는 점만 다르다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/329aba80-e817-4d87-943e-930216015598/image.png" alt=""></p>
<p><strong>그 외</strong>
sms, 이메일
sms서비스와 이메일 서비스를 거쳐 사용자 단말로 알림 전송.</p>
<h2 id="연락처-정보-수집-절차">연락처 정보 수집 절차</h2>
<p>알림을 보내려면 모바일 단말 토큰, 전화번호, 이메일 주소 등의 정보가 필요하다. </p>
<p>사용자가 우리 앱을 설치하거나 처음으로 계정을 등록하면 API 서버는 해당 사용자의 정보를 수집하여 데이터베이스에 저장한다. </p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/acfb588b-25ca-4f33-919d-b9caae8c76c7/image.png" alt=""></p>
<p>이때, 사용자 정보와 기기는 1:N의 관계를 갖는다.
한 사용자가 여러 단말을 가질 수 있고, 알림은 사용자의 모든 단말로 전송되어야 하기 때문이다.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/66f5f6dd-6a82-4812-a7a7-a1cee016e178/image.png" alt=""></p>
<hr>
<h2 id="알림-전송-및-수신-절차">알림 전송 및 수신 절차</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/bf9a99ad-5f51-4d96-a566-8435165fea4b/image.png" alt=""></p>
<p>이전 단계들을 토대로 개략적인 설계안을 작성하자.</p>
<ol>
<li><p><strong>서비스(1~N):</strong> 마이크로서비스/크론잡 등이 알림 시스템 API를 호출해 알림 발송을 요청한다.</p>
</li>
<li><p><strong>알림 시스템:</strong> 요청을 받아 제3자 서비스 규격에 맞는 페이로드(Payload)를 생성한다.</p>
</li>
<li><p><strong>제3자 서비스 및 단말:</strong> 확장성과 지역별 제약(예: 중국의 FCM 불가)을 고려해 제3자 서비스(APNs, FCM, JPush 등)를 거쳐 최종 단말(iOS, Android, SMS, Email)로 전달한다.</p>
</li>
</ol>
<p>이 설계에는 문제가 있다.</p>
<ul>
<li><strong>SPOF (단일 장애점):</strong> 서버 1대 장애 발생 시 전체 알림 서비스 마비</li>
<li><strong>규모 확장성 한계:</strong> DB, 캐시 등 개별 컴포넌트의 독립적 스케일아웃 불가</li>
<li><strong>성능 병목:</strong> 페이로드 생성(HTML 등) 및 제3자 연동 대기로 인한 과부하 발생</li>
</ul>
<hr>
<p>초안의 문제점을 알아 차린 뒤에는 개략적 설계안을 <strong>개선</strong>해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/aaf23284-95f1-4543-b6f5-f12163fc1556/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>기존 초안의 문제점</th>
<th>개선 방안</th>
<th>개선 효과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>장애 관리</strong></td>
<td><strong>SPOF (단일 장애점)</strong><br>- 단일 서버 장애 시 전체 알림 중단</td>
<td><strong>알림 서버 증설 및 메시지 큐 도입</strong><br>- 알림 서버 다중화<br>- 알림 종류별 별도 큐 분리</td>
<td>- 단일 서버 장애 시에도 시스템 유지<br>- 특정 제3자 서비스 장애 전파 차단</td>
</tr>
<tr>
<td><strong>확장성</strong></td>
<td><strong>규모 확장성 한계</strong><br>- DB/캐시 등 독립 스케일아웃 불가</td>
<td><strong>DB, 캐시, 작업 서버 분리</strong><br>- DB/캐시 주 서버에서 독립<br>- 전송 전담 작업 서버 레이어 구축</td>
<td>- 트래픽 및 데이터 증가 시 컴포넌트별 독립적인 수평 확장 가능</td>
</tr>
<tr>
<td><strong>성능 및 과부하</strong></td>
<td><strong>성능 병목</strong><br>- 페이로드 생성 및 외부 대기로 과부하</td>
<td><strong>메시지 큐 기반 비동기 처리</strong><br>- 알림 서버는 큐에 메시지 발행만 수행<br>- 작업 서버가 꺼내어 제3자 연동 처리</td>
<td>- 서버 간 강한 결합 제거<br>- 대량 트래픽 발생 시 메시지 큐가 버퍼 역할 수행</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ef5f0548-d1fb-46ac-86a7-8238a7ee6691/image.png" alt=""></p>
<p>① API를 호출하여 알림 서버로 알림을 보낸다.
② 알림 서버는 사용자 정보, 단말 토큰, 알림 설정 같은 메타데이터(metadata)를 캐시나 데이터베이스에서 가져온다.
③ 알림 서버는 전송할 알림에 맞는 이벤트를 만들어서 해당 이벤트를 위한 큐에 넣는다. 가령 iOS 푸시 알림 이벤트는 iOS 푸시 알림 큐에 넣어야 한다.
④ 작업 서버는 메시지 큐에서 알림 이벤트를 꺼낸다.
⑤ 작업 서버는 알림을 제3자 서비스로 보낸다.
⑥ 제3자 서비스는 사용자 단말로 알림을 전송한다.</p>
<hr>
<h1 id="3단계-상세-설계">3단계 상세 설계</h1>
<h2 id="안정성-reliability">안정성 (Reliability)</h2>
<p>여러 서버와 시스템이 동시에 동작하는 분산 환경에서는 단일 서버 환경보다 서버 장애, 네트워크 오류, 메시지 중복 및 유실 등이 발생할 가능성이 높다.</p>
<p>따라서 분산환경에서 운영될 알림 시스템을 설계할 때는 안정성을 확보하기 위한 사 
항을 고려해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/594711b7-ddcf-4283-9667-912bee1e577c/image.png" alt=""></p>
<h3 id="1-데이터-손실-방지">1. 데이터 손실 방지</h3>
<p>알림은 지연되거나 순서가 틀려도 괜찮지만, 사라지면 곤란하다.
이 요구사항을 만족하려면 알림 시스템은 알림데이터를 데이터베이스에 보관하고 재시도 메커니즘을 구현해야 한다.
알림 로그(notification log) 데이터베이스를 유지하는 것이 한 가지 방법이다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/addcafb9-bda9-4e75-b997-684b71dd7c44/image.png" alt=""></p>
<h3 id="2-알림-중복-발송-방지">2. 알림 중복 발송 방지</h3>
<p>같은 알림의 중복 전송을 완전히 막을 수는 없다. 
알림은 대부분 한 번만 전송되지만, 분산 시스템 특성상 가끔 중복이 발생한다. 
빈도를 줄이려면 중복 탐지 메커니즘을 도입하고 오류를 신중히 처리해야 한다. 
간단한 방법은, 알림 도착 시 이벤트 ID로 이전에 처리된 적 있는지 확인해 중복이면 버리고 아니면 발송하는 것이다.</p>
<h2 id="추가로-필요한-컴포넌트-및-고려사항">추가로 필요한 컴포넌트 및 고려사항</h2>
<h3 id="알림-템플릿">알림 템플릿</h3>
<p>대형 알림 시스템은 하루 수백만 건의 알림을 처리하는데, 대부분 형식이 비슷하다. </p>
<p>대형 알림 시스템에서 알림 템플릿이 사용된다.
인자, 스타일, 추적 링크만 조정하면 사전에 정한 형식에 맞춰 알림을 만들어 내는 틀로, 매번 처음부터 만들 필요를 없애 준다. 이를 통해 형식의 일관성을 유지하고, 오류 가능성과 작성 시간을 줄일 수 있다.</p>
<pre><code>본문:
모두가 알고 있는 그 시험이 코앞으로 다가왔습니다. 
빅데이터분석기사 필기시험이 [date]에 있습니다! 지금부터 벼락치기 시작하지 않으면 늦습니다!

타이틀 (CTA: Call to Action):
지금 바로 빅분기 필기 벼락치기 시작하세요!
모두가 알고 있는 그 시험이 코앞으로 다가왔습니다.빅데이터분석기사 필기시험이 [date]에 있습니다! 지금부터 벼락치기 시작하지 않으면 늦습니다!

타이틀 (CTA: Call to Action):
지금 바로 빅분기 필기 벼락치기 시작하세요!</code></pre><h2 id="알림-설정">알림 설정</h2>
<p>사용자마다 알림 피로도가 다르기 때문에, 서비스는 사용자가 알림 수신 여부와 방식을 세밀하게 조정할 수 있도록 알림 설정(Notification Settings) 테이블을 별도로 관리한다.
테이블에는 다음과 같은 필드들이 필요할 것이며, 이와 같은 설정을 도입한 뒤에는 특정 종류의 알림을 보내기 전에 반드시 해당 사용자가 해당 알림을 켜 두었는지 확인해야 한다.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/86b1b629-283a-40b8-b32c-55b79315a14b/image.png" alt=""></p>
<p>** 그 외**</p>
<p>전송률 제한 : 한 사용자가 받을 수 있는 알림의 빈도를 제한하는 것</p>
<p>재시도 방법 : 제3자 서비스가 알림 전송에 실패하면, 해당 알림을 재시도 전용 큐에 넣고,
같은 문제가 계속해서 발생하면 개발자에게 통지한다.</p>
<p>푸시 알림과 보안 : iOS·안드로이드 앱의 알림 전송 API는 appKey와 appSecret을 이용해 인증을 수행하며, 이를 통해 인증되었거나 승인된 클라이언트만 해당 API로 알림을 보낼 수 있도록 보안을 유지한다.</p>
<p>큐 모니터링: 알림 큐에 쌓인 알림의 개수가 너무 많으면 서버들이 이벤트를 빠르게 처리하지 못하는 것. 이런 경우에는 작업 서버를 증설하자.</p>
<p>이벤트 추적 : 알림 시스템에서는 <code>알림 확인율</code>, <code>클릭률</code>, <code>실제 앱 사용으로의 전환율</code>으로의 전환율 같은 메트릭(사용자 행동을 수치로 측정한 지표)을 추적하는 것이 사용자를 이해하는 데 중요하며, 이를 위해 데이터 분석 서비스가 제공하는 이벤트 추적 기능과 알림 시스템을 통합하는 것이 일반적이다.</p>
<h2 id="개선된-설계안">개선된 설계안</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/21eca1f3-d098-4d22-ae26-9fb3cb642962/image.png" alt=""></p>
<p>지금까지의 내용을 반영하여 수정한 설계안은 다음과 같다.</p>
<pre><code>인증/전송률 제한: 알림 서버에 authentication과 rate-limiting 추가
재시도 기능: 전송 실패 시 큐에 재삽입하여 지정 횟수만큼 재시도
전송 템플릿: 알림 생성을 단순화하고 내용 일관성 유지
모니터링/추적: 시스템 상태 확인 및 향후 개선을 위한 관측 기능 추가</code></pre><h1 id="4단계-마무리">4단계 마무리</h1>
<p>알림은 사용자 행동을 기반으로 한 서비스 설계 및 수익 창출에 있어 필요불가결한 기능이다. </p>
<p>이번 장을 통해 우리는 좋은 알림 시스템이란 규모 확장이 쉽고, 푸시 알림·SMS·이메일 등 다양한 채널을 지원하며, 컴포넌트 간 연쇄 장애를 예방하기 위해 메시지 큐를 적극 활용하는 구조라는 것을 알게 되었다. </p>
<p>이러한 설계 원칙은 대규모 시스템을 구축할 때 기능성과 효율성을 모두 확보하기 위해 반드시 알아두어야 할 것이다.</p>
<p>시스터디 화이팅~!</p>
<hr>
<h1 id="-잼얘">+) 잼얘</h1>
<p>스터디장에게 남소(?) 가능함.</p>
<p>특징</p>
<ol>
<li>기본정보 : 95 or 94 or 93 년생 남자</li>
<li>취미 : 미술품 감상, 러닝, 독서</li>
<li>거주지: 경기도</li>
<li>기타: 친구를 소개 받고 있음. </li>
<li>친구가 생기면 할 일 : 영화 오디세이 감상</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그아웃이 잘 되지 않으면?]]></title>
            <link>https://velog.io/@cold_as_hell/%EC%BA%90%EC%8B%B1%EC%84%B8%EC%85%98-%EC%98%A4%EB%A5%98-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4</link>
            <guid>https://velog.io/@cold_as_hell/%EC%BA%90%EC%8B%B1%EC%84%B8%EC%85%98-%EC%98%A4%EB%A5%98-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4</guid>
            <pubDate>Sun, 30 Aug 2026 04:42:02 GMT</pubDate>
            <description><![CDATA[<h1 id="로그아웃">로그아웃</h1>
<p>웹사이트, 앱, 컴퓨터 시스템 등에 연결된 자신의 계정 접속을 안전하게 종료하고 나가는 것
어떠한 원인으로든 로그아웃이 잘 이루어지지 않으면 기존 인증 정보가 남아 다른 사용자가 로그인했을 때 이전 사용자의 계정 정보로 잘못 인식되거나, 개인정보 및 구매이력이 잘못 처리되는 문제가 발생할 수 있다.</p>
<table>
<thead>
<tr>
<th>원인</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>클라이언트 토큰 미삭제</strong></td>
<td>로그아웃 시 브라우저의 <code>localStorage</code>/<code>sessionStorage</code>/쿠키에 저장된 토큰을 삭제하지 않아 이전 인증 정보가 남음</td>
</tr>
<tr>
<td><strong>서버 세션/Refresh Token 미폐기</strong></td>
<td>클라이언트 토큰만 지우고 서버 DB의 세션이나 Refresh Token을 폐기하지 않으면, 해당 정보가 재사용될 수 있음</td>
</tr>
<tr>
<td><strong>다중 탭/창 동기화 미흡</strong></td>
<td>여러 탭이 열려있을 때 한 탭에서 로그아웃해도 다른 탭의 상태(전역 스토어, 캐시)가 갱신되지 않아 이전 인증 정보로 요청이 이어짐</td>
</tr>
<tr>
<td><strong>캐싱 문제</strong></td>
<td>개인화된 API 응답에 <code>Cache-Control</code> 설정이 없어 브라우저나 CDN이 이전 사용자의 응답을 캐싱해 재사용</td>
</tr>
<tr>
<td><strong>토큰 만료 시간 설정 오류</strong></td>
<td>Access/Refresh Token의 만료 시간이 지나치게 길게 설정되어 로그아웃 후에도 유효 기간이 남아있음</td>
</tr>
<tr>
<td><strong>클라이언트 전역 상태 미초기화</strong></td>
<td>Redux, React Query 등 프론트엔드 캐시에 이전 사용자 정보가 남아있는 상태로 새 로그인 진행</td>
</tr>
</tbody></table>
<hr>
<p>....</p>
<p>(추가예정)</p>
<h1 id="시스터디-답변">시스터디 답변</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/03e5ad0a-512d-4f73-a915-eabdf52cd965/image.png" alt=""></p>
<p>기기에 저장된 <strong>인증 정보가 제대로 폐기되지 않았거나</strong>, 브라우저 창이 여러 개 열려 있어 로그아웃 이후에도 기존 인증 정보가 유효한 상태에서 구매 이력이 저장되면서 남편의 계정정보로 아내의 구매이력이 잘못 저장된 <strong>데이터 정합성 문제</strong>로 의심됩니다.</p>
<p>우선 <strong>DB에서는 SQL을 활용해 잘못 저장된 구매이력을 정확하게 조회하고, 대상 데이터를 특정한 뒤 아내의 계정정보로 수정하여 데이터 정합성을 복구</strong>하겠습니다.</p>
<p>이후 <strong>백엔드에서는 로그아웃 시 인증 방식에 따라 세션은 폐기하고, JWT는 Refresh Token을 무효화하며, 쿠키 방식은 인증 쿠키를 만료 처리</strong>하겠습니다. 또한 구매 이력 저장 시에는 <strong>클라이언트에서 전달한 사용자 정보를 신뢰하지 않고 서버에서 검증된 인증 정보를 기준으로 저장</strong>하여 데이터 정합성을 보장하겠습니다.</p>
<p>프론트엔드에서는 <strong>로그아웃 시 저장된 토큰과 사용자 상태, 캐시를 초기화</strong>하고, <strong>다중 탭에서도 로그아웃 상태가 동기화</strong>되도록 처리하겠습니다.</p>
<p>결과적으로 <strong>DB에서는 잘못된 데이터 복구, 백엔드에서는 인증 정보 무효화와 사용자 검증, 프론트엔드에서는 인증 상태 초기화와 다중 탭 동기화</strong>를 통해 데이터 정합성을 회복하고 동일한 문제가 재발하지 않도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[9]]></title>
            <link>https://velog.io/@cold_as_hell/9</link>
            <guid>https://velog.io/@cold_as_hell/9</guid>
            <pubDate>Wed, 26 Aug 2026 15:00:02 GMT</pubDate>
            <description><![CDATA[<p>아 에바임 글 수정하다 저장안해서 날렸어요. ㄹㅇ임 ㄹㅇㄹㄹㅇㄹㅇㄹㅇㄹㅇㄹㅇㄹㅇㄹㅇ</p>
<hr>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/3d2fe02b-8e86-4ce5-9b76-b940c52a492c/image.svg" alt=""></p>
<h1 id="웹크롤러란">웹크롤러란</h1>
<p>검색 엔진에서 널리 쓰는 기술로, 웹에 새로 올라오거나 갱신된 콘텐츠를 찾아내는 것이 
주된 목적이다. </p>
<p>여기서 콘텐츠는 웹 페이지일 수도 있고, 이미지나 비디오, 또는 PDF 파일일 수도 있다. 웹 크롤러는 몇 개 웹 페이지에서 시작하여 그 링크를 따라 나가면서 새로운 콘텐츠를 수집한다. </p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/1e2d5dc8-519c-4b18-9398-e346ae5024ed/image.png" alt=""></p>
<h2 id="웹크롤러의-별명">웹크롤러의 별명</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/c4dcc9ec-5898-4b19-8a6d-41b5b24c55e4/image.png" alt=""></p>
<p><strong>1. 로봇(robot)</strong>
사람이 일일이 웹 페이지를 방문하는 대신, 자동화된 프로그램이 사람을 대신해 반복적으로 페이지를 방문하고 수집하는 작업을 하기 때문</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/46e555de-d316-4325-b9eb-e0b38e3a3ee7/image.png" alt=""></p>
<p><strong>2. 스파이더 (spider)</strong>
웹(web, 거미줄)을 따라 이 페이지에서 저 페이지로 링크를 타고 옮겨 다니며 돌아다니는 모습이, 마치 거미가 거미줄 위를 기어다니는 것과 비슷하기 때문</p>
<hr>
<h2 id="웹크롤러의-사용예시">웹크롤러의 사용예시</h2>
<p><strong>검색 엔진 인덱싱(search engine indexing)</strong>
가장 흔하게 쓰이는 방식이다. 크롤러가 웹 페이지들을 모아서 검색할 때 쓸 색인(목록)을 만든다. 우리가 아는 구글 검색도 Googlebot이라는 크롤러가 이렇게 모은 정보를 바탕으로 작동한다.</p>
<p><strong>웹 아카이빙(web archiving)</strong>
나중에 다시 보거나 보관해두려고 웹 상의 자료들을 모아두는 것이다. 여러 나라의 국립 도서관들이 이런 식으로 크롤러를 돌려서 웹사이트들을 저장해두고 있고, 미국 국회 도서관이나 EU 웹 아카이브가 대표적인 예다.</p>
<p><strong>웹 마이닝(web mining)</strong>
웹이 워낙 빠르게 커지다 보니, 그 안에 쌓인 데이터에서 쓸만한 정보를 뽑아내는 작업도 활발해졌다. 예를 들어 금융 회사들은 크롤러로 기업들의 주주총회 자료나 연간 보고서를 긁어모아서, 그 회사가 어떤 방향으로 사업을 하고 있는지 파악하는 데 활용한다.</p>
<p><strong>웹 모니터링(web monitoring)</strong>
인터넷에서 누가 저작권이나 상표권을 침해하고 있지는 않은지 감시하는 용도로도 쓰인다. 디지마크(Digimarc)라는 회사는 크롤러를 이용해 불법 복제된 콘텐츠를 찾아내서 신고하는 일을 한다.</p>
<hr>
<h1 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h1>
<p><strong>&quot;문제를 이해하고 개략적인 설계안을 제시하자&quot;</strong></p>
<p>웹 크롤러의 기본 알고리즘은 간단하다.</p>
<ol>
<li>URL 집합이 입력으로 주어지면, 해당 URL들이 가리키는 모든 웹 페이지를
다운로드한다.</li>
<li>다운받은 웹 페이지에서 URL들을 추출한다.</li>
<li>추출된 URL들을 다운로드할 URL 목록에 추가하고 위의 과정을 처음부터
반복한다</li>
</ol>
<h2 id="그런데">그런데</h2>
<p>웹 크롤러가 정말로 이처럼 단순하게 동작할까? 그렇지 않다. 
웹 크롤러는 실제로는 절대 단순하지 않으며, 대규모로 확장 가능한 크롤러를 설계하는 건 매우 어려운 작업이라 인터뷰 시간 안에 완성하기 힘들다. 그래서 본격적인 설계에 앞서 질문을 통해 요구사항을 파악하고 설계 범위를 좁혀야 한다.</p>
<h2 id="질문예시">질문예시</h2>
<pre><code>지원자: 이 크롤러의 주된 용도는 무엇인가요? 검색 엔진 인덱스 생성용인가
요? 아니 면 데이터 마이닝 ? 아니면 그 외의 다른 용도가 있나요?
면접관: 검색 엔진 인덱싱에 쓰일 것입니다.
지원자: 매달 얼마나 많은 웹 페이지를 수집해야 하나요?
면접관: 10억 개(Ibillion)의 웹 페이지를 수집해야 합니다.
지원자: 새로 만들어진 웹 페이지나 수정된 웹 페이지도 고려해야 하나요?
면접관: 그렇습니다.
지원자: 수집한 웹 페이지는 저장해야 합니까?
면접관: 네. 5년간 저장해 두어야 합니다.
지원자: 중복된 콘텐츠는 어떻게 해야 하나요?
면접관: 중복된 콘텐츠를 갖는 페이지는 무시해도 됩니다.</code></pre><p><strong>이렇듯 면접관과의 대화를 통해 기능 요구사항을 확실히 해두면 좋다.</strong></p>
<h2 id="좋은-웹크롤러">좋은 웹크롤러</h2>
<p>면접관과 함께 크롤러의 기능 요구사항을 명확히 하면서, 동시에 좋은 크롤러가 갖춰야 할 속성(규모 확장성, 안정성, 예절, 확장성)에도 주의를 기울이자.</p>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>규모 확장성</strong><br>(scalability)</td>
<td>웹에는 수십억 개의 페이지가 존재하므로, 병행성(parallelism)을 활용해야 효과적으로 크롤링할 수 있다</td>
</tr>
<tr>
<td><strong>안정성</strong><br>(robustness)</td>
<td>잘못 작성된 HTML, 무응답 서버, 장애, 악성 링크 등 함정이 많은 웹 환경에서 비정상적 입력이나 상황에도 잘 대응해야 한다</td>
</tr>
<tr>
<td><strong>예절</strong><br>(politeness)</td>
<td>수집 대상 웹 사이트에 짧은 시간 동안 너무 많은 요청을 보내지 않아야 한다</td>
</tr>
<tr>
<td><strong>확장성</strong><br>(extensibility)</td>
<td>이미지 등 새로운 형태의 콘텐츠를 지원할 때 전체 시스템을 새로 설계할 필요 없이 쉽게 확장할 수 있어야 한다</td>
</tr>
</tbody></table>
<hr>
<h2 id="개략적-규모-추정">개략적 규모 추정</h2>
<p>문제 이해 및 범위 확정을 통해 개략적 설계안을 제시하고, 면접관의 동의를 구한다.
책에서의 개략적 설계안은 아래와 같다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>계산/가정</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>월간 다운로드 페이지 수</td>
<td>가정</td>
<td>10억(1,000,000,000) 개</td>
</tr>
<tr>
<td>평균 QPS</td>
<td>10억 / 30일 / 24시간 / 3600초</td>
<td>약 400 페이지/초</td>
</tr>
<tr>
<td>최대(Peak) QPS</td>
<td>평균 QPS × 2</td>
<td>800</td>
</tr>
<tr>
<td>웹 페이지 평균 크기</td>
<td>가정</td>
<td>500KB</td>
</tr>
<tr>
<td>월간 저장 용량</td>
<td>10억 페이지 × 500KB</td>
<td>500TB/월</td>
</tr>
<tr>
<td>5년간 저장 용량</td>
<td>500TB × 12개월 × 5년</td>
<td>30PB</td>
</tr>
</tbody></table>
<h1 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h1>
<p>요구사항이 분명해지면 개략적 설계를 진행하자. 
이를 위해서는 크롤러를 이루는 각 시스템 컴포넌트를 하나하나 살펴봐야 한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/65dc1e10-1e47-4ca3-8a74-da45d864785a/image.png" alt=""></p>
<h2 id="시작-url-집합">시작 URL 집합</h2>
<p>크롤링을 시작하는 출발점 URL들의 모음으로 크롤러가 가능한 한 많은 링크를 탐색할 수 있도록 하는 URL을 고르는 것이 바람직하다.</p>
<p>예시)</p>
<p>좋아하는 여자가 참여한 프로젝트 중, 알려진 프로젝트인 TypeSomething을 살펴보자.
바쁜와중에도 이런걸 만들다니, 정말 대견하다? 자랑스럽다? 암튼 짱이다.</p>
<p><a href="https://typesomething.vercel.app/">https://typesomething.vercel.app/</a> 에 있는 모든 노래와
<img src="https://velog.velcdn.com/images/cold_as_hell/post/1b73a4d6-575f-4c12-af8f-843413edbcc5/image.png" alt=""></p>
<p><a href="https://typesomething.vercel.app/leaderboard">https://typesomething.vercel.app/leaderboard</a>
유저랭킹,
<img src="https://velog.velcdn.com/images/cold_as_hell/post/d7f7f2ec-8171-4fc6-8667-d93825ac1f11/image.png" alt=""></p>
<p><a href="https://typesomething.vercel.app/my">https://typesomething.vercel.app/my</a>
마이페이지
업적, 최근 플레이한 곡 등을 크롤링한다고 해보자.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/a5c79c07-e387-4a96-937f-0f346d160a7d/image.png" alt=""></p>
<p>크롤러가 가능한 한 많은 링크를 탐색할 수 있도록, 루트도메인인 
<a href="https://typesomething.vercel.app">https://typesomething.vercel.app</a> 에서 크롤링을 시작하자.</p>
<h2 id="미수집-url-저장소-url-frontier">미수집 URL 저장소 (URL Frontier)</h2>
<p>다운로드할 URL들을 관리하는 컴포넌트로, FIFO 큐와 유사하게 작동한다.</p>
<h2 id="html-다운로더">HTML 다운로더</h2>
<p>인터넷에서 웹 페이지를 다운로드하는 컴포넌트이다.</p>
<h2 id="도메인-이름-변환기">도메인 이름 변환기</h2>
<p>URL을 IP 주소로 변환하는 역할을 한다.</p>
<h2 id="콘텐츠-파셔">콘텐츠 파셔</h2>
<p>다운로드된 웹 페이지를 파싱하고 검증하여 문제가 있는 페이지를 걸러낸다.</p>
<h2 id="중복-콘텐츠-확인">중복 콘텐츠 확인</h2>
<p>웹 페이지의 해시 값을 비교하여 데이터 중복을 줄인다.</p>
<h2 id="콘텐츠-저장소">콘텐츠 저장소</h2>
<p>HTML 문서를 보관하는 시스템으로, 디스크와 메모리를 함께 사용하여 효율성을 높인다.</p>
<h2 id="url-추출기">URL 추출기</h2>
<p>HTML 페이지를 파싱하여 링크들을 추출한다.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/199aeb84-071f-458e-9df5-c312e1bc51c9/image.png" alt=""></p>
<h2 id="url-필터">URL 필터</h2>
<p>특정 콘텐츠 타입, 오류 발생 URL, 접근 제외 목록에 포함된 URL 등을 크롤링 대상에서 배제한다.</p>
<h2 id="이미-방문한-url-추적">이미 방문한 URL 추적</h2>
<p>블룸 필터나 해시 테이블을 사용하여 같은 URL을 여러 번 처리하는 것을 방지하고 서버 부하를 줄인다.</p>
<h2 id="url-저장소">URL 저장소</h2>
<p>이미 방문한 URL을 보관하는 저장소이다.</p>
<h2 id="웹-크롤러-작업-흐름">웹 크롤러 작업 흐름</h2>
<p>위에서 잠시 확인했던 그림을 다시 보자</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/e9140ba8-8136-46db-9bd1-d0411d50d310/image.png" alt=""></p>
<ol>
<li><p>시작 URL들을 미수집 URL 저장소에 저장한다.</p>
</li>
<li><p>HTML 다운로더는 미수집 URL 저장소에서 URL 목록을 가져와 IP 주소를 변환 후 웹 페이지를 다운로드한다.</p>
</li>
<li><p>콘텐츠 파서는 다운된 HTML 페이지를 파싱하고 검증하며, 중복 콘텐츠인지 확인 후 저장한다.</p>
</li>
<li><p>중복이 아닌 콘텐츠는 URL 추출기로 전달되어 링크를 추출하고, URL 필터를 거친다.</p>
</li>
<li><p>필터링된 URL은 URL 저장소에서 이미 처리한 URL인지 확인 후, 저장소에 없는 URL은 URL 저장소와 미수집 URL 저장소에 추가한다.</p>
</li>
</ol>
<hr>
<h1 id="3단계-상세-설계">3단계 상세 설계</h1>
<p>개략적 설계안에 따른 컴포넌트와 구현 기술에 대해 생각해보자.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>핵심 내용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>DFS vs BFS</strong></td>
<td>웹은 그래프 구조(페이지=노드, 링크=엣지)로 볼 수 있음. DFS는 그래프가 매우 깊을 경우 부적합. BFS가 일반적으로 널리 쓰이는 크롤링 순회 방식이나, 표준 BFS도 두 가지 문제가 있음: ① 한 페이지에서 나온 링크는 대부분 같은 사이트로 연결되어 예절성 위반 우려, ② URL 우선순위를 고려하지 않음(모든 페이지를 동일한 우선순위로 처리)</td>
</tr>
<tr>
<td><strong>미수집 URL 저장소 (URL Frontier)</strong></td>
<td>위 BFS의 두 문제(예절성, 우선순위)를 해결하기 위한 컴포넌트. 다운로드할 URL을 관리하는 자료구조로, 예절성 관리용 큐와 우선순위 관리용 큐로 구성되며 FIFO 큐 형태</td>
</tr>
<tr>
<td><strong>HTML 다운로더</strong></td>
<td>실제로 URL 프론티어에서 URL을 가져와 웹 서버로부터 페이지를 다운로드하는 컴포넌트. DNS 확인(Resolver) 등이 관련됨</td>
</tr>
<tr>
<td><strong>안정성 확보 전략</strong></td>
<td>① 안정 해시(Consistent Hashing) - 다운로더 서버 부하 분산<br>② 크롤링 상태 저장 - 중단 시 재개 가능하도록 진행 상태 및 데이터 저장<br>③ 예외 처리 - 오류 발생해도 전체 시스템이 멈추지 않도록 처리<br>④ 데이터 검증 - 시스템 오류 방지를 위한 검증 로직</td>
</tr>
<tr>
<td><strong>확장성 확보 전략</strong></td>
<td>새로운 형태의 콘텐츠(이미지, PDF 등) 지원을 쉽게 하기 위해, 새 모듈을 플러그인 형태로 끼워 넣을 수 있도록 모듈화·유연한 설계 지향</td>
</tr>
<tr>
<td><strong>문제 있는 콘텐츠 감지 및 회피 전략</strong></td>
<td>① 중복 콘텐츠 - 해시/체크섬 비교로 중복 제거<br>② 거미 덫(Spider Trap) - 무한 루프를 유발하는 링크 구조 감지 및 회피(예: URL 길이 제한)<br>③ 데이터 노이즈 - 광고, 스크립트 코드, 스팸 URL 등 크롤링 가치 없는 콘텐츠 제외</td>
</tr>
</tbody></table>
<h2 id="dfs를-쓸-것인가-bfs를-쓸-것인가">DFS를 쓸 것인가, BFS를 쓸 것인가</h2>
<blockquote>
<p>DFS는 그래프가 너무 깊어질 위험 때문에 애초에 쓰지 않고, BFS는 채택하지만 예절성 문제(서버 과부하)와 우선순위 문제(페이지 동급 취급)라는 결함이 있다.</p>
</blockquote>
<p>웹 크롤러 설계에서 중요한 컴포넌트와 구현 기술을 심도 있게 살펴본다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ef2a38d1-f353-4d08-9c89-ec2ffab734bd/image.png" alt=""></p>
<p>웹은 <strong>유향 그래프(directed graph)</strong>나 마찬가지다. 페이지는 노드(node), 하이퍼링크(URL)는 에지(edge)로 볼 수 있고, 크롤링은 이 그래프를 에지를 따라 탐색하는 과정이다. 그래프 탐색에는 대표적으로 DFS와 BFS 두 알고리즘이 쓰인다.</p>
<p><strong>DFS(깊이 우선 탐색)</strong>는 좋은 선택이 아니다. 그래프 크기가 클 경우 얼마나 깊이 들어가게 될지 가늠하기 어렵기 때문이다.</p>
<p>그래서 웹 크롤러는 보통 <strong>BFS(너비 우선 탐색)</strong>를 사용한다. BFS는 FIFO(선입선출) 큐를 이용해, 한쪽으로 탐색할 URL을 넣고 다른 쪽으로 꺼내는 방식으로 동작한다. 하지만 이 방식에는 두 가지 문제가 있다.</p>
<h3 id="문제점-1--동일-서버-과부하">문제점 1 — 동일 서버 과부하</h3>
<p>한 페이지에서 나온 링크의 상당수는 같은 서버로 되돌아간다. 예를 들어 위키피디아(wikipedia.com) 페이지에서 추출한 링크 대부분은 같은 wikipedia.com 서버 내 다른 페이지를 가리키는 내부 링크다. 이런 링크들을 병렬로 한꺼번에 다운로드하면 그 서버에 요청이 몰려 과부하가 걸리게 되고, 이런 크롤러는 &#39;예의 없는(impolite)&#39; 크롤러로 여겨진다.</p>
<h3 id="문제점-2--우선순위-부재">문제점 2 — 우선순위 부재</h3>
<p>표준 BFS는 URL 간 우선순위를 두지 않아 모든 페이지를 동일하게 처리한다. 하지만 실제로는 페이지마다 품질과 중요도가 다르므로, 페이지 순위(page rank), 사용자 트래픽 양, 업데이트 빈도 등의 척도를 반영해 처리 우선순위를 구별하는 것이 합리적이다.</p>
<p>→ 이 두 가지 문제(예절성, 우선순위)를 해결하기 위해 등장하는 컴포넌트가 바로 <strong>미수집 URL 저장소(URL Frontier)</strong>다.</p>
<blockquote>
<p>해결방법부터는 2026을 강타할 최고의 스터디장이 진행할 것.</p>
</blockquote>
<h1 id="4단계">4단계</h1>
<hr>
<h1 id="-이번주-잼얘">+ 이번주 잼얘</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/bf07a7cf-2dac-46b5-b462-d854f28c091b/image.png" alt=""></p>
<p>프리미엄 대방어 코스를 위한 적금,
대방어 적금을 만들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2026년 최신] Python frozenset 잘 쓰는 법]]></title>
            <link>https://velog.io/@cold_as_hell/2026%EB%85%84-%EC%B5%9C%EC%8B%A0-Python-frozenset-%EC%9E%98-%EC%93%B0%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@cold_as_hell/2026%EB%85%84-%EC%B5%9C%EC%8B%A0-Python-frozenset-%EC%9E%98-%EC%93%B0%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Wed, 19 Aug 2026 12:23:09 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/468373">링크</a></p>
<blockquote>
<p><strong>파이프를 최대 k번 열었다 닫은 후, 감염될 수 있는 배양체 개수의 최댓값은?</strong></p>
</blockquote>
<ul>
<li>파이프는 처음에 모두 닫혀 있음. </li>
<li>파이프는 종류별로만 여닫을 수 있음. </li>
<li>한 종류를 열어 놓은 상태에서 다른 종류를 동시에 열수 없음.</li>
</ul>
<pre><code>따라서 A → B → C처럼 파이프 종류를 순서대로 선택하게 되며,
이때 최대 k번 선택하여 감염되는 배양체 수를 최대화해야 한다.
</code></pre><p>예시)
<img src="https://velog.velcdn.com/images/cold_as_hell/post/b9b92a2e-6cb3-40d7-b199-e2344d319ee2/image.png" alt=""></p>
<p>입출력 예제는 이러하다. </p>
<hr>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/b0f54e19-99de-4c6a-9d75-1f7124160b3b/image.png" alt=""></p>
<p>이동 경로</p>
<blockquote>
<p>1 → 5
1 → 2, 3
5 → 6, 7</p>
</blockquote>
<p>감염된 배양체</p>
<blockquote>
<p>1, 5, 2, 3, 6, 7 = 총 6개</p>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/5f116488-d0a0-4582-a54d-86d5a2b99ce8/image.png" alt=""></p>
<p>이동 경로</p>
<blockquote>
</blockquote>
<p>6 → 5
5 → 4
6 → 3
3 → 7
4 → 1
1 → 2</p>
<p>감염된 배양체</p>
<blockquote>
<p>6, 5, 4, 3, 7, 1, 2 = 총 7개</p>
</blockquote>
<blockquote>
<p>즉, 현재 감염 상태 → 파이프 종류 선택 → 새로 감염되는 배양체 계산 → 다음 선택의 반복</p>
</blockquote>
<hr>
<h1 id="풀이방법">풀이방법</h1>
<h2 id="frozenset">frozenset</h2>
<p>감염 상태를 표현하고, 중복 상태를 자동 제거</p>
<h2 id="bfs">BFS</h2>
<p>파이프 하나를 열었을 때 감염이 퍼지는 과정(상태 전이)을 계산</p>
<h2 id="완전탐색">완전탐색</h2>
<p>매 턴마다 가능한 모든 파이프 개방 경우(1,2,3)를 전부 전개</p>
<hr>
<h1 id="소스코드">소스코드</h1>
<blockquote>
<p><strong>파이프를 개방했을 때의 모든 경우의 수를 저장하고, 감염 집합을 frozenset으로 중복 제거해가며 상태 집합을 유지하며 그중 최대 크기를 답으로 기록한다.</strong></p>
</blockquote>
<pre><code class="language-python">from collections import deque

def solution(n, infection, edges, k):
    # 그래프 구성 (배양체 간 연결 및 파이프 종류)
    tree = [[] for _ in range(n + 1)]
    for u, v, pipe in edges:
        tree[u].append((v, pipe))
        tree[v].append((u, pipe))

    def spread(infected, target_pipe):
        &quot;&quot;&quot;target_pipe 열었을 때 감염 확장&quot;&quot;&quot;
        res = set(infected)
        q = deque(res)
        while q:
            curr = q.popleft()
            for nxt, pipe in tree[curr]:
                if pipe == target_pipe and nxt not in res:
                    res.add(nxt)
                    q.append(nxt)
        return frozenset(res)

    states = {frozenset([infection])}
    max_count = 1

    # 최대 k번 파이프 작동
    for _ in range(k):
        next_states = set()
        for state in states:
            max_count = max(max_count, len(state))
            for p_type in (1, 2, 3):
                next_states.add(spread(state, p_type))
        states = next_states

    # 최종 결과 최댓값 갱신
    for state in states:
        max_count = max(max_count, len(state))

    return max_count</code></pre>
<hr>
<h1 id="생각한-것">생각한 것</h1>
<p>윽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자원 경합과 연쇄장애]]></title>
            <link>https://velog.io/@cold_as_hell/%EC%9E%90%EC%9B%90-%EA%B2%BD%ED%95%A9%EA%B3%BC-%EC%97%B0%EC%87%84%EC%9E%A5%EC%95%A0</link>
            <guid>https://velog.io/@cold_as_hell/%EC%9E%90%EC%9B%90-%EA%B2%BD%ED%95%A9%EA%B3%BC-%EC%97%B0%EC%87%84%EC%9E%A5%EC%95%A0</guid>
            <pubDate>Wed, 19 Aug 2026 11:33:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/3a621f44-e96c-4a88-9777-ac889e5707de/image.png" alt=""></p>
<h1 id="자원경합">자원경합</h1>
<p>둘 이상의 프로세스, 스레드, 또는 작업이 한정된 동일 자원을 동시에 사용하려고 다투는 상태</p>
<hr>
<h2 id="공유자원">공유자원</h2>
<p>공유 자원(Shared Resource)이란 동시에 실행되는 프로세스들이 공동으로 접근하여 사용하는 자원을 의미한다. 전역 변수(예: &#39;잔액&#39;, &#39;총합&#39;)와 같은 데이터뿐만 아니라 파일, 입출력장치, 보조기억장치 등의 하드웨어 및 시스템 자원까지 포함한다.</p>
<p>여러 프로세스가 이러한 공유 자원에 동시에 접근해 읽고 쓸 때 접근 순서에 따라 결과가 달라지는 동시성 문제가 발생할 수 있다. 따라서 공유 자원에 접근하는 코드 영역 중 동시에 실행해서는 안 되는 영역을 임계 구역(Critical Section)으로 설정하고, 상호 배제(Mutual Exclusion)를 통해 보호해야 한다.</p>
<hr>
<h2 id="lock-경합">Lock 경합</h2>
<h3 id="lock의-메커니즘-비교">Lock의 메커니즘 비교</h3>
<table>
<thead>
<tr>
<th>Lock 종류</th>
<th>작동 방식</th>
<th>주요 특징 및 적합한 상황</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Mutex</strong></td>
<td>단일 스레드 전용 점유</td>
<td>소유권 개념이 존재하며, 락을 획득한 스레드만 해제 가능하다.</td>
</tr>
<tr>
<td><strong>Semaphore</strong></td>
<td>카운터 기반 N개 접근 허용</td>
<td>설정한 수만큼 동시 접근을 허용한다. Mutex는 Binary Semaphore의 특수한 형태다.</td>
</tr>
<tr>
<td><strong>Spinlock</strong></td>
<td>대기 중 지속적 CPU 점유 (Busy-wait)</td>
<td>Context Switching 비용보다 대기 시간이 짧을 때 유리하다. 대기가 길어지면 CPU 낭비가 극대화된다.</td>
</tr>
<tr>
<td><strong>Read-Write Lock</strong></td>
<td>읽기 공유 / 쓰기 단독 배타 접근</td>
<td>읽기 작업이 많고 쓰기 작업이 적은 워크로드에서 경합을 줄이는 데 효과적이다.</td>
</tr>
</tbody></table>
<hr>
<h3 id="lock-경합-심화-시-문제점">Lock 경합 심화 시 문제점</h3>
<blockquote>
<p>⚠️ <strong>락 경합이 심화될 때 나타나는 시스템 병목 현상</strong></p>
</blockquote>
<ul>
<li><strong>Context Switching 비용 증가</strong>: 락을 기다리는 스레드가 대기 상태로 전환되고, 락 해제 시 다시 스케줄링되는 과정에서 상당한 CPU 오버헤드가 발생한다.</li>
<li><strong>대기 큐 적체</strong>: 락을 기다리는 스레드나 트랜잭션이 큐에 쌓이면서 처리 지연이 누적되고 전체 시스템의 처리량(Throughput) 저하로 이어진다.</li>
</ul>
<hr>
<h3 id="lock-범위-granularity">Lock 범위 (Granularity)</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>보호 범위</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Coarse-grained Lock</strong></td>
<td>넓은 범위 (예: 테이블 전체)</td>
<td>구현이 단순하다.</td>
<td>동시성이 떨어지고 경합 가능성이 높다.</td>
</tr>
<tr>
<td><strong>Fine-grained Lock</strong></td>
<td>좁은 범위 (예: Row, Page)</td>
<td>경합이 줄어들고 동시성이 향상된다.</td>
<td>락 관리 오버헤드와 구현 복잡도가 증가한다.</td>
</tr>
</tbody></table>
<p>락을 얼마나 잘게 쪼개느냐가 동시성(Concurrency)과 오버헤드 사이의 트레이드오프를 결정하는 핵심 설계 지점이다.</p>
<hr>
<h3 id="deadlock과-starvation">Deadlock과 Starvation</h3>
<ul>
<li><strong>Deadlock (교착 상태)</strong>: 둘 이상의 트랜잭션이나 스레드가 서로가 점유한 자원을 상호 대기하며 무한정 멈춰 있는 상태다.</li>
<li><strong>Starvation (기아 상태)</strong>: 특정 트랜잭션이 우선순위나 타이밍 문제로 인해 지속적으로 락을 획득하지 못하고 뒤로 밀리는 현상이다.</li>
</ul>
<hr>
<h1 id="자원고갈">자원고갈</h1>
<p>시스템이 가진 메모리, CPU, 디스크 공간, 네트워크 대역폭 등의 제한된 자원이 과도하게 소모되거나 반환되지 않아 더 이상 정상적인 작업을 수행할 수 없는 상태다.</p>
<hr>
<h2 id="자원고갈의-주요-현상-및-원인">자원고갈의 주요 현상 및 원인</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>주요 현상</th>
<th>원인 및 메커니즘</th>
</tr>
</thead>
<tbody><tr>
<td><strong>메모리 고갈 (OOM)</strong></td>
<td>Heap 공간 부족으로 프로그램이 강제 종료된다.</td>
<td>자원 누수(Resource Leak), 무한 루프 또는 과도한 재귀 호출로 인해 메모리가 계속 점유된다.</td>
</tr>
<tr>
<td><strong>File Descriptor 고갈</strong></td>
<td>OS의 최대 파일 오픈 개수를 초과하여 새 파일이나 소켓을 열지 못한다.</td>
<td>사용이 끝난 파일 핸들이나 소켓을 시스템에 정상적으로 반환하지 않는 코드 결함이 원인이다.</td>
</tr>
<tr>
<td><strong>Port 고갈</strong></td>
<td>네트워크 연결에 필요한 Ephemeral Port가 모자라 통신 불능 상태가 된다.</td>
<td>대량의 연결 요청이 몰리거나(DoS/DDoS), Connection Pool 미사용 및 CLOSE_WAIT/TIME_WAIT 상태 적체로 발생한다.</td>
</tr>
</tbody></table>
<hr>
<h1 id="연쇄장애">연쇄장애</h1>
<p>상호 연결된 분산 시스템이나 구조에서 하나의 구성 요소가 고장 났을 때, 그 여파가 인접 서비스로 파급되어 전체 시스템이 차례대로 마비되는 현상이다.</p>
<hr>
<h2 id="핵심-요인">핵심 요인</h2>
<ul>
<li><strong>Head-of-Line Blocking</strong>: 앞선 느린 작업이 대기열을 점유하여 뒤따르는 작업 전체를 지연시킨다.</li>
<li><strong>Thread Pool Exhaustion</strong>: 느린 하위 의존성 하나로 인해 상위 서비스의 스레드 풀 전체가 대기 상태로 잠식된다.</li>
<li><strong>Retry Storm / Thundering Herd</strong>: 타임아웃 발생 후 다량의 재시도 요청이 한꺼번에 몰리며 하류 시스템의 부하를 폭발적으로 증폭시킨다.</li>
</ul>
<hr>
<h2 id="연쇄장애-전파-경로">연쇄장애 전파 경로</h2>
<pre><code class="language-text">지연 증가 ──&gt; 점유시간 증가 ──&gt; Thread Pool 고갈 ──&gt; 대기 적체 ──&gt; 타임아웃/재시도 ──&gt; 부하 증폭 ──&gt; 상위 서비스 전파
</code></pre>
<p>작은 지연이 자원 점유 시간을 늘리고, 이는 Thread Pool 고갈과 대기 적체로 이어진다. 결과적으로 타임아웃에 따른 <strong>재시도 폭주(Retry Storm)</strong> 현상이 발생하여 부하가 증폭되고 상위 서비스로 장애가 전파된다.</p>
<hr>
<h1 id="씨스터디-답변">씨스터디 답변</h1>
<blockquote>
<p>여러 독립적인 작업이 하나의 공유 자원이나 상태를 사용하고 있습니다. 특정 작업 하나에서 부하가 발생하자 관계없는 다른 작업들까지 함께 느려졌습니다. 왜 이런 현상이 발생할 수 있고, 영향 범위를 줄이기 위해 어떻게 설계하시겠습니까? 각자의 도메인에 맞춰 설명해주세요.</p>
</blockquote>
<blockquote>
<p>특정 작업의 부하가 공유 자원을 둘러싼 자원 경합과 자원 고갈을 유발하고, 이것이 대기열을 타고 전파되면서 시스템 전체의 연쇄 장애로 이어집니다.</p>
<p>// 저는 요즘 DB 유지보수에 관심이 있고, 그중에서도 엔진 레벨에서 자원과 락을 직접 제어할 수 있는 MS SQL Server 기반 환경에 마음이 갑니다.</p>
<p>MS SQL Server 환경에서 대량의 데이터를 일괄 처리할 때 행 단위 잠금이 테이블 잠금으로 자동 승격되면, 작업과 무관한 트랜잭션까지 대기 상태에 빠지며 선두 차단 현상이 발생합니다.</p>
</blockquote>
<p>선두 차단 현상은 앞선 요청이 잠금으로 인해 처리되지 못하면서, 그 뒤의 요청들까지 연쇄적으로 진행하지 못하고 대기하게 되는 현상입니다.
이 현상이 장기화되면 데이터베이스 연결 자원이 고갈되고 스레드 풀 고갈과 재시도 폭주로 이어져 결국 상위 서비스 전체에 연쇄 장애를 일으키게 됩니다.</p>
<blockquote>
</blockquote>
<p>이를 해결하기 위해 리소스 관리자로 배치 작업의 CPU와 메모리 사용량을 제한하여 시스템 영향을 최소화하고, 읽기 커밋된 스냅숏 격리(RCSI)를 적용해 읽기 작업이 잠금 대기 없이 tempdb에 저장된 행 버전 정보를 읽도록 하겠습니다.</p>
<p>DB를 잘 치는 것은 아니지만, 도메인 및 답변 중복을 간만에 없애기 위해 DBA 관점의 답변을 준비하였다.</p>
<h2 id="더-알아보기">더 알아보기</h2>
<p>대량의 데이터를 일괄 처리할 때 행 단위 잠금을 많이 잡으면 SQL 서버가 이를 테이블 잠금으로 자동 승격시킬 수 있습니다. 많은 행에 각각 잠금을 유지하는 것보다 하나의 넓은 범위로 관리하는 것이 잠금 관리 비용을 줄일 수 있기 때문입니다. 하지만 이 경우 개별 행이 아닌 테이블 전체에 가까운 범위가 잠기면서, 배치 작업과 관계없는 다른 트랜잭션(작업범위)까지 잠금을 기다리게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[8]]></title>
            <link>https://velog.io/@cold_as_hell/8</link>
            <guid>https://velog.io/@cold_as_hell/8</guid>
            <pubDate>Wed, 19 Aug 2026 03:26:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/87cc26ed-bf38-495a-9193-3c948a0a11dc/image.png" alt=""></p>
<p>씨스터디를 떠나기 전, 마지막으로(?) 8장을 살펴봤다. 
아디오스! 씨스터디 혹은 시스터디!!!</p>
<hr>
<p>프로그램에서 URL 가독성 등을 위해 URL 단축기 설계를 알아두면 좋다.</p>
<h1 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h1>
<p>이전 장과 마찬가지로, 시스템을 성공적으로 설계해 내려면 질문을 통해 모호함을 줄이고 요구사항을 알아내야 한다. 교재에서 질문 및 답변은 아래와 같다.</p>
<pre><code>지원자: URL 단축기가 어떻게 동작해야 하는지 예제를 보여주실 수 있을까요?
면접관: https://www.systeminterview.com/q=chatsystem&amp;c=loggedin&amp;v=v3&amp;l=long 입력으로 주어졌다고 해 봅시다. 이 서비스는 https://tinyurl.com/y7ke-ocwj 같은 단축 URL을 결과로 제공해야 합니다. 이 URL에 접속하면 원래 URL로 갈 수도 있어야 하죠.

지원자: 트래픽 규모는 어느 정도일까요?
면접관: 매일 1억(100million) 개의 단축 URL을 만들어 낼 수 있어야 합니다.

지원자: 단축 URL의 길이는 어느 정도여야 하나요?
면접관: 짧으면 짧을수록 좋습니다.

지원자: 단축 URL에 포함될 문자에 제한이 있습니까?
면접관: 단축 URL에는 숫자(0부터 9까지)와 영문자(a부터 z, A부터 Z까지)만 사용할 수 있습니다.

지원자: 단축된 URL을 시스템에서 지우거나 갱신할 수 있습니까?
면접관: 시스템을 단순화하기 위해 삭제나 갱신은 할 수 없다고 가정합시다.</code></pre><p>질문과 답변을 통해 아래와 같은 시스템을 정의하였다.</p>
<pre><code>1. URL 단축: 주어진 긴 URL을 훨씬 짧게 줄인다.
2. URL 리디렉션(redirection): 축약된 URL로 HTTP 요청이 오면 원래 URL로 안내
3. 높은 가용성과 규모 확장성, 그리고 장애 감내가 요구
</code></pre><p>정의된 시스템 정의를 토대로 URL 단축기 설계를 위한 개략적 추정을 하자.</p>
<pre><code>1. 트래픽 추정 (Traffic)
• 쓰기 연산 (URL 단축)
  - 매일 1억(100 million) 개의 단축 URL 생성
  - 초당 쓰기 연산(TPS): 100,000,000 / (24 × 3,600) = 1,160 TPS
• 읽기 연산 (URL 리디렉션)
  - 쓰기 연산과 읽기 연산의 비율을 1 : 10으로 가정
  - 초당 읽기 연산(TPS): 1,160 × 10 = 11,600 TPS

2. 저장 용량 추정 (Storage)
• 10년 기준 총 레코드 수
  - 1억 개/일 × 365일 × 10년 = 3,650억(365 billion) 개 레코드
• 필요 저장 용량
  - 축약 전 URL 평균 길이를 100 Byte로 가정
  - 10년간 저장 용량: 3,650억 개 × 100 Byte = 36.5 TB

3. 시스템 설계 시 시사점
• 읽기 연산이 초당 11,600회로 압도적이기 때문에 캐시(Cache) 레이어 도입 필수
• 10년간 축적될 36.5 TB 데이터와 높은 QPS 처리를 위해 NoSQL(Key-Value Store) 활용 및 DB 샤딩(Sharding) 고려</code></pre><hr>
<h1 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h1>
<p>이번절에서는 URL 단축키 설계를 위한  API 엔드포인트(endpoint), URL 리디렉션, 그리고 URL 단축 플로에 대해 살펴보겠다.</p>
<p>세 가지 요소를 가장 먼저 정의하는 이유는 아래와 같다.</p>
<table>
<thead>
<tr>
<th>정의할 요소</th>
<th>결정하는 내용</th>
<th>먼저 정의하는 이유</th>
</tr>
</thead>
<tbody><tr>
<td><strong>API 엔드포인트</strong></td>
<td>외부와의 소통 규약</td>
<td>클라이언트와 서버 간 통신 방식을 정해 전체 설계의 기준점 마련</td>
</tr>
<tr>
<td><strong>301 vs 302 리디렉션</strong></td>
<td>리디렉션 및 캐시 전략</td>
<td>캐시 동작과 서버 요청량에 영향을 주므로 시스템 아키텍처 방향 결정</td>
</tr>
<tr>
<td><strong>단축 플로우</strong></td>
<td>입력 → 단축 → 저장 → 조회 → 리디렉션</td>
<td>전체 처리 흐름을 확정해야 해시 충돌, DB 샤딩 등의 상세 설계 가능</td>
</tr>
</tbody></table>
<h2 id="api-엔드포인트">API 엔드포인트</h2>
<p>클라이언트는 서버가 제공하는 API 엔드포인트를 통해 서버와 통신한다. 
URL 단축기는 기본적으로 두 개의 엔드포인트를 필요로 한다.</p>
<p><strong>(1) URL 단축용 엔드포인트</strong></p>
<ul>
<li>새 단축 URL을 생성하고자 하는 클라이 언트는 이 엔드포인트에 단축할 URL을 인자로 실어서 POST 요청을 보내야 한다. </li>
</ul>
<pre><code>예시

POST /api/vl/data/shorten 
</code></pre><p>• 요청 본문(인자): { longUrl: string }
• 응답 본문(반환): { shortUrl: string }</p>
<p><strong>(2) URL 리디렉션용 엔드포인트</strong>
단축 URL에 대해서 HTTP 요청 이 오면 원래 URI로 보내주기 위한 용도의 엔드포인트. 
다음과 같은 형태를 띤다.</p>
<pre><code>예시

GET /api/vl/shortUrl</code></pre><p>• 응답 본문(반환): HTTP 리디렉션 목적지가 될 원래 URl</p>
<h2 id="url-리디렉션">URL 리디렉션</h2>
<p>사용자가 요청한 URL 대신 서버가 지정한 다른 URL로 클라이언트를 이동시키는 기능</p>
<p><strong>예시</strong>
<img src="https://velog.velcdn.com/images/cold_as_hell/post/0bae13ac-da1f-4619-991b-7154c86035e4/image.png" alt=""></p>
<p> <img src="https://velog.velcdn.com/images/cold_as_hell/post/3be799fe-4fd4-489d-b3c4-03062aee0a0c/image.png" alt=""></p>
<pre><code>① 사용자가 단축 URL 접속

https://tinyurl.com/qtj5o
          │
          │ GET
          ▼
② TinyURL 서버
          │
          │ 301 응답
          │
          │ Location:
          │ https://www.amazon.com/...
          ▼
③ 브라우저가 Location의 URL로 다시 요청
          │
          ▼
④ Amazon 페이지 접속
</code></pre><p>이 때, 301 응답과 302 응답의 차이점을 유의하여 확인하여야 한다.
둘다 리디렉션 응답이긴 하지만 차이가 있다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th><strong>301 Moved Permanently</strong></th>
<th><strong>302 Found</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>의미</strong></td>
<td>요청한 URL이 <strong>영구적으로</strong> 다른 URL로 이동했음을 의미</td>
<td>요청한 URL이 <strong>일시적으로</strong> 다른 URL에서 처리됨을 의미</td>
</tr>
<tr>
<td><strong>리디렉션 방식</strong></td>
<td><code>Location</code> 헤더에 원래 URL을 담아 전달</td>
<td><code>Location</code> 헤더에 원래 URL을 담아 전달</td>
</tr>
<tr>
<td><strong>캐시</strong></td>
<td>브라우저가 리디렉션 결과를 캐시할 수 있음</td>
<td>일반적으로 매번 단축 URL 서버에 요청한 후 리디렉션</td>
</tr>
<tr>
<td><strong>서버 부하</strong></td>
<td>낮출 수 있음. 캐시된 경우 단축 URL 서버를 거치지 않을 수 있음</td>
<td>상대적으로 높음. 요청마다 단축 URL 서버를 거침</td>
</tr>
<tr>
<td><strong>트래픽 분석</strong></td>
<td>이후 요청이 서버에 도달하지 않을 수 있어 추적에 불리</td>
<td>요청이 서버를 거치므로 클릭 수, 접속 위치 등 분석에 유리</td>
</tr>
<tr>
<td><strong>적합한 상황</strong></td>
<td>서버 부하 감소가 중요한 경우</td>
<td>트래픽 분석 및 사용자 행동 추적이 중요한 경우</td>
</tr>
</tbody></table>
<h2 id="url-단축-플로">URL 단축 플로</h2>
<p>긴 URL → 짧은 문자열로 바꾸려면  &quot;변환기&quot;가 필요하다. 
이 변환기 역할을 하는 게 바로 해시 함수(hash function) fx
<img src="https://velog.velcdn.com/images/cold_as_hell/post/6963de04-57e2-4ba9-a91c-603a821d7f09/image.png" alt=""></p>
<p>이 해시 함수는 다음 요구사항을 만족해야 한다.
• 입 력으로 주어지는 긴 URL이 다른 값이 면 해시 값도 달라야 한다.
• 계산된 해시 값은 원래 입력으로 주어졌던 긴 URL로 복원될 수 있어야한다.</p>
<p>이 해시 함수에 대한 상세 설계는 다음 절에서 살펴볼 것이다.</p>
<hr>
<h1 id="3단계-상세-설계">3단계 상세 설계</h1>
<p>URL을 단축하는 서비스를 구현하려면 고려해야 할 사항이 많다.
따라서 이를 구체적으로 구현하기 위한 상세 설계가 필요하며,
데이터 모델, 해시 함수, URL 단축 로직, 리디렉션 로직 등을 구체적으로 설계하고 고려해야 한다.</p>
<h2 id="데이터-모델">데이터 모델</h2>
<p>개략적 설계를 진행할 때는 모든 것을 해시 테이블에 두었었다. 이 접근법은 초기 전략으로는 괜찮지만 대규모 시스템에 쓰기에는 곤란한데, 메모리는 용량이 제한되어있고, 비싸기 때문이다. 때문에 대규모시스템에서는 해시테이블이 아닌 관계형 데이터베이스를 채택하는 것이 더 나은 선택일 수도 있다. </p>
<p>관계형 데이터베이스에서는 〈단축 URL, 원래 URL〉의 순서쌍을 저장한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ed1a4d0e-9f03-4579-8da1-ec129df42657/image.png" alt=""></p>
<h2 id="해시-함수">해시 함수</h2>
<p>앞에서도 잠시 언급했듯, 해시 함수(hash function)는 원래 URL을 단축 URI로 변환하는 데 쓰인다.</p>
<p><strong>“짧은 URL을 몇 글자로 만들면 충분할까?”</strong>
[0-9, a-z, A-Z]의 문자들로 구성된다. 따라서 사용할 수 있는 문자의 개수는 10+ 26+ 26 = 62개다.</p>
<p>누군가 무수히 많은 url을 단축하려고 한다고 가정해보자.
1글자로 압축하려고 한다면, 62개의 문자가 있으니 최대 62개로 압축할 수 있다.
2글자라면? 첫 번째 자리에도 62개를 넣을 수 있고, 두 번째 자리에도 62개를 넣을 수 있으니
62 × 62 = 62² = 3,844개 를 만들 수 있다.</p>
<p>이와 같은 논리를 우리에게도 적용해보자.
책에서는 이 시스템이 최대 3650억 개의 서로 다른 URL을 단축할 것으로 추정했으니
필요 이상으로 URL을 길게 만들지 않기 위해서 , 62ⁿ ≥ 3650억을 만족하는 가장 작은 n을 찾아야 한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/2590b01e-7442-41ec-af5f-a0324282ff6f/image.png" alt=""></p>
<p>이제 필요한 단축 URL의 길이를 정했으므로, 실제로 원래 URL을 이 길이의 단축 URL로 변환하는 방법을 살펴보자.
단축할 URL 글자를 구하면,  ‘해시 후 충돌 해소’ 과 ‘base-62 변환’으로 해시함수를 구현할 수 있다.</p>
<h3 id="해시-후-충돌-해소">해시 후 충돌 해소</h3>
<p>긴 URL을 줄이려면 원래 URL을 7글자의 문자열로 변환하는 해시 함수가 필요하다.</p>
<p>해시 함수란 어떤 데이터를 입력하면 일정한 규칙에 따라 다른 값으로 변환하는 함수를 뜻하며, 잘 알려진 해시 함수로는 CRC32, MD5, SHA-1 등이 있다.</p>
<p>그런데 해시 함수를 사용해 URL을 변환했을 때, 결과값이 우리가 원하는 7글자를 초과한다면 어떻게 해야 할까?</p>
<p>이 문제를 해결하는 첫 번째 방법은 계산된 해시값에서 처음 7글자만 사용하는 것이다. 하지만 이렇게 하면 해시값의 길이가 짧아지면서 서로 다른 URL이 같은 해시값을 갖는 <strong>충돌(collision)</strong>이 발생할 가능성이 높아진다.</p>
<p>충돌이 발생하면, 충돌이 해소될 때까지 사전에 정한 문자열을 원래 URL에 덧붙여 다시 해시하는 방법을 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/71bcc0be-ab98-437a-8429-c3683e73bb92/image.png" alt=""></p>
<p>→ ✅ 장점: 충돌 해결 가능
→ ❌ 단점: DB 조회가 필요해서 오버헤드 발생
→ 💡 개선: 블룸 필터를 사용해 DB 조회를 줄임</p>
<h3 id="base-62-변환">base-62 변환</h3>
<p>base-62 변환은 진법 변환(base conversion)이라고도 불린다.
이 기법은 URL 단축기를 구현할 때 흔히 사용되는 접근법 중 하나다.
이 기법은 수의 표현 방식이 다른 두 시스템이 같은 수를 공유하여야 하는 경우에 유용하다.</p>
<p><a href="https://en.wikipedia.org/wiki/Base62">62진법</a>
을 쓰는 이유는 hashValue에 사용할 수 있는 문자(character) 개수가 62개이기 때문이다. </p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/87c97b25-6828-4b4d-ae75-1269de2bda28/image.png" alt=""></p>
<h3 id="비교">비교</h3>
<table>
<thead>
<tr>
<th align="left">해시 후 충돌 해소 전략</th>
<th align="left">base-62 변환</th>
</tr>
</thead>
<tbody><tr>
<td align="left">단축 URL의 길이가 고정됨</td>
<td align="left">단축 URL의 길이가 가변적. ID 값이 커지면 같이 길어짐</td>
</tr>
<tr>
<td align="left">유일성이 보장되는 ID 생성기가 필요치 않음</td>
<td align="left">유일성 보장 ID 생성기가 필요</td>
</tr>
<tr>
<td align="left">충돌이 가능해서 해소 전략이 필요</td>
<td align="left">ID의 유일성이 보장된 후에야 적용 가능한 전략이라 충돌은 아예 불가능</td>
</tr>
<tr>
<td align="left">ID로부터 단축 URL을 계산하는 방식이 아니라서 다음에 쓸 수 있는 URL을 알아내는 것이 불가능</td>
<td align="left">ID가 1씩 증가하는 값이라고 가정하면 다음에 쓸 수 있는 단축 URL이 무엇인지 쉽게 알아낼 수 있어서 보안상 문제가 될 소지가 있음</td>
</tr>
</tbody></table>
<hr>
<h2 id="url-단축기-상세-설계">URL 단축기 상세 설계</h2>
<p>URL 단축기는 핵심 서비스이므로 구조가 단순해야 하고 절대 꺼지면 안 된다.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/aeea2fbf-d0c9-4798-a206-167173f18d52/image.png" alt=""></p>
<ol>
<li><strong>URL 조회</strong>: 입력받은 긴 URL이 DB에 있는지 확인한다.</li>
<li><strong>기존 URL 반환</strong>: 이미 존재한다면 저장되어 있던 단축 URL을 즉시 반환한다.</li>
<li><strong>ID 발급</strong>: DB에 없는 새 URL이면, DB Primary Key로 쓸 <strong>고유 ID</strong>를 생성한다.</li>
<li><strong>Base62 변환</strong>: 생성된 ID를 62진법으로 변환해 <strong>단축 URL</strong>을 만든다.</li>
<li><strong>저장 및 반환</strong>: [ID / 단축 URL / 원본 URL]을 DB에 저장하고, 단축 URL을 반환한다.</li>
</ol>
<hr>
<h2 id="url-리디렉션-상세-설계">URL 리디렉션 상세 설계</h2>
<p>교재의 URL 리디렉션(redirection) 메커니즘은 쓰기보다 읽기를 더 자주 하는 시스템이라,〈단축 URL, 원래 URL〉의 쌍을 캐시에 저장했을 때 성능이 높아진다.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/93dd2827-a657-4954-b9fc-861154bef038/image.png" alt=""></p>
<p>동작흐름은 아래와 같다.</p>
<ol>
<li>요청 전달: 사용자가 단축 URL을 클릭하면, 로드밸런서가 요청을 웹 서버로 전달한다.</li>
<li>캐시 확인 (Hit): 캐시에 URL이 있으면 즉시 클라이언트에게 반환한다.</li>
<li>DB 조회 (Miss): 캐시에 없으면 DB에서 조회한다. (없을 경우 오류 반환)</li>
<li>캐시 저장 및 반환: DB에서 찾은 URL을 캐시에 저장한 뒤 클라이언트에게 반환한다.</li>
</ol>
<hr>
<h1 id="4단계-마무리">4단계 마무리</h1>
<p>이외에도 이번 주제와 관련해 가용성과 확장성을 높이기 위한 추가 논점들을 제시할 수 있을 것이다.</p>
<table>
<thead>
<tr>
<th>주제</th>
<th>핵심 논점 및 적용 내용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>처리율 제한 장치</strong></td>
<td>• 대량 요청 폭주 시 서버 마비를 막기 위한 보안 및 방어 대책<br>• IP 주소나 사용자 기반 필터링 규칙 적용</td>
</tr>
<tr>
<td><strong>웹 서버 규모 확장</strong></td>
<td>• 웹 계층을 무상태(Stateless) 구조로 설계하여 자유로운 증설 및 삭제 가능</td>
</tr>
<tr>
<td><strong>데이터베이스 규모 확장</strong></td>
<td>• DB 다중화 및 샤딩(Sharding)을 통한 읽기/쓰기 성능과 저장 용량 확장</td>
</tr>
<tr>
<td><strong>데이터 분석 솔루션</strong></td>
<td>• 클릭 수, 클릭 시점, 유저 유입 경로 등 비즈니스 핵심 지표 수집 및 분석 모듈 통합</td>
</tr>
<tr>
<td><strong>시스템 기본 속성</strong></td>
<td>• 대규모 시스템 운영을 위한 가용성, 데이터 일관성, 안정성 확보 방안</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[7]]></title>
            <link>https://velog.io/@cold_as_hell/7-5cpgj3v1</link>
            <guid>https://velog.io/@cold_as_hell/7-5cpgj3v1</guid>
            <pubDate>Sun, 16 Aug 2026 14:21:55 GMT</pubDate>
            <description><![CDATA[<p>!youtube[ubR49QOloe4]</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/2ec0aef3-539e-49a5-9d6e-77f95742ff43/image.png" alt=""></p>
<BR>

<p>이번주는 천사와 함께 북스터디 세션 진행을 맡게 되었으며, 특히 7장을 도맡을 예정이다.</p>
<hr>
<p>백엔드 프로그래밍을 하며 관계형 DB를 사용할 때, 데이터의 고유값을 위한 유일키(unique key)를 어떻게 만들고 있는가?
관계형 DB의 auto-increment 기능 사용이라는 편리한 방식이 있지만 분산 환경에서는 한계가 있다.</p>
<p>예를 들어 사용자가 영상 URL이나 영상 파일을 서버에 업로드한다고 하자. 같은 URL이나 영상이 여러 번 업로드될 수 있으므로, 각 요청(작업)을 구분할 고유값이 필요하다. 다만 분산 시스템에서는 여러 서버가 동시에 ID를 생성하기 때문에 순차적인 ID가 필요하거나 생성 순서를 보장해야 하는 경우에는 단순한 자동 증가 방식으로 처리하기 어렵다. 서버 간 중복이나 순서 문제를 방지하기 위해 분산 환경에 적합한 ID 생성 전략이 필요하다.</p>
<p><strong>DB서버 1대를 쓸 경우</strong>: 사용자가 많아지면 DB 서버 1대로는 모든 요청을 감당하지 못해 과부하가 걸린다.</p>
<p><strong>DB서버 여러대를 쓸 경우</strong>:
DB 서버를 여러 대(A, B)로 늘리면, 각 서버가 중복된 ID를 만들어낼 수 있다.
이를 (중복된 ID 생성) 막으려고 여러 DB 서버끼리 통신하며 확인하는 절차에서 응답 속도가 심각하게 느려진다.</p>
<blockquote>
<p>그러니 유일성이 보장되는 아이디 생성에 대해 생각해보자.</p>
</blockquote>
<hr>
<h1 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h1>
<p>시스템 설계 면접 문제를 푸는 첫 단계는 면접관에게 질문을 던져 문제의 모호함을 명확히하고 설계 방향을 정하는 것이다. </p>
<pre><code>예시
지원자: ID는 어떤 특성을 갖나요?
면접관: ID는 유일해야 하고, 정렬 가능해야 합니다.
지원자: 새로운 레코드에 붙일 ID는 항상 1만큼 큰 값이 어야 하나요?
면접관: ID의 값은 시간이 흐름에 따라 커질 테지만 언제나 1씩 증가한다고 할수는 없습니다. 
다만 확실한 것은, 아침에 만든 ID보다는 저녁에 만든 ID가 큰 값을 갖는다는 점 입 니다.
지원자: ID는 숫자로만 구성되나요?
면접관: 그렇습니다.
지원자: 시스템 규모는 어느 정도입니까?
면접관: 초당 10,000 ID를 생성할 수 있어야 합니다.</code></pre><p>질문을 통해 도출된 면접관의 요구사항은 아래와 같다.</p>
<ul>
<li>ID는 반드시 유일해야 함.</li>
<li>ID는 숫자로만 구성되어야 함.</li>
<li>ID는 64비트로 표현 가능한 값이어야 함.</li>
<li>값이 정확히 1씩 증가하지는 않지만, 발급 시각(시간) 순서에 따라 정렬 가능해야 함 (= 늦게 생성된 ID일수록 큰 값을 가짐).</li>
<li>시스템 규모는 초당 10,000개의 ID를 생성할 수 있어야 함.</li>
</ul>
<p>이처럼 면접관에게 질문을 던짐으로써 분산 시스템을 위한 유일 ID 생성기 설계 문제의 요구사항을 파악하고, 모호한 부분을 명확히 할 수 있었다. 
이는 이후 개략적 수치 추정 및 상세 설계에 도움이 될 것이다.</p>
<hr>
<h1 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h1>
<p>분산 시스템에서 유일성이 보장되는 ID를 만드는 방법은 여러가지.</p>
<h2 id="다중-마스터-복제">다중 마스터 복제</h2>
<blockquote>
<p><strong>여러 DB 서버가 동시에 데이터를 생성할 수 있게 하고, 각 서버가 서로 겹치지 않는 ID를 생성하도록 하는 방식</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/bd1247a0-0ae3-44a1-9a6d-6fa1e7170345/image.png" alt=""></p>
<p>모든 DB 서버는 <code>auto_increment</code>  즉 ID 자동증가 기능을 사용하게 함.
아이디 자동증가를 할 때, 최초가 아닌 다음 서버 증가값은 +1이 아닌 서버 대수 만큼 증가시키는 방식.</p>
<p><strong>장점</strong>
여러 서버가 동시에 ID를 생성할 수 있으므로 단일 서버에서 ID를 생성하는 것보다 초당 생성 가능한 ID 수를 늘릴 수 있다.</p>
<p><strong>단점</strong>
(1) 여러 데이터 센터에 걸쳐 규모를 늘리기 어렵다.</p>
<ul>
<li>서버가 여러 데이터 센터에 흩어져 있으면, 모든 서버가 동일한 서버 수와 증가 규칙을 실시간으로 공유하기 어렵다.</li>
</ul>
<p>(2) ID의 유일성은 보장되지만, DB 서버별 처리 속도 차이로 인해 ID 값이 생성된 순서대로 정렬되지 않을 수 있다. </p>
<ul>
<li>즉, 시간 흐름에 맞추어 ID 값이 항상 커진다는 것까지는 보장할 수 없어 &quot;늦게 생성된 ID일수록 더 큰 값을 가져야 한다&quot;는 요구사항을 충족하기 어렵다.</li>
</ul>
<p>(3) 서버를 추가하거나 삭제할 때도 잘 동작하도록 만들기 어렵다.</p>
<ul>
<li>서비스를 운영하면서 서버 수를 유동적으로 조절하려고 하면 그때마다 모든 서버의 설정(증가폭, 시작값)을 동기화해서 바꿔야 하고, 그 과정에서 ID 충돌이나 일시적 장애가 발생하기 쉽다</li>
</ul>
<hr>
<h2 id="uuid-universally-unique-identifier">UUID( Universally Unique Identifier)</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/e7569e3e-3d6a-4ef3-97ef-2a4d3282559f/image.png" alt=""></p>
<blockquote>
<p><strong>네트워크 상에서 중복되지 않는 고유한 값을 만들기 위한 128비트 표준 규약이다.</strong></p>
</blockquote>
<pre><code># 형식 : 09c93e62-50b4-468d-bf8a-c07el040bfb2</code></pre><p><strong>장점</strong>
(1) UUID는 서버 간 조율 없이 독립적으로 생성 가능.</p>
<ul>
<li>서버 간 조율로 인한 문제 발생 하지 않음. (EX. DB 서버별 처리 속도 차이로 인한 문제</li>
</ul>
<p>(2) 중복 된 값이 생겨 충돌할 가능성이 지극히 낮음.</p>
<ul>
<li>위키피디아에 의하면, 중복 UUID가 1 개 생길 확률을 50%로 끌어 올리려면 초당 10억 개의 UUID를 100년 동안 계속해서 만들어야 한다.</li>
</ul>
<p><strong>단점</strong>
(1) ID가 128비트로 길다. </p>
<ul>
<li>문제의 요구사항은 64비트다.</li>
</ul>
<p>(2) ID를 시간순으로 정렬할 수 없다.</p>
<ul>
<li>문제 요구 사항에 맞지 않음.</li>
</ul>
<p>(3) ID에 숫자(numeric) 아닌 값이 포함될 수 있다.</p>
<ul>
<li>문제 요구 사항에 맞지 않음.</li>
</ul>
<hr>
<h2 id="티켓-서버-ticket-server">티켓 서버 (ticket server)</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/dbcbf678-69bd-4d7a-a336-60a1796bddf2/image.png" alt=""></p>
<p>이 접근법은 <code>auto_increment</code> 기능을 갖춘 데이터베이스 서버, 즉 <strong>티켓 서버(Ticket Server)</strong>를 중앙 집중형으로 하나만 두는 방식이다. 
<code>티켓 서버(Ticket Server)</code> 는 오직 <strong>&quot;유일한 ID(티켓)를 발급하는 일&quot;만 전담하며, 다른 애플리케이션 서버들은 ID가 필요할 때마다 이 티켓 서버에 요청을 보내 값을 받아온다.</strong></p>
<p><strong>장점</strong>
(1) 유일성이 보장되는, 오직 숫자로만 구성된 ID를 쉽게 만들 수 있다.</p>
<ul>
<li><code>auto_increment</code> 기능을 가진 서버 단 하나에서만 ID를 발급하므로, 여러 서버가 동시에 ID를 만들면서 생기는 충돌 위험이 원천적으로 없다.</li>
</ul>
<p>(2) 구현하기 쉽고, 중소 규모 애플리케이션에 적합하다.</p>
<ul>
<li>별도의 증가 규칙 조정이나 조율 로직 없이, DB 서버 한 대에 <code>auto_increment</code> 컬럼만 두면 되므로 개발·운영 난이도가 낮다.</li>
</ul>
<p><strong>단점</strong>
(1) 티켓 서버가 SPOF(Single Point of Failure)가 된다.</p>
<ul>
<li>이 서버에 장애가 발생하면 이 서버를 이용하는 모든 시스템이 영향을 받는다. 이를 피하려면 티켓 서버를 여러 대 준비해야 하는데, 그렇게 하면 데이터 동기화 같은 새로운 문제가 발생한다.</li>
</ul>
<p>※ SPOF(Single Point of Failure) : 시스템을 구성하는 여러 요소 중, 그 부분 하나가 고장 나면 시스템 전체(또는 서비스 전체)가 멈춰버리는 지점. 단일장애지점이라고도 불림.</p>
<hr>
<h2 id="트위터-스노플레이크twitter-snowflake-접근법">트위터 스노플레이크(twitter snowflake) 접근법</h2>
<p>전체 64비트 ID 구조를 의미 있는 여러 개의 절(section)로 쪼개어 처리하는 각개 격파 전략(Divide and Conquer)을 사용함.</p>
<h3 id="각개-격파-전략divide-and-conquer">각개 격파 전략(Divide and Conquer)</h3>
<p>생성해야 하는 ID의 구조를 여러 절(section)로 분할하는 것.
비트 분할 비율은 시스템의 요구사항과 비즈니스 특성에 따라 다르게 설계한다.
스노플레이크의 비트 분할 비율은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ac49ec2a-615f-43e9-8b3f-06fed89d4ed9/image.png" alt=""></p>
<p>스노우플레이크 ID(64비트)는 시간·위치·순번 충돌을 각각 방어하도록 비트를 쪼갠 구조이며, 필드 구성은 시스템마다 다르게 설계될 수 있다.</p>
<table>
<thead>
<tr>
<th>필드</th>
<th>비트</th>
<th>방어하는 충돌</th>
</tr>
</thead>
<tbody><tr>
<td>사인 비트</td>
<td>1</td>
<td>(예비, 미사용)</td>
</tr>
<tr>
<td>타임스탬프</td>
<td>41</td>
<td>시간 축 충돌 → 정렬 가능성 확보</td>
</tr>
<tr>
<td>데이터센터 ID</td>
<td>5</td>
<td>지역(데이터센터) 간 충돌</td>
</tr>
<tr>
<td>서버 ID</td>
<td>5</td>
<td>같은 데이터센터 내 서버 간 충돌</td>
</tr>
<tr>
<td>일련번호</td>
<td>12</td>
<td>같은 서버·같은 밀리초 내 충돌</td>
</tr>
</tbody></table>
<p><strong>장점</strong>
모든 요구사항을 만족하면서도 분산 환경에서 규모 확장이 가능</p>
<ul>
<li></li>
<li><p><em>단점*</em>
(1) 비트 수 제약으로 인한 확장 한계</p>
</li>
<li><p>데이터센터/서버/일련번호에 배분된 비트 수는 설계 시점에 고정되므로, 운영 중에 서버 수가 예상보다 많아지거나 밀리초당 발급량이 늘어나도 비트 구조를 유연하게 바꾸기 어렵다.</p>
</li>
</ul>
<p>(2) 데이터센터/서버 ID를 수동으로 관리해야 함</p>
<ul>
<li>각 서버에 고유 ID를 미리 배정하고 충돌 없이 관리해야 하므로 운영 부담이 있다.</li>
</ul>
<hr>
<h1 id="3단계-상세-설계">3단계 상세 설계</h1>
<p>유일성, 숫자로만 구성, 64비트 표현, 시간순 정렬 가능성, 초당 1만 개 이상 생성까지 앞서 도출된 요구사항을 전부 충족하는 유일한 방식,,,
트위터 스노플레이크 접근법으로 상세 설계를 해보자.</p>
<h2 id="타임스탬프">타임스탬프</h2>
<blockquote>
<p> <strong>타임스탬프는 유한한 비트 수로 표현되므로, 시간이 흐르면 언젠가 표현 가능한 값의 최댓값을 넘어서는 오버플로우가 발생할 수밖에 없다.</strong></p>
</blockquote>
<blockquote>
<p> <strong>동일 서버에서 같은 밀리초에 여러 ID가 생성되면 타임스탬프와 서버 정보가 중복되어 충돌이 발생한다.</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/13ec1524-4395-464e-ad83-a0b7b44a4cd5/image.png" alt=""></p>
<p>타임스탬프는 41비트로 표현되는데, 이 비트가 담을 수 있는 값에는 최댓값이 있다(2⁴¹-1 밀리초). 
이를 연 단위로 환산하면 대략 69년이다. 즉, 69년이 지나면 타임스탬프가 41비트로 담을 수 있는 최댓값을 넘어서 <strong>오버플로우(overflow)</strong>가 발생한다.</p>
<p>보통은 유닉스 시간(Unix Time) 기준에 따라 1970년 1월 1일을 타임스탬프의 시작점(기원 시각, epoch)으로 삼지만, 트위터는 이 69년을 최대한 오래 사용하기 위해 <strong>현재에 가까운 시점(2010년 11월 4일)</strong>을 기원 시각으로 잡았다. 
69년짜리 카운트다운의 시작을 늦춰서, 오버플로우가 발생하는 시점도 뒤로 미룬 것이다.</p>
<p>하지만 이는 근본적인 해결책이 아니므로, 결국 69년이 지나면 기원 시각을 다시 현재에 맞게 재설정하거나 ID 체계 자체를 새로운 방식으로 이전(migration)해야 한다.</p>
<p>타임스탬프에는 이런 장기적인 오버플로우 문제 외에도, 더 짧은 시간 단위에서 발생하는 문제가 하나 더 있다. 
<strong>밀리초 단위로만 기록되기 때문에, 동일 서버에서 같은 밀리초에 여러 ID가 생성되면 타임스탬프와 서버 정보가 중복되어 충돌이 발생한다는 점</strong>이다.</p>
<p>※ 컴퓨터가 처리할 수 있는 숫자나 메모리 공간의 최대 범위를 넘어서는 현상</p>
<hr>
<h2 id="일련번호">일련번호</h2>
<blockquote>
<p><strong>타임스탬프의 중복 문제를 해결할 수 있는 방법.</strong></p>
</blockquote>
<p>일련번호는 12비트라서 2¹² = 4096가지 값(0~4095)을 가질 수 있다.</p>
<p>각 서버는 자기만의 카운터로 이 값을 관리하며, 같은 밀리초 안에 ID를 여러 개 만들 때마다 1씩 증가시킨다(0, 1, 2...). 밀리초가 바뀌면 다시 0으로 초기화된다.</p>
<table>
<thead>
<tr>
<th>순서</th>
<th>타임스탬프(예시)</th>
<th>서버ID</th>
<th>일련번호</th>
</tr>
</thead>
<tbody><tr>
<td>1번째 ID</td>
<td>1586451091225 // x</td>
<td>서버A</td>
<td>0</td>
</tr>
<tr>
<td>2번째 ID</td>
<td>1586451091225 // x</td>
<td>서버A</td>
<td>1</td>
</tr>
<tr>
<td>3번째 ID</td>
<td>1586451091225 // x</td>
<td>서버A</td>
<td>2</td>
</tr>
<tr>
<td>다음 밀리초 첫 ID</td>
<td>1586451091226  // x + 1</td>
<td>서버A</td>
<td>0 (리셋)</td>
</tr>
</tbody></table>
<p>서로 다른 서버끼리 일련번호가 같아도 문제없다. 데이터센터ID·서버ID가 이미 서버를 구분해주기 때문이다.</p>
<table>
<thead>
<tr>
<th></th>
<th>타임스탬프</th>
<th>서버ID</th>
<th>일련번호</th>
</tr>
</thead>
<tbody><tr>
<td>서버 A의 ID</td>
<td>1586451091225</td>
<td>A</td>
<td>0</td>
</tr>
<tr>
<td>서버 B의 ID</td>
<td>1586451091225</td>
<td>B</td>
<td>0</td>
</tr>
</tbody></table>
<hr>
<h1 id="4단계-마무리">4단계 마무리</h1>
<p>이번 장에서는 유일성이 보장되는 ID 생성기 구현에 쓰일 수 있는 네 가지 전략을 살펴보았다.
이 중 우리가 최종적으로 선택한 방식은 스노우플레이크로, 모든 요구사항을 만족하면서도 분산 환경에서 규모 확장이 가능했기 때문이다.
설계를 마치고 시간이 남았다면, 면접관과 다음과 같은 주제를 추가로 논의해볼 수 있을 것 이다.</p>
<table>
<thead>
<tr>
<th>주제</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>시계 동기화(clock synchronization)</td>
<td>이번 설계는 모든 ID 생성 서버가 같은 시계를 쓴다고 가정했지만, 서버가 여러 코어나 여러 물리 장비에서 실행되면 이 가정이 깨질 수 있음. <strong>NTP</strong>(Network Time Protocol, 네트워크상의 여러 장비들의 시계를 하나의 기준 시각에 맞춰 동기화하는 표준 프로토콜)가 대표적 해결 수단</td>
</tr>
<tr>
<td>각 절(section)의 길이 최적화</td>
<td>동시성이 낮고 수명이 긴 애플리케이션이라면 일련번호 비트를 줄이고 타임스탬프 비트를 늘리는 등 상황에 맞게 조정 가능</td>
</tr>
<tr>
<td>고가용성(high availability)</td>
<td>ID 생성기는 <strong>필수 불가결</strong>(mission critical, 시스템 운영에 반드시 있어야 하며 장애 시 서비스 전체에 심각한 영향을 미치는 핵심 요소)한 컴포넌트이므로 매우 높은 가용성이 요구됨</td>
</tr>
</tbody></table>
<pre><code>※ **NTP(Network Time Protocol)**: 네트워크로 연결된 여러 컴퓨터(서버)들의 시계를 표준 기준 시각(주로 원자시계 기반)에 맞춰 자동으로 동기화해주는 프로토콜입니다. 서버마다 내부 시계가 조금씩 오차가 나는 문제(clock drift)를 주기적으로 보정해주는 역할을 한다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[시스템은 요청을 어떻게 처리하고, 그 결과를 어떻게 반환하는가?]]></title>
            <link>https://velog.io/@cold_as_hell/%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%80-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B3%A0-%EA%B7%B8-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@cold_as_hell/%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%80-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B3%A0-%EA%B7%B8-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Sun, 09 Aug 2026 07:28:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/7c7eff4c-47bb-42f5-9e26-4adf2434aa87/image.png" alt=""></p>
<p>이번주 면접 질문에 맞추어 시스템은 요청을 어떻게 처리하고, 그 결과를 어떻게 반환하는지 알아보고자 한다.</p>
<hr>
<h1 id="시스템의-요청과-응답-과정">시스템의 요청과 응답 과정</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/b515ddf2-2d7d-4057-b72d-01096d348f1f/image.png" alt=""></p>
<p>클라이언트가 HTTP 요청을 보내면 Web Server는 요청을 받는다. 
Web Server는 요청의 URL이나 개발자가 설정해 둔 라우팅 규칙 등을 기준으로 요청을 어떻게 처리할지 결정한다. 
보통 이미지, HTML, CSS와 같은 정적 콘텐츠에 대한 요청이라면 Web Server가 직접 파일을 찾아 응답하지만,
데이터 조회, 조건부 랜더링 등 Application Server의 비즈니스 로직이 필요한 요청이라면 해당 요청을 Application Server로 전달한다.</p>
<p>Application Server는 전달받은 요청에 따라 비즈니스 로직을 실행하고, 필요한 경우 데이터베이스나 외부 시스템에서 데이터를 조회하거나 처리한다. 이 과정에서 작업의 특성과 의존성에 따라 동기 또는 비동기 방식으로 작업을 처리할 수 있다.</p>
<p>처리가 완료되면 Application Server는 결과를 생성하고, Web Server를 통해 HTTP Response를 클라이언트에게 반환한다. 클라이언트는 응답에 포함된 HTTP 상태 코드와 데이터를 확인하여 요청의 성공 여부에 따라 화면을 갱신하거나 오류를 처리한다.</p>
<p>ㅡㅡ</p>
<p><strong>동기</strong> : 하나의 작업을 요청한 후 <strong>해당 작업이 완료되어 결과를 반환할 때까지</strong>현재의 제어 흐름이 <strong>다음 작업으로 진행되지 않는 처리 방식</strong>
<strong>비동기</strong> : 작업을 요청한 후 *<em>해당 작업의 완료 및 결과 반환을 기다리지 않고 *</em>제어 흐름을 다음 작업으로 넘기는 처리 방식</p>
<hr>
<h1 id="응답-코드-상태-코드">응답 코드 (상태 코드)</h1>
<p>Web Server는 클라이언트에게 응답을 반환할 때 상태 코드와 함께 이를 반환한다.
메서드가 서버에게 무엇을 해야 하는지 말해주는 것처럼, 상태 코드는 클라이언트에게 무엇이 일어났는지 말해준다. 상태 코드는 응답의 시작줄에 위치한다. </p>
<pre><code>‘HTTP/1.0 200 OK’ // 상태코드 : 200</code></pre><h2 id="코드값에-따른-응답코드">코드값에 따른 응답코드</h2>
<p>응답 코드들은 세 자리 숫자로 된 그들의 코드값을 기준으로 묶인다. 200에서 299까지의 상태 코드는 성공을 나타낸다. 
300에서 399까지의 코드는 리소스가 옮겨졌음. 즉, 리다이렉션 또는 요청을 완료하기 위해 추가적인 동작이 필요함을 의미한다.
400에서 499까지의 코드는 클라이언트의 요청을 처리할 수 없는 상황에서 반환되는 클라이언트 오류를 의미한다.
500에서 599까지의 코드는 서버에서 뭔가 실패했음을 의미한다.</p>
<table>
<thead>
<tr>
<th>전체 범위</th>
<th>정의된 범위</th>
<th>분류</th>
</tr>
</thead>
<tbody><tr>
<td>100-199</td>
<td>100-101</td>
<td>정보</td>
</tr>
<tr>
<td>200-299</td>
<td>200-206</td>
<td>성공</td>
</tr>
<tr>
<td>300-399</td>
<td>300-305</td>
<td>리다이렉션</td>
</tr>
<tr>
<td>400-499</td>
<td>400-415</td>
<td>클라이언트 에러</td>
</tr>
<tr>
<td>500-599</td>
<td>500-505</td>
<td>서버 에러</td>
</tr>
</tbody></table>
<hr>
<h1 id="씨스터디-답변">씨스터디 답변</h1>
<h2 id="1-동기와-비동기의-차이는-무엇인가요">1. 동기와 비동기의 차이는 무엇인가요?</h2>
<blockquote>
<p><strong>동기</strong> : 하나의 작업을 요청한 후 <strong>해당 작업이 완료되어 결과를 반환할 때까지</strong>현재의 제어 흐름이 <strong>다음 작업으로 진행되지 않는 처리 방식</strong>
<strong>비동기</strong> : 작업을 요청한 후 *<em>해당 작업의 완료 및 결과 반환을 기다리지 않고 *</em>제어 흐름을 다음 작업으로 넘기는 처리 방식</p>
</blockquote>
<p>즉 동기와 비동기 방식은 작업의 완료를 기준으로 프로그램의 제어 흐름을 어떻게 관리하느냐의 차이입니다.
동기와 비동기 방식에 대해 이해하는 것은 작업 간 의존성을 관리하고, 불필요한 대기 시간을 줄여 프로그램의 응답성과 처리 효율을 높이는 데 도움이 되기 때문에 중요한데,</p>
<p>개발하고자 하는 프로그램에 순차적으로 이루어져야하는 것이 있다면 동기 방식을 사용하고, // ex) 쳇봇시스템. 사용자가 한 체팅에 질문을 여러개 했다면 답변은 차례대로.</p>
<p>한꺼번에 실행되어도 되는 것이 있다면 비동기 방식을 사용하면 // ex) 사용자 화면에서 버튼이 빛나는 애니메이션과 클릭시 애니메이션을 동시에 확인하여 시각적으로 사로잡기.</p>
<p>좋을 것입니다.</p>
<hr>
<h2 id="2-도메인에서-받아볼-수-있는-응답코드에-대해-설명해주세요">2. 도메인에서 받아볼 수 있는 응답코드에 대해 설명해주세요</h2>
<p>웹에는 <strong>HTTP 응답 상태 코드(Status Code)</strong>가 있습니다. HTTP 응답 상태 코드는 클라이언트가 서버에 HTTP 요청을 보냈을 때, 서버가 그 요청을 어떻게 처리했는지를 나타내기 위해 응답에 포함하여 반환하는 3자리 숫자 코드를 뜻합니다.</p>
<p>저는 최근 <a href="https://github.com/Profitah/mylove_ms">스터디 발표용 서비스</a>를 만들면서 실제 결제 기능을 도입하려 했습니다. 하지만 요청 형식은 올바르지만 논리적으로 처리가 불가능한 경우 반환되는 422 에러가 발생하면서 결제 기능 도입에 실패했습니다.</p>
<p>422 상태코드를 토대로 원인을 탐색해보니, 제가 도입하려던 Apple Pay는 애플이 만든 브라우저인 사파리에서만 사용 가능한 기능이라 다른 브라우저에서는 정상적인 호출이 불가능하여 우회적인 방식을 사용하거나 다른 브라우저에서는 Apple Pay 기능을 사용하지 못하게 처리해야 한다는 것을 알게 되었습니다.</p>
<p>마감 스프린트 기간이 얼마 남지 않은 상황에서, 이를 구현하는 것은 어려운 일이였고,
저는 실제 결제 기능을 제외하기로 결정했습니다.</p>
<p>해당 서비스의 mvp 기능은 유저간 체팅을 주고 받는 것이였으며,
결제 기능은 부가적인 것이였기에 스프린트 기간에 맞추어 기능을 개발하기 위한 최선의 선택이였습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6]]></title>
            <link>https://velog.io/@cold_as_hell/6</link>
            <guid>https://velog.io/@cold_as_hell/6</guid>
            <pubDate>Fri, 07 Aug 2026 03:56:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/0d243a56-5e15-4dca-8023-9767c5968d79/image.png" alt=""></p>
<p>6장 키-값 저장소 설계 : 면접관</p>
<hr>
<h1 id="키-값-저장소key-value-store">키-값 저장소(key-value store)</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ed6512aa-ccf8-47d3-a469-b66c1adea456/image.png" alt=""></p>
<p>키-값 데이터베이스라고도 불리는 비 관계형 데이터베이스.
키와 값 사이의 이런 연결 관계를 “키-값” 쌍 (pair) 이라고 지칭한다.
키-값 저장소의 키는 중복되거나 변경할 수 없는 고유값이며,</p>
<p>키는 일반 텍스트일 수도 있고 해시 값일 수도 있다. 
값은 문자열일 수도 있고 리스트(list)일 수도 있고 객체(object)
일 수도 있다.</p>
<h2 id="해시키가-짧으면-어떤-점이-좋나요">해시키가 짧으면 어떤 점이 좋나요?</h2>
<p>성능상의 이유로, 키는 짧을수록 좋다.
키가 길 때보다 짧을 때 좋은 점은 아래와 같다.</p>
<p>(1) 저장시 메모리 사용량을 절감할 수 있음.
(2)더 빠르게 주소 탐색이 가능해짐.
(3)네트워크 대역 폭 및 I/O 비용이 감소</p>
<h2 id="해시키가-길어-성능이-저하된다면">해시키가 길어 성능이 저하된다면?</h2>
<p>지나치게 길이가 긴 키를 사용하게 될 경우에는 메모리 사용량이 늘어나고 인덱싱 및 데이터 탐색 속도가 떨어져 성능이 저하된다.</p>
<p>이때 해시 키(Hash Key)를 사용하면 아무리 긴 문자열 키라도 일정한 고정 길이로 규격화할 수 있어,
키의 상한선 길이를 제한하고 주소 탐색 연산 효율을 높일 수 있다.</p>
<pre><code>• 일반 텍스트 키 : &quot;user:1004:preference:notification:last_logged_in_at&quot; (48자)
• 해시 키       : &quot;253DDEC4&quot; (8자 고정)
</code></pre><hr>
<h2 id="예시">예시</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/73063449-5980-4a6e-9b6b-54c6173ee73f/image.png" alt=""></p>
<p>(1) 아마존 다이나모
(2) memcached
(3) 레디스</p>
<hr>
<h1 id="그러나">그러나</h1>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/60b6fd88-8912-436a-a241-179ef52ced27/image.png" alt=""></p>
<p>성능 극대화를 위해 해시키를 짧게 만들면, 메모리사용률과 처리 성능 개선을 이룰 수 있지만,
해시 충돌로 인한  데이터 일관성 훼손의 위험이 있다.</p>
<p>데이터 쓰기와 읽기(데이터 정합성) 그리고 메모리 사용량 사이에 어떤 균형을 찾고, 
데이터의 일관성과 가용성 사이에서 타협적 결정을 내린 설계 방식에 대해 고민해보자.</p>
<hr>
<h2 id="단일-서버-키-값-저장소">단일 서버 키-값 저장소</h2>
<p>키-값 쌍 전부를 메모리에 해시 테이블로 저장하는 방식</p>
<ul>
<li><p>장점 : 빠른속도를 보장함.</p>
</li>
<li><p>단점 : 모든 데이터를 메모리 안에 두는 것이 불가능할 수도 있다.</p>
</li>
<li><p>해결책 </p>
<pre><code>       (1) 데이터 압축(compression) 
        (2) 자주 쓰이는 데이터만 메모리에 두고 나머지는 디스크에 저장</code></pre></li>
</ul>
<blockquote>
<p><strong>속도는 빠르지만 메모리 용량에 한계가 있기 때문에, 압축하거나 자주 사용하는 데이터만 메모리에 저장하는 방식으로 보완한다</strong></p>
</blockquote>
<hr>
<h2 id="분산-키-값-저장소">분산 키-값 저장소</h2>
<p>해시 함수(Hash Function)를 이용해 데이터를 어떤 서버에 저장할지 결정하는 방식.
분산 해시 테이블이라고도 불린다.</p>
<ul>
<li>장점: </li>
<li>단점:</li>
</ul>
<blockquote>
</blockquote>
<hr>
<h1 id="더-알아보기----cap-정리">더 알아보기 -  CAP 정리</h1>
<p>분산 키-값 저장소를 사용해 분산시스템을 설계할 때는 CAP정리를 이해하고 있어야 한다.</p>
<p>CAP정리는 일관성(consistency), 가용성(availability), 파티션 감내 (partition tolerance)라는 세 가지 요구사항을 동시에 만족하는 분산 시스템을설계하는 것은 불가능하다는 정리로 이들 가운데 어던 두가지를 충족하려면 나머지 하나는 반드시 희생되어야 한다.</p>
<pre><code>※ 데이터 일관성： 분산 시스템에 접속하는 모든 클라이언트는 어떤 노드에 접속했느냐에 관계없이 언제나 같은 데이터를 보게 되어야 한다.
※ 분산 시스템에 접속하는 클라이언트는 일부 노드에 장애가 발생하더라도 항상 응답을 받을 수 있어야 한다.
※ 파티션은 두 노드 사이에 통신 장애가 발생하였음을 의미한다. 
파티션 감내는 네트워크에 파티션이 생기더라도 시스템은 계속 동작하여야 한다는 것을 뜻한다.
</code></pre><h2 id="cp-시스템">CP 시스템</h2>
<p>일관성과 파티션 감내를 지원하는 키-값 저장소. 
가용성을 희생한다.</p>
<h2 id="ap-시스템">AP 시스템</h2>
<p>가용성과 파티션 감내를 지원하는 키-값 저장소. 
데이터 일관성을 희생한다.</p>
<h2 id="ca-시스템">CA 시스템</h2>
<p>일관성과 가용성을 지원하는 키-값 저장소. 파티션 감내는 지원하지 않는다. 
그러나, 노드 사이에 통신장애는 완전히 회피할 수 없기에 분산시스템은 파티션 문제를 완전히 회피할 수 없으며
설계시에는 반드시 파티션 문제를 감내할 수 있도록 설계되어야 한다.
따라서 실세계에서 CA 시스템은 존재하기 어려운 개념이라고 이해하는 것이 좋다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/b9811761-f403-4508-ac7e-e037968c9672/image.png" alt=""></p>
<h3 id="분산시스템의-이상적인-노드상태">분산시스템의 이상적인 노드상태</h3>
<p>n1에 기록된 데이터가 자동적으로 n2와 n3에 복제되며 데이터 일관성과 가용성을 유지한다.
<strong>이상적인 환경에서는</strong> 네트워크 파티션이 발생하는 상황도 절대로 일어나지 않을 것이다.
<img src="https://velog.velcdn.com/images/cold_as_hell/post/6a030a60-95e2-48d7-8254-24cac438f82b/image.png" alt=""></p>
<h3 id="실세계의-분산-시스템">실세계의 분산 시스템</h3>
<p>분산 시스템은 파티션 문제를 피할 수 없다. 
그리고 파티션 문제가 발생하면 우리는 일관성과 가용성 사이에서 하나를 선택해야 한다. </p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/def155bf-3dad-4d7e-940a-b770848e15db/image.png" alt=""></p>
<p>만약 n3에 장애가 발생한다면 n1, n2노드와 통신할 수 없다.
따라서 클라이언트가 n1 , n2에 기록한 데이터는 n3에 전달되지 않는다.
n3에 기록되었으나 아직 n1 , n2로 전달되지 않은 데이터가 있다면 n1과 n2는 오래된 사본을 갖고 있을 것이다.</p>
<h3 id="가용성-대신-일관성을-선택한다면">가용성 대신 일관성을 선택한다면</h3>
<p>세 서버 사이에 생길 수 있는 데이터 불일치 문제를 피하기 위해 nl과 n2에 대해 쓰기 연산을 중단시켜야 하는
데, 
그러면 시스템을 사용할 수 없게 된다</p>
<h3 id="일관성-대신-가용성을-선택한다면">일관성 대신 가용성을 선택한다면</h3>
<p>시스템(클라이언트 서비스)은 사용 가능하나, 노드 간 네트워크가 복구될 때까지 데이터 불일치 현상이 지속된다.</p>
<h1 id="시스템-컴포넌트">시스템 컴포넌트</h1>
<p>키-값 저장소 구현에 사용될 핵심 컴포넌트들 및 기술들을 살펴보자.</p>
<h2 id="데이터-파티션">데이터 파티션</h2>
<p>대규모 애플리케 이션의 경우 전체 데이터를 한 대 서버에 욱여넣는 것은 불가능하다. 
가장 단순한 해결책은 데이터를 작은 파티션들로 분할한 다음 여러 대 서버에 저장하는 것이다. </p>
<p>이때 고려해야할 점은</p>
<blockquote>
<p><strong>1) 데이터를 여러 서버에 고르게 분산할 수 있는가
2) 노드가 추가되거나 삭제될 때 데이터의 이동을 최소화 할 수 있는가</strong></p>
</blockquote>
<p>해시함수로 샤딩을 할 경우,
이 문제를 해결하기 위해서 &#39; <code>안정 해시(consistent hash)</code>를 쓰면 도움이 된다. </p>
<h3 id="안정해시">안정해시</h3>
<p>키를 해시 링(hash ring) 위에 올린 뒤, 시계 방향으로 가장 가까운 서버에 저장하는 방식.
이때 실제 서버 수가 적으면 데이터가 특정 서버로 쏠릴 수 있으므로, 부하를 균등하게 분산하기 위해 다수의 가상 노드를 해시 링에 올린다</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ebc64a6e-1126-47d3-af60-67114462fe1e/image.png" alt=""></p>
<p><strong>[주요 특징]</strong></p>
<table>
<thead>
<tr>
<th>주요 특징</th>
<th>이점 (좋은 점)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>규모 확장 자동화</strong> (Automatic Scaling)</td>
<td>• 트래픽 변동에 따라 서버가 자동으로 추가/삭제되어 <strong>안정적 운영</strong> 가능<br>• 사용량이 적은 시간에는 서버를 줄여 <strong>비용 절감</strong></td>
</tr>
<tr>
<td><strong>다양성</strong> (Heterogeneity)</td>
<td>• 서버 스펙이 달라도 성능에 맞게 <strong>가상 노드 수를 조정</strong>해 부하 분배<br>• 고성능 서버에 더 많은 가상 노드를 배치해 <strong>자원 활용 극대화</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="데이터-다중화">데이터 다중화</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/998b5d64-2e4d-42be-931c-4082b6af14bf/image.png" alt=""></p>
<p>높은 가용성과 안정성 확보를 위해 데이터 사본을 N개 서버에 보관한다.
<strong>그런데, 이때 선택된 N개의 가상노드가 매핑되는 실제 물리 서버의 개수가 N개보다 작다면(즉, 서로 다른 가상노드가 같은 물리 서버에 속해 있다면), 서버에 문제가 생겼을 때 예상보다 적은 사본이 남아있을 가능성이 있다.</strong></p>
<p>즉, 의도했던 수준의 내결함성을 확보하지 못하는 것이다. 
<strong>뿐만 아니라, 같은 데이터 센터에 속한 노드는 정전, 네트워크 이슈, 자연재해 등의 문제를 동시에 겪을 가능성이 있다.</strong> <strong>따라서 데이터의 사본은 다른 센터의 서버에 보관하고, 센터들은 고속 네트워크로 연결한다.</strong></p>
<p>※ 내결함성 : 일부 서버에 장애가 발생해도 시스템이 정상적으로 서비스를 유지할 수 있는 능력</p>
<h2 id="데이터-일관성">데이터 일관성</h2>
<p>여러 서버에 데이터를 복제(다중화)해두면 가용성은 올라가지만, 새로운 문제가 생긴다.</p>
<blockquote>
<p>&quot;여러 개의 사본 중에, 누가 최신 데이터인지 어떻게 알지?&quot;</p>
</blockquote>
<p>이 문제를 해결하는 방법이 바로 정족수 합의(Quorum Consensus) 프로토콜이다.</p>
<p>※  정족수 : 분산 시스템에서 읽기/쓰기 연산이 &quot;성공했다&quot;고 인정받으려면 일정 개수 이상의 서버가 응답해야 한다.</p>
<h3 id="정족수-합의-프로토콜-변수">정족수 합의 프로토콜 변수</h3>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>N</strong></td>
<td>데이터의 사본(복제본) 개수</td>
</tr>
<tr>
<td><strong>W</strong></td>
<td>쓰기 연산 정족수 — 쓰기가 성공했다고 인정받기 위해 필요한 최소 서버 응답 수</td>
</tr>
<tr>
<td><strong>R</strong></td>
<td>읽기 연산 정족수 — 읽기가 성공했다고 인정받기 위해 필요한 최소 서버 응답 수</td>
</tr>
</tbody></table>
<p>클라이언트가 데이터를 쓸 때, N개의 서버 중 적어도 W개로부터 &quot;쓰기 성공&quot; 응답을 받아야 그 쓰기 연산은 성공한 것으로 간주된다.
클라이언트가 데이터를 읽을 때, N개의 서버 중 적어도 R개로부터 응답을 받아야 그 읽기 연산은 성공한 것으로 간주된다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/65826924-4d0e-4498-b182-25e4eb5050b5/image.png" alt=""></p>
<p>예를들어 N = 3 일때,
W=1 은 쓰기 연산이 성공했다고 판단하기 위해 클라이언트와 노드 사이에 위치한 중재자(coordinator)는 최소 한 대 서버로부터 쓰기 성공 응답을 받아야 한다는 뜻이다.
si으로부터 성공 응답을 받았다면 sO, s2로부터의 응답은 기다릴 필요가 없다. </p>
<p>이처럼 N, W, R의 값을 정하는 것은 응답 지연(latency)과 데이터 일관성(consistency) 사이의 타협점을 찾는 전형적인 과정이다. W=1 또는 R=1인 구성의 경우, 중재자는 한 대 서버로부터의 응답만 받으면 되므로 응답 속도는 빠를 것이다. 반면 W나 R의 값이 1보다 큰 경우에는 시스템이 보여주는 데이터 일관성의 수준은 향상되겠지만, 중재자는 가장 느린 서버로부터의 응답까지 기다려야 하므로 전체 응답 속도는 느려질 것이다.</p>
<p>특히 W + R &gt; N인 경우에는 강한 일관성(strong consistency)이 보장된다. 쓰기 연산에 참여한 서버 집합과 읽기 연산에 참여한 서버 집합이 겹치지 않을 수 없기 때문에, 일관성을 보증할 최신 데이터를 가진 노드가 최소 하나는 겹치게 되는 것이다.</p>
<h3 id="일관성-모델">일관성 모델</h3>
<p>데이터 일관성을 위한 키-값 저장소를 설계할 때 고려해야 할 중요한 요소 중 하나. 
일관성의 수준을 결정함.</p>
<table>
<thead>
<tr>
<th>일관성 모델</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>강한 일관성 (Strong Consistency)</strong></td>
<td>모든 읽기 연산은 가장 최근에 갱신된 결과를 반환한다. 즉, 클라이언트는 절대 낡은(out-of-date) 데이터를 보지 못한다.</td>
</tr>
<tr>
<td><strong>약한 일관성 (Weak Consistency)</strong></td>
<td>읽기 연산이 가장 최근에 갱신된 결과를 반환하지 못할 수도 있다.</td>
</tr>
<tr>
<td><strong>결과적 일관성 (Eventual Consistency)</strong></td>
<td>약한 일관성의 한 형태로, 갱신 결과가 즉시는 아니어도 <strong>결국에는 모든 사본에 반영(동기화)</strong>되는 모델이다.</td>
</tr>
</tbody></table>
<p>강한 일관성은 모든 사본에 갱신이 반영될 때까지 읽기/쓰기를 막는 방식인데, 요청 처리가 멈추므로 고가용성 시스템엔 부적합하다.
그래서 다이나모, 카산드라 등은 결과적 일관성 모델을 택하며, 이번 장의 설계도 이를 따른다.</p>
<p>다만 결과적 일관성은 병렬 쓰기 시 데이터 불일치가 생길 수 있어, 이를 클라이언트가 버전 정보로 해결해야 한다.</p>
<h3 id="비-일관성-해소-기법：-데이터-버저닝">비 일관성 해소 기법： 데이터 버저닝</h3>
<p>그 기법이 바로 데이터 버저닝이다.</p>
<p>데이터 버저닝은 데이터를 바꿀 때마다 기존 걸 덮어쓰지 않고, 새 버전을 만드는 것이다. 그래서 각 버전은 절대 변하지 않는다.</p>
<p>다만 버저닝만으로는 한계가 있다. 여러 클라이언트가 동시에(병렬로) 같은 데이터를 수정하면, 사본마다 서로 다른 버전을 갖게 되는데 — 이 중 어떤 버전이 진짜 최신인지, 아니면 서로 충돌하는 버전인지 버전 번호만으로는 판단할 수 없다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/5288c191-41bc-4f91-80d1-1c30633ce283/image.png" alt=""></p>
<h3 id="백터시계">백터시계</h3>
<p>버전들 사이의 인과관계를 파악하고 충돌 여부를 판단하는 문제를 해결하기 위해 등장한 기술이 바로 <strong>벡터 시계(vector clock)</strong>다.</p>
<p>$$D([S_1, v_1], [S_2, v_2], \dots, [S_n, v_n])$$</p>
<ul>
<li><strong>$D$</strong>: 데이터</li>
<li><strong>$S_i$</strong>: 그 데이터를 수정한 서버 (노드 ID)</li>
<li><strong>$v_i$</strong>: 그 서버에서 몇 번째 수정인지 나타내는 카운터 (버전)</li>
</ul>
<blockquote>
<p>*&quot;데이터 $D$는 $S_1$이 $v_1$번, $S_2$가 $v_2$번... 수정했다&quot;*는 버전 이력표.</p>
</blockquote>
<hr>
<p>어떤 서버 $S_i$가 데이터 $D$를 읽고 수정할 때:</p>
<ol>
<li><strong>이미 $[S_i, v_i]$ 항목이 있으면</strong><ul>
<li>그 카운터 숫자를 <strong>1 증가</strong>시킨다. ($v_i \rightarrow v_i + 1$)</li>
</ul>
</li>
<li><strong>목록에 $[S_i, v_i]$ 항목이 없으면</strong><ul>
<li>새로 <strong>$[S_i, 1]$ 항목을 추가</strong>한다.</li>
</ul>
</li>
</ol>
<hr>
<h4 id="동작-예시">동작 예시</h4>
<table>
<thead>
<tr>
<th align="left">단계</th>
<th align="left">발생한 작업</th>
<th align="left">Vector Clock 상태</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>1</strong></td>
<td align="left">$S_1$이 데이터 최초 작성</td>
<td align="left">$D([S_1, 1])$</td>
<td align="left">$S_1$ 항목 신규 추가 (규칙 2)</td>
</tr>
<tr>
<td align="left"><strong>2</strong></td>
<td align="left">$S_1$이 데이터를 한 번 더 수정</td>
<td align="left">$D([S_1, 2])$</td>
<td align="left">$S_1$ 카운터 +1 (규칙 1)</td>
</tr>
<tr>
<td align="left"><strong>3</strong></td>
<td align="left">$S_2$가 이 데이터를 가져와서 수정</td>
<td align="left">$D([S_1, 2], [S_2, 1])$</td>
<td align="left">$S_2$ 항목 신규 추가 (규칙 2)</td>
</tr>
<tr>
<td align="left"><strong>4</strong></td>
<td align="left">$S_1$이 $D$를 다시 가져와서 수정</td>
<td align="left">$D([S_1, 3], [S_2, 1])$</td>
<td align="left">$S_1$ 카운터 +1 (규칙 1)</td>
</tr>
</tbody></table>
<hr>
<h4 id="충돌">충돌</h4>
<p>두 버전 $D_A$와 $D_B$를 비교할 때:</p>
<ul>
<li><p><strong>선후 관계 있음 (정상 덮어쓰기 가능):</strong><br>한 쪽 버전의 모든 서버 카운터가 다른 쪽 버전의 카운터보다 <strong>작거나 같으면</strong> 옛날 버전임.</p>
</li>
<li><p><strong>동시 쓰기 충돌 (Conflict 발생!):</strong><br>어느 한쪽이 완벽하게 우위에 있지 않은 경우 (예: $D_A([S_1, 2], [S_2, 1])$ vs $D_B([S_1, 1], [S_2, 2])$)<br>-&gt; 시스템은 이 두 버전을 모두 남겨두고 <strong>클라이언트/어플리케이션에서 병합(Merge)</strong>하도록 처리함.</p>
</li>
</ul>
<h2 id="장애-처리">장애 처리</h2>
<p>대규모 분산 시스템에서 장애(failure)는 예외적 사건이 아니라 매우 흔하게 발생하는 일이다.</p>
<p>장애 처리는 크게 두 단계로 나뉜다:
장애 감지(Failure Detection) — 어떤 서버에 문제가 생겼는지 파악하는 것
장애 해소(Failure Resolution) — 감지된 장애에 대응하는 전략. 일시적 장애처리와 영구적 장애처리로 나뉜다.</p>
<h3 id="장애감지">장애감지</h3>
<p>최소 서버 2대 이상이 같은 문제를 겪어야 장애로 인식된다.</p>
<p>모든 노드 사이 에 멀티캐스팅(multicasting) 채널을 구축하는 것이 서버 장애를 감지하는 가장 손쉬운 방법이다. 
하지만 이 방법은 서버가 많을 때는 분명 비효율적 이다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/15249c7f-6120-422c-b373-703143791bc4/image.png" alt=""></p>
<p>서버가 많을 때는, 가십 프로토콜(gossip protocol) 같은 분산형 장애 감지 (decentralized failure detection) 솔루션을 채택하는 편이 보다 효율적이다. </p>
<p>분산형 장애감지 솔루션의 과정은 아래와 같다.</p>
<pre><code>[1. Heartbeat 증가] ──&gt; [2. Random Gossip 전파] ──&gt; [3. 최신값 Merge 갱신] ──&gt; [4. Timeout 장애 판정]</code></pre><p>예시
<img src="https://velog.velcdn.com/images/cold_as_hell/post/6b115cca-523a-41b9-8835-9f8e02e284bc/image.png" alt=""></p>
<p>데이터 분산과 멤버십 관리의 효율성을 극대화하기 위해,
테이블과 같은 멤버십 목록을 해시 링 위에 올려두자. 
해시 링 위에 올라간 노드 s0 역시 좌측 테이블과 같은 멤버십 목록을 가진다.</p>
<p>이후 가십프로토콜 솔루션의 동작은 아래와 같이 일어난다.</p>
<ol>
<li><strong>카운터 이상 감지</strong></li>
</ol>
<ul>
<li>노드 $s0$은 노드 $s2$ (Member ID = 2)의 박동 카운터(<code>9908</code>)가 다른 노드들에 비해 오랫동안 증가하지 않았다는 것(<code>Time: 11:58:02</code>)을 발견한다.</li>
</ul>
<ol start="2">
<li><strong>목록 전파 (Gossip)</strong></li>
</ol>
<ul>
<li>노드 $s0$은 노드 $s2$의 박동 카운터 정보를 포함한 멤버십 목록을 무작위로 선택된 다른 노드들(예: $s1, s3$)에게 주기적으로 전달한다.</li>
</ul>
<ol start="3">
<li><strong>장애 상태 확정 (Offline)</strong></li>
</ol>
<ul>
<li>노드 $s2$의 박동 카운터가 지정된 임계 시간 동안 계속 증가하지 않았음을 확인한 주변 노드들은 최종적으로 노드 $s2$를 <strong>장애(Offline) 노드</strong>로 표시한다.</li>
</ul>
<h3 id="장애-해소">장애 해소</h3>
<h4 id="일시적-장애-처리">일시적 장애 처리</h4>
<p>가십 프로토콜로 장애를 감지한 시스템은 가용성을 보장하기 위해 필요한 조치를 해야 한다. 
엄격한 정족수(strict quorum) 접근법을 쓴다면, 읽기와 쓰기 연산을 금지해야 할 것이다.</p>
<p><strong>느슨한 정족수(sloppy quorum) 접근법</strong>
엄격한 정졳수 접근법을 완화하여 가용성을 높인다.
(1) 정족수 요구사항을 강제하는 대신, 쓰기 연산을 수행할 W개의 건강한 서버와 읽기 연산을 수행할 R개의 건강한 서버를 해시 링에서 고른다. 
(2) 이때 장애 상태인 서버는 무시한다.</p>
<p><strong>단서 후임시 위탁(hinted handoff) 기법</strong>
네트워크·서버 장애 상황에서도 가용성을 유지하면서, 복구 후 힌트를 통해 데이터 정합성을 보장한다.
(1) 장애가 발생한 서버로 향하는 요청은 다른 서버가 임시로 대신 처리한다.
(2) 이때 대리 처리한 서버는 해당 변경사항에 대한 <strong>힌트(hint)</strong>를 남겨둔다.
(3) 장애 서버가 복구되면, 힌트에 기록된 변경사항을 일괄 반영하여 데이터 일관성을 유지한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/5eea3fc7-8a3d-4216-a527-dbf608c842d8/image.png" alt=""></p>
<h4 id="영구장애-처리">영구장애 처리</h4>
<p>단서 후 임시 위탁 기법은 일시적 장애를 처리하기 위한 것이다. 영구적인 노드의 장애 상태는 어떻게 처리해야 할까? 
그런 상황을 처리하기 위해 우리는 반-엔트로피(anti-entropy) 프로토콜을 구현하여 사본들을 동기화할 것이다.</p>
<p><strong>반-엔트로피 프로토콜</strong>
사본들을 비교하여 최신 버전으로 갱신하는 과정.
사본 간의 일관성이 망가진 상태를 탐지하고 전송 데이터 의 양을 줄이기 위해서는 머클(Merkle) 트리를 사용함.</p>
<p><strong>머클트리</strong>
각 노드에 그 자식 노드들에 보관된 값의 해시(자식노드가 종단leaf 노드인 경우), 또는 자식 노드들의 레이블로부터 계산된 해시
값을 레이블로 붙여두는 트리. </p>
<p>머클트리를 사용하면 대규모 자료 구조의 내용을 효과적이면서도 보안상 안전한 방법으로 검증(verification)할 수 있다.</p>
<p>해시트리라고도 불린다.</p>
<p><strong>머클트리 동기화 과정</strong>
서버간 데이터 비교는 머클 트리의 루트 노드 해시값을 비교하는 것으로 시작하며, 값이 일치하면 같은 데이터를 가진 것으로 판단되어 동기화 작업이 중단된다.</p>
<p>루트 노드 해시값이 다르면 왼쪽과 오른쪽 자식 노드의 해시값을 순서대로 비교하며 트리 아래쪽으로 내려가고, 이 과정을 반복하다 보면 서로 다른 데이터를 가진 버킷을 찾아낼 수 있어 해당 버킷들만 동기화하면 된다.</p>
<p>예시
<img src="https://velog.velcdn.com/images/cold_as_hell/post/6abbaaae-5e2b-4a21-8320-356fc821efe8/image.png" alt="">
(1) 키 공간을 버킷으로 나눈다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/686acc99-71d0-4f8b-aaad-461c23d704f6/image.png" alt="">
(2) 버킷에 포함된 각각의 키에 균등 분포 해시(uniform hash) 함수를 적용하여 해시 값을 계산한다</p>
<p>(3) 버킷별로 해시값을 계산한 후, 해당 해시 값을 레이블로 갖는 노드를 만든다
<img src="https://velog.velcdn.com/images/cold_as_hell/post/56cd88ff-b2b8-45de-890f-f8c4708c2627/image.png" alt=""></p>
<p>(4) 자식 노드의 레이블로부터 새로운 해시 값을 계산하여, 이진 트리를 상향식으로 구성해 나간다
<img src="https://velog.velcdn.com/images/cold_as_hell/post/206203d4-81bd-44ec-ba76-ed683a02693a/image.png" alt=""></p>
<p><strong>머클트리 동기화 시 유의점</strong>
머클 트리를 사용하면 동기화해야 할 데이터의 양은 실제 데이터 차이의 크기에 비례할 뿐, 서버에 저장된 전체 데이터 총량과는 무관하다는 장점이 있다.
다만 실제 시스템에서는 버킷 하나의 크기가 상당히 클 수 있음을 유의해야 한다.</p>
<p>예를 들어 10억(1B) 개의 키를 100만(1M) 개의 버킷으로 관리한다면, 버킷 하나당 약 1,000개의 키를 담당하게 되는 셈이다.
즉, 불일치가 발견된 버킷 단위로 동기화가 이루어지므로, 버킷 크기가 크면 실제 차이가 적더라도 한 번에 동기화되는 데이터량이 늘어날 수 있다.</p>
<h4 id="데이터-센터-장애-처리">데이터 센터 장애 처리</h4>
<p>데이터 센터 장애는 정전, 네트워크 장애, 자연재해 등 다양한 이유로 발생할수 있다. 
데이터 센터 장애에 대응할 수 있는 시스템을 만들려면 데이터를 여러 데이터 센터 에 다중화하는 것이 중요하다. </p>
<p>한 데이터센터가 완전히 망가져도 사용자는 다른 데이터 센터 에 보관된 데 이터를 이용할 수 있을 것이다.</p>
<h2 id="시스템-아키텍처-다이어그램">시스템 아키텍처 다이어그램</h2>
<p>시스템 아키텍처 다이어그램을 그릴 때 고려해야할 점은 아래와 같다.</p>
<ul>
<li>클라이언트-저장소 통신: get(key), put(key, value)라는 단순한 API를 통해 이루어진다.</li>
<li>중재자(coordinator): 클라이언트 요청을 받아 키-값 저장소에 대한 프락시 역할을 하는 노드.</li>
<li>노드 분포: 안정 해시(consistent hash)의 해시 링 위에 노드들이 배치되며, 노드의 자동 추가/삭제가 가능하도록 시스템은 <strong>완전히 분산화(decentralized)</strong>되어 있다.</li>
<li>데이터 다중화: 하나의 데이터는 여러 노드에 복제되어 저장된다.</li>
<li>SPOF 없음: 모든 노드가 동등한 책임을 지므로, 단일 장애점(Single Point of Failure)이 존재하지 않는다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/300bf269-f40b-472c-ad67-9abdf5c0c3ee/image.png" alt=""></p>
<h2 id="쓰기경로와-읽기경로">쓰기경로와 읽기경로</h2>
<h3 id="쓰기경로">쓰기경로</h3>
<p>카산드라의 사례로 쓰기 요청 이 특정 노드에 전달되면 무슨 일이 벌어지는지 확인해보자.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/e98917f2-3fdc-4a86-bd8e-a03bf93eec5f/image.png" alt=""></p>
<p>① 쓰기 요청이 커밋 로그(commit log) 파일에 기록된다.
② 데이터가 메모리 캐시에 기록된다.
③ 메모리 캐시가 가득차거나 사전에 정의된 어떤 임계치에 도달하면 데이터는 디스크에 있는 SSTable 에 기록된다.</p>
<p>※ SSTable (Sorted-String Table): &lt;Key, Value&gt; 순서쌍을 키 기준으로 정렬하여 관리하는 디스크 상의 불변(Immutable) 데이터 파일</p>
<h3 id="읽기경로">읽기경로</h3>
<p>읽기 요청을 받은 노드는 데이터가 메모리 캐시에 있는지부터 살핀다. 있는 경우에는 데이터를 즉시 클라이언트에 반환하고, 없는 경우에는 일련의 과정을 거쳐 디스크에서 결과를 반환한다.</p>
<p>반환할 응답이 메모리캐시가 없을 때 과정은 아래와같다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/9722f04b-4235-47ff-b8a0-9960ac2c9c2e/image.png" alt=""></p>
<p>① 데이터가 메모리에 없으므로 블룸 필터를 검사한다.
② 블룸 필터를 통해 어떤 SSTable에 키가 보관되어 있는지 알아낸다.
③ SSTable에서 데이터를가져온다.
④ 해당 데이터를 클라이언트에게 반환한다.</p>
<hr>
<h1 id="요약">요약</h1>
<table>
<thead>
<tr>
<th>목표 / 문제</th>
<th>기술 / 해결 방법</th>
</tr>
</thead>
<tbody><tr>
<td>대규모 데이터 저장</td>
<td>안정 해시</td>
</tr>
<tr>
<td>안정 해시를 사용해 서버들에 부하 분산</td>
<td>안정 해시</td>
</tr>
<tr>
<td>읽기 연산에 대한 높은 가용성 보장</td>
<td>데이터 다중화</td>
</tr>
<tr>
<td>데이터를 여러 데이터센터에 다중화</td>
<td>여러 데이터센터에 걸친 데이터 다중화</td>
</tr>
<tr>
<td>쓰기 연산에 대한 높은 가용성 보장</td>
<td>버저닝 및 벡터 시계를 사용한 충돌 해소</td>
</tr>
<tr>
<td>데이터 파티션</td>
<td>안정 해시</td>
</tr>
<tr>
<td>점진적 규모 확장성</td>
<td>안정 해시</td>
</tr>
<tr>
<td>다양성(heterogeneity)</td>
<td>안정 해시</td>
</tr>
<tr>
<td>조절 가능한 데이터 일관성</td>
<td>정족수 합의(quorum consensus)</td>
</tr>
<tr>
<td>일시적 장애 처리</td>
<td>느슨한 정족수 프로토콜(sloppy quorum)과 단서 후 임시 위탁(hinted handoff)</td>
</tr>
<tr>
<td>영구적 장애 처리</td>
<td>머클 트리(Merkle tree)</td>
</tr>
<tr>
<td>데이터센터 장애 대응</td>
<td>여러 데이터센터에 걸친 데이터 다중화</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[발사 ! 발사!!  발사!!!]]></title>
            <link>https://velog.io/@cold_as_hell/%EB%B0%9C%EC%82%AC-%EB%B0%9C%EC%82%AC-%EB%B0%9C%EC%82%AC-tvifzhs0</link>
            <guid>https://velog.io/@cold_as_hell/%EB%B0%9C%EC%82%AC-%EB%B0%9C%EC%82%AC-%EB%B0%9C%EC%82%AC-tvifzhs0</guid>
            <pubDate>Sun, 02 Aug 2026 01:05:14 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/181188">링크</a></p>
<blockquote>
<p><strong>한 번의 요격으로 최대한 많은 미사일을 맞추면서, 모든 미사일을 요격할 수 있는 최소 발사 횟수는?</strong></p>
</blockquote>
<ul>
<li>A 나라가 발사한 폭격 미사일은 x축에 평행한 직선 형태의 모양이며, 개구간을 나타내는 정수 쌍 (s, e) 형태로 표현된다.</li>
<li>B 나라는 특정 x 좌표에서 y축에 평행이 되도록 미사일을 발사하며, 발사된 미사일은 해당 x 좌표에 걸쳐있는 모든 폭격 미사일을 관통하여 한 번에 요격할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/950f3185-6294-410b-9604-489d6d4dd8f2/image.png" alt=""></p>
<pre><code>즉, 여러 폭격 미사일들의 구간이 겹치는 공통 범위 안에서
요격 미사일을 쏘면, 그 범위와 겹치는 미사일들을 한 번에 여러 개 잡을 수 있어 유리하다.</code></pre><hr>
<h1 id="사용한-알고리즘">사용한 알고리즘</h1>
<blockquote>
<p><strong>그리디 알고리즘</strong></p>
</blockquote>
<p>이 알고리즘은, <strong>매 순간마다 지금 당장 가장 좋아 보이는 선택을 하고, 그 선택을 절대 번복하지 않는다.</strong></p>
<p>문제를 풀 수 있는 모든 방법을 고려하며 최적 조합을 찾아내는 DP와 다르게 미래를 고려하지 않고 &quot;지금 이 순간&quot; 가장 좋은 선택을 한다.</p>
<h2 id="예시-0-1-배낭-문제knapsack-problem와-그리디알고리즘">예시) 0-1 배낭 문제(KnapSack Problem)와 그리디알고리즘</h2>
<p>배낭과 물건이 있고, 이 배낭에는 물건을 담거나 담지 않거나 두가지 선택만을 할 수 있다.
이때 배낭의 제한 용량 안에서 담은 물건들이 가장 무거워 지려면 어떻게 해야할까?</p>
<p>그리디 알고리즘으로 무게 대비 가치가 높은 물건부터 순서대로 배낭에 채워 최적해를 구한다고 가정해보자.</p>
<p>그리디 알고리즘은 선택을 번복하지 않기에 물건을 담은 이후 빼지 않는다.
그렇기 때문에 당장 가치가 높아 보이는 큰 물건 하나를 담느라, <strong>무게가 작은 물건 여러 개를 조합하여 더 큰 가치를 만드는 최적해</strong>는 구할 수 없는 경우가 생기기도 한다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/ded20d43-2fd0-4e33-9410-eb98df64c273/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/28bdb67c-6414-4b28-9357-301b154a6bcf/image.gif" alt=""></p>
<h2 id="그리디-알고리즘은-ㅇㅇ이다">그리디 알고리즘은 ㅇㅇ이다.</h2>
<ol>
<li>&#39;가장 OO한 것&#39;이라는 명확한 선택 기준이 있어야 한다.</li>
<li>한 번 선택한 것을 되돌리지 않아도 되어야 한다.</li>
<li>답을 구하기 위한 주요 로직에서 최초의 선택이 이후의 선택에도 영향을 주며, 최종 답까지 이어져야 한다</li>
</ol>
<hr>
<h1 id="풀이과정">풀이과정</h1>
<blockquote>
<p><strong>미사일을 e(끝점) 기준으로 정렬한 뒤, 가장 먼저 끝나는 미사일부터 요격한다.</strong></p>
</blockquote>
<pre><code class="language-python">&quot;&quot;&quot;
targets: 폭격 미사일의 x 좌표 범위 목록
s:  start. 폭격 미사일의 x좌표 첫 시작
e : end. 폭격 미사일의 x좌표 끝점
&quot;&quot;&quot;

# 미사일 1개의 끝점을 가지고 오는 함수 매개변수의 이름은 ms로 함..ㅋㅋㅋ  mㅣ sㅏ 일
def get_end(ms):
    return ms[1] 

# 미사일들을 끝점 기준으로 정렬한 뒤,
# 그리디하게 순회하며 필요한 최소 요격 횟수를 구하는 함수
def solution(targets):
    targets.sort(key=get_end) # get_end 함수가 반환하는 값을 key값으로 정렬 (= ms[1]. 즉, 미사일 끝점을 기준으로 정렬)

    result = 0 # 요격횟수
    last_pos = None # 마지막 요격 위치

    for s, e in targets: # targets의 각 원소 [시작점, 끝점]을 s, e로 구조분해하여 전체 순회
        if last_pos is None or s &gt;= last_pos: # 첫 요격이거나, 시작점(s)이 이전 요격 위치 이상이면 요격되지 않은 미사일이므로
            result += 1 # 요격횟수를 증가시키고 
            last_pos = e # 마지막 요격 위치를 현재 미사일의 끝점(e)으로 갱신

    return result # 이후 결과 반환
</code></pre>
<hr>
<h1 id="생각한-것">생각한 것</h1>
<h2 id="none-vs--floatinf-vs-0">None vs -float(&#39;inf&#39;) vs 0</h2>
<blockquote>
<p>None은 숫자의 범위(0, 음수, 무한대 등)와 무관하게 항상 안전하지만, 
0이나 특정 숫자로 초기화하는 방식은 문제의 제약조건에 맞아 떨어지는지 확인할 필요가 있다.</p>
</blockquote>
<p>요격 로직 초기값(last_pos)을 뭘로 잡을지 세 가지 방법에 대해 생각했다.</p>
<ul>
<li>None: 값이 완전히 존재하지 않음</li>
<li>-float(&#39;inf&#39;): 무한대의 음수인 실수</li>
<li>0: 숫자값 0</li>
</ul>
<p>&quot;없다는 것&quot;과 &quot;숫자값이 0인 것&quot;, 그리고 &quot;정수가 아닌 0 이하의 실수(음의 무한대)&quot;는 서로 전혀 다른 개념이기에,이 세 가지의 의미를 명확히 구분하여 이해하고 있어야, 문제의 조건에 따라 적절한 값을 선택할 수 있다.</p>
<p>예를 들어 이 문제에서 미사일 구간의 시작점 s가 음수를 포함할 수 있는 조건이었다면, last_pos의 초기값을 0으로 설정하는 방식은 부적절했다. </p>
<p>s &gt;= last_pos 비교에서 s가 음수(예: s = -222)로 주어질 경우, 이미 요격된 것으로 잘못 처리되는 오류가 발생하기 때문이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5]]></title>
            <link>https://velog.io/@cold_as_hell/5</link>
            <guid>https://velog.io/@cold_as_hell/5</guid>
            <pubDate>Fri, 31 Jul 2026 10:03:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/312d9cb0-e96d-4717-bca3-32a975ca173e/image.png" alt=""></p>
<hr>
<h1 id="안정해시란">안정해시란,</h1>
<p>수평적 규모 확장성을 위해서는, 쉽게 말해 서버를 여러대로 늘려 성능을 높이려면 사용자의 요청이나 데이터를 여러 서버에 골고루 분배하는 것이 중요하다.</p>
<p><a href="https://velog.io/@cold_as_hell/1">1장에서 살펴보았듯</a>, 사용자의 요청이나 데이터가 한 서버에 몰리면서버가 느려지거나 죽기 때문이다.</p>
<hr>
<h1 id="안정해시로-어떤-문제를-해결할-수-있나요">안정해시로 어떤 문제를 해결할 수 있나요?</h1>
<h2 id="해시-키-재배치rehash-문제">해시 키 재배치(rehash) 문제</h2>
<p>서버의 개수가 변경될 때 기존 키들이 다른 서버로 대량 이동하는 문제</p>
<hr>
<p>N개의 캐시 서버가 있을 때,  이 서버들에 부하를 균등하게 나누는 보편적 방법은 해시 함수를 사용하는 것이다.</p>
<pre><code class="language-python">serverlndex = hash(key) % N // N = 서버의 개수</code></pre>
<p>그리고 서버의 개수를 변경하면, N이 달라지기 때문에 해시 함수에 의한 해시키 배치도 변경된다.</p>
<h2 id="책은-너무-어렵다-ㅠㅠ">책은 너무 어렵다 ㅠㅠ</h2>
<p>조금  다가가기 쉬운 예시로 개념을 이해해보자.</p>
<p>Q. 윤아는 <strong>클로드♡</strong>, 코덱스, 제미나이, 커서, Aider 등의 ai에게만 의존해 
100만명의 회원이 있는 웹페이지를 만들었다!</p>
<p>그런데, 회원정보를 노린 해커가 윤아의 사이트를 공격했다. 
어찌저찌 공격을 막아냈지만 웹페이지 주요기능이 다운되었고 서버 롤백을 해야만 하는 상황...</p>
<p>이 사이트는 1개의 데이터 서버에 100만 명의 개인정보를 모두 보관하고 있었기 때문에, 
윤아는 데이터를 롤백하며 롤백시점 이전 회원가입한 신규회원 데이터, 비밀번호 등 개인정보를 변경한 회원의 변경이력 등을 날려버렸고 엄청난 악플에 시달리게 된다. ㅠㅠ</p>
<hr>
<p>이후 윤아는 과거의 실수를 반복하지 않기 위해, ai에서 찾은 정보를 토대로 데이터를 종류별로 서버를 나눠 저장하기로 했다.</p>
<p>회원 프로필 DB (User DB) → 이름, 이메일, 가입일 등 기본 정보
인증 정보 DB 3대 (Auth DB) → 비밀번호(해시값), 로그인 관련 정보
그외 DB 20대 (Guitar DB) → 기타 다른 정보</p>
<p>Auth DB는 로그인 요청이 워낙 많아 1대의 서버로는 감당이 안 됐다. 
그래서 윤아는 hash(user_id) % N 방식으로 여러 대의 서버에 나눠 저장하기로 했다. // 이
계산 한 번으로 어느 서버에 그 유저의 인증 정보가 있는지 즉시 알 수 있어, 
2번 이상의 조회 없이 빠르게 값을 읽고 가져올 수 있기 때문이다.</p>
<p>그리고 왜인지 삘을 받은 윤아는 스스로 Auth DB의 대수를 추가했다.</p>
<p>그리고 다음 날, 윤아는 100만 명의 회원들로부터 로그인이 되지 않는다는 수많은 항의를 받게 된다... </p>
<p>이유는 무엇일까?</p>
<hr>
<p>A. <del>작업을 하기 전 클로드에게 이 일을 해도 되냐는 질문을 하지 않았기 때문</del> </p>
<p>서버 대수를 추가하면서 hash(user_id) % N의 N값이 변경되었지만, 기존 인증 정보는 이전 해시 규칙에 따라 저장된 상태였다. 따라서 로그인 요청은 새로운 계산 결과에 따라 다른 서버를 조회하게 되었고, 실제 인증 정보가 저장된 서버를 찾지 못해 대부분의 사용자가 로그인에 실패하게 되었다. </p>
<p>이것이 일반 해시 방식에서 발생하는 해시 키 재배치(Rehash) 문제이다.</p>
<hr>
<h1 id="해시-공간과-해시링">해시 공간과 해시링</h1>
<p>해시공간과 해시링은 해시 키 재배치(rehash) 문제를 논리적으로 이해하는데 도움이 되는 개념이다. 
나는 이해하지 못했다만, 여러분은 이해해보길 바란다.</p>
<hr>
<p>오늘 발표의 목표 중 하나는 <strong>다음 순서 발표자를 빛내자</strong>이며,
실제로 다음 순서에 있는 발표자를 빛내기 위해 발표길이를 많이 줄였다.
ㄹㅇㄹㅇㄹㅇ이다.</p>
<p>화
이
팅!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[4]]></title>
            <link>https://velog.io/@cold_as_hell/4</link>
            <guid>https://velog.io/@cold_as_hell/4</guid>
            <pubDate>Tue, 28 Jul 2026 11:55:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/d662b1fb-3966-41b0-8d51-a1d6855831e3/image.png" alt=""></p>
<p>3장은 분량이 짧아 각자 읽어보는 것으로 하고, 4장으로 넘어갔다.</p>
<p>이번 장은 나의 slack VIP 그녀와 함께 발표를 맡았으며, 내가 맡은 범위는 1~2장이다.</p>
<hr>
<h1 id="처리율제한장치의-설계">처리율제한장치의 설계</h1>
<h2 id="처리율-제한-장치rate-limiter-란">처리율 제한 장치(rate limiter) 란?</h2>
<blockquote>
<p>처리율 제한 장치는 일정 시간 동안 허용되는 요청 수를 제한하여 서버 과부하와 악의적인 요청을 막는 장치이다.</p>
</blockquote>
<p>HTTP를 예로 들면 이 장치는 특정 기간 내에 전송되는 클라이언트의 요청 횟수를 제한한다. 
API 요청 횟수가 제한 장치에 정의된 임계치(threshold)를 넘어서면 추가로 도달한 모든 호출은 처리가 중단되는 것이다.</p>
<h3 id="예시">예시</h3>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/f22996a9-bb5d-4333-be1e-c2f773eb689c/image.png" alt="">
최근 디스코드에 업로드 된 수상한 링크를 살펴보자. 
체팅창을 도배하거나 선물하기 버튼을 빠르게 연타하면 일정시간 동작이 차단된다.
도배 금지 로직이 있는 것이다. 이것을 처리율 제한 장치라고한다.</p>
<pre><code class="language-js">
...

  // 사용자의 동작이 시간당 메시지 허용횟수를 초과하면(=도배하면) 일정 시간 채팅을 차단

  if (state.messageCount &gt; RATE_LIMIT_MAX) {

    state.blockedUntil = now + RATE_LIMIT_PENALTY_MS

    return RATE_LIMIT_PENALTY_MS

  }

...

</code></pre>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/1844e17c-036f-48a9-bcc8-3875763444b5/image.png" alt="">
<img src="https://velog.velcdn.com/images/cold_as_hell/post/6331f8ac-27a7-4a39-9107-233225a261d1/image.png" alt=""></p>
<p>이외에도 아래와 같은 기능을 처리율제한장치라고 한다.</p>
<ul>
<li>사용자는 초당 2회 이상 새 글을 올릴 수 없다.
• 같은 IP 주소로는 하루에 10개 이상의 계정을 생성할 수 없다.
• 같은 디바이스로는 주당 5회 이상 리워드(reward)를 요청 할 수 없다.</li>
</ul>
<hr>
<h2 id="장점">장점</h2>
<h3 id="1-자원-고갈resource-starvation-방지">1. 자원 고갈(Resource Starvation) 방지</h3>
<p>DoS(Denial of Service) 공격으로 인해 과도한 요청이 서버 자원을 모두 소모하여 정상 사용자가 서비스를 이용하지 못하는 &#39;자원 고갈(Resource Starvation)&#39; 상황을 방지할 수 있다.</p>
<h4 id="예시-1">예시</h4>
<p>구글 독스(Google docs) :API는 사용자당 분당 300회의 read 요청만 허용
x (sns): 3시간 동안 300개의 트윗만 올릴 수 있도록 제한</p>
<blockquote>
<p>특정 허용 횟수를 초과한 요청은 서버가 처리를 중단하여 과도한 요청으로 서버가 과부하되는 것을 막는다.</p>
</blockquote>
<hr>
<h3 id="2-비용-절감">2. 비용 절감</h3>
<p>추가 요청에 대한 처리를 제한하면 불필요한 API 호출을 줄일 수 있다. 
또한 서버를 과도하게 증설하지 않아도 되고, 중요한 API에 더 많은 자원을 할당할 수 있어 운영 비용을 절감할 수 있다.</p>
<h4 id="예시-2">예시</h4>
<p>신용 조회, 신용카드 결제, 건강 정보 조회와 같이 <strong>호출 횟수에 따라 비용이 발생하는 API</strong>는 과도한 요청이 발생하면 비용이 빠르게 증가한다.</p>
<p>따라서 처리율 제한 장치를 적용하면 <strong>불필요하거나 비정상적인 요청을 차단하여 API 호출 비용을 절감하고, 중요한 요청에 자원을 우선적으로 사용할 수 있다.</strong></p>
<hr>
<h3 id="3-서버-과부하-방지">3. 서버 과부하 방지</h3>
<p>봇(Bot)의 대량 요청이나 사용자의 비정상적인 이용 패턴으로 발생하는 과도한 트래픽을 제한하여 서버 과부하를 방지할 수 있다.</p>
<h4 id="예시-3">예시</h4>
<p>예를 들어 로그인 API에서 일정 시간 동안 허용되는 로그인 시도 횟수를 제한하면, 여러 비밀번호를 반복적으로 입력해 계정을 탈취하려는 비밀번호 대입 공격(Brute Force)과 봇의 반복 요청을 차단하여 서버 과부하를 예방할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/bde67b71-9017-4559-bc4d-d77a98332cee/image.png" alt=""></p>
<p>추가로 CAPTCHA와 같이 요청을 보내는 주체가 사람인지 봇인지 판별하는 기능과 함께 사용하면 더욱 효과적으로 자동화된 봇의 접근을 방지할 수 있을 것이다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/90b1fa86-0bb6-4c49-b1d3-52425c8f210d/image.png" alt=""></p>
<hr>
<h1 id="1단계">1단계</h1>
<blockquote>
<p><strong>문제 이해 및 설계 범위 확정</strong>
요구사항을 잘 정리하는 것이 중요하다.</p>
</blockquote>
<p>처리율 제한 장치를 구현하는 데에는 여러 가지 알고리즘을 사용할 수 있으며, 각각 고유한 장단점을 가지고 있다. 대표적인 알고리즘은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>알고리즘</th>
<th>기능</th>
<th align="center">시간복잡도</th>
<th>장점</th>
<th>단점</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>고정 윈도 카운터</strong><br>(Fixed Window Counter)</td>
<td>일정한 시간 구간마다 요청 수를 세고 제한을 초과하면 차단</td>
<td align="center"><strong>O(1)</strong></td>
<td>구현이 간단하고 메모리 사용량이 적음</td>
<td>경계에서 요청이 몰리면 실제 제한보다 많이 허용 가능</td>
<td><strong>채팅</strong>, <strong>댓글</strong>, <strong>로그인</strong></td>
</tr>
<tr>
<td><strong>이동 윈도 로그</strong><br>(Sliding Window Log)</td>
<td>요청 시간을 모두 저장하고 최근 요청만 계산</td>
<td align="center"><strong>O(n)</strong></td>
<td>가장 정확함</td>
<td>메모리 사용량이 많음</td>
<td><strong>인증 API</strong>, <strong>관리자 API</strong></td>
</tr>
<tr>
<td><strong>이동 윈도 카운터</strong><br>(Sliding Window Counter)</td>
<td>이전·현재 윈도 요청 수를 가중치로 계산</td>
<td align="center"><strong>O(1)</strong></td>
<td>정확도와 성능의 균형</td>
<td>근사 계산이라 오차 가능</td>
<td><strong>금융·결제 API</strong></td>
</tr>
<tr>
<td><strong>토큰 버킷</strong><br>(Token Bucket)</td>
<td>토큰을 일정 속도로 생성하고 요청 시 소비</td>
<td align="center"><strong>O(1)</strong></td>
<td>순간적인 요청(Burst) 허용</td>
<td>토큰 설정이 중요</td>
<td><strong>API Gateway</strong></td>
</tr>
<tr>
<td><strong>누출 버킷</strong><br>(Leaky Bucket)</td>
<td>일정한 속도로 요청 처리</td>
<td align="center"><strong>O(1)</strong></td>
<td>처리 속도가 일정</td>
<td>요청이 대기하거나 버려질 수 있음</td>
<td><strong>스트리밍</strong>, <strong>QoS</strong></td>
</tr>
</tbody></table>
<p>각 알고리즘의 장단점을 비교하고 어떤 상황에서 해당 알고리즘을 선택할지 근거를 함께 설명하려면, 면접관과 원활한 소통을 통해 요구사항을 정리하자.</p>
<hr>
<h1 id="2단계">2단계</h1>
<blockquote>
<p><strong>개략적 설계안 제시 및 동의 구하기</strong></p>
</blockquote>
<blockquote>
<p>처리율장치의 위치, 알고리즘, 아키텍처를 고려하여 어떻게 구성할지 결정하는 단계</p>
</blockquote>
<h2 id="처리율장치의-위치">처리율장치의 위치</h2>
<p>클라이언트-서버 통신모델 사용한다고 가정하자. 어디에 모델을 둘 것인가?</p>
<p><strong>클라이언트 vs 서버 vs 미들웨어</strong>
기술 스택이나 엔지니어링 인력, 우선순위, 목표에 따라 선택지를 정해보자</p>
<hr>
<h3 id="클라이언트">클라이언트</h3>
<p><strong>&quot; 불가능 &quot;</strong></p>
<p>클라이언트는 처리율 제한을 안정적으로 걸 수 있는 장소가 못 된다. 
클라이언트 요청은 쉽게 위변조가 가능해서다. 모든 클라이언트의 구현을 통제하는 것도 어려울 수 있다.</p>
<p>(1) <strong>클라이언트 요청은 쉽게 위변조가 가능하기에 처리율 제한을 안정적으로 걸 수 없다.</strong>
누군가 악의적으로 서비스를 공격하고자 할 때, 처리율장치의 코드를 제거하거나 우회해서 무제한 요청을 보낼 수도 있다.</p>
<p>(2) <strong>지나치게 다양한 클라이언트의 종류</strong>
크롬, 사파리, 네이버 웨일 등 다양한 웹 브라우저와 iOS, Android 등 여러 플랫폼은 각각 지원하는 기능과 저장 방식이 다르기 때문에 모든 환경에서 동일한 처리율 제한 로직을 구현하고 유지·관리하기 어렵다.</p>
<p>예를 들어, 브라우저를 껏다켜도 데이터가 보존 되는 성질의 Local Storage에 처리율장치와 관련된 산출물을 저장한다고 하자.
브라우저에서는 Local Storage를 이용해 브라우저를 종료한 뒤에도 처리율 제한과 관련된 정보를 저장할 수 있다. 그러나 Android나 iOS 네이티브 앱에는 Local Storage가 없기 때문에 DataStore, SharedPreferences, UserDefaults 등 플랫폼별 저장소를 사용해야 한다.</p>
<p>이처럼 플랫폼마다 구현 방식이 달라 모든 클라이언트에서 동일한 처리율 제한 로직을 유지·관리하기 어렵다.</p>
<hr>
<h3 id="서버">서버</h3>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/3b51ab17-1483-460b-9b0b-2db7bafa959e/image.png" alt=""></p>
<p>서비스 특징에 맞는 알고리즘을 자유롭게 선택하고 싶다면, 처리율장치를 서버에 구현해야한다.</p>
<p>처리율장치를 서버에 구현하면 아래와 같은 방법으로 동작한다.</p>
<p>(1) 클라이언트가 HTTP 요청을 보낸다.
(2) 서버에 도착하면 가장 먼저 처리율 제한 장치가 요청을 검사한다.
(3) 제한을 넘지 않았으면 API 서버가 요청을 처리한다.
(4) 제한을 넘었다면 API 서버까지 가지 않고 요청을 차단한다.</p>
<h4 id=""></h4>
<hr>
<h3 id="미들웨어">미들웨어</h3>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/e11d699d-eeb4-4e23-a125-fda2fe81a944/image.png" alt="">
알고리즘 선택에 제약이 있을 수 있지만, 중복 구현을 줄이고 모든 서비스에 동일한 정책을 일관되게 적용하고 싶다면 처리율 제한 장치를 미들웨어에 구현하는 것이 효과적이다.</p>
<p>이 방식은 처리율 제한 장치를 API 서버 내부에 두는 대신, 처리율 제한 미들웨어(Middleware)를 두어 API 서버로 전달되는 요청을 먼저 검사하고 통제한다.</p>
<p>클라우드 마이크로서비스의 경우 API Gateway라는 처리율 제한을 지원하는 미들웨어에 처리율제한장치를 위치시키는 경우가 많다. 클라우드 마이크로서비스는 기능별로 서버를 여러대 만들어 둔다는 특징이 있는데, </p>
<p>각 서버에 처리율 제한장치를 구현하는 것보다 모든 요청의 진입점인 API Gateway에서 한 번에 처리하는 것이 장치 관리와 확장 측면에서 훨씬 효율적이기 때문이다.</p>
<hr>
<h2 id="처리율장치를-위한-알고리즘">처리율장치를 위한 알고리즘</h2>
<p>알고리즘의 개념과 장점 단점에 대해 상세히 다루고 있는 챕터이다.
위에서 표와 간략히 살펴봤으니 발표세션에서는 넘어가고 각자 읽어보는 것으로 하자..! ^^</p>
<h2 id="처리율장치를-위한-아키텍처">처리율장치를 위한 아키텍처</h2>
<p>처리율장치의 처리율제한은 어떻게 구현하는 것이 좋을까?
처리율 제한 알고리즘의 핵심은 &#39;요청 횟수를 세는 것&#39;임으로 효율적인 요청횟수 세기로 부터 이를 고민해 볼 수 있겠다.</p>
<h3 id="개략적-아키텍처">개략적 아키텍처</h3>
<p>처리율 제한의 기본 아이디어는 사용자, IP 주소, API 엔드포인트 등 추적 대상별로 카운터(Counter)를 두고, 설정한 한도를 초과하는 요청은 차단하는 것.</p>
<p>이때 카운터 저장소로는 디스크 I/O 병목이 발생하는 RDB 대신, 빠른 속도와 만료(TTL) 기능을 제공하는 인메모리 캐시(Redis 등)를 사용하는 것이 바람직하다.</p>
<h3 id="구조-및-동작원리">구조 및 동작원리</h3>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/1b78906f-5a0b-40f7-9995-304019270ec1/image.png" alt=""></p>
<p><strong>주요 구성 요소</strong></p>
<ul>
<li><strong>클라이언트</strong>: API 요청을 전송한다.</li>
<li><strong>처리율 제한 미들웨어</strong>: API 서버 전단에서 요청을 중계하며 한도 초과 여부를 검사한다.</li>
<li><strong>Redis (인메모리 저장소)</strong>: 대상별 카운터와 만료 시간을 관리한다. (<code>INCR</code>, <code>EXPIRE</code> 명령어 활용)</li>
<li><strong>API 서버</strong>: 제약을 통과한 요청을 받아 실제로 비즈니스 로직을 처리한다.</li>
</ul>
<br>

<p><strong>요청 처리 흐름</strong></p>
<ul>
<li><strong>요청 수신</strong>: 클라이언트가 보내는 HTTP 요청을 처리율 제한 미들웨어가 먼저 수신한다.</li>
<li><strong>카운터 조회</strong>: 미들웨어는 Redis의 지정 키(버킷)에서 현재 카운터를 가져와 설정된 한도와 비교한다.</li>
<li><strong>분기 처리</strong>:<ul>
<li><strong>한도 초과 시</strong>: 요청을 즉시 거부(예: HTTP <code>429 Too Many Requests</code> 반환)하고 API 서버로 전달하지 않는다.</li>
<li><strong>한도 내 요청 시</strong>: 카운터 값을 1 증가(<code>INCR</code>)시키고 만료 시간(<code>EXPIRE</code>)을 설정/갱신한 뒤, 요청을 실제 API 서버로 전달한다.</li>
</ul>
</li>
</ul>
<hr>
<p>이렇듯, 1단계에서는 처리율 제한 장치의 정의와 핵심 필요성을, 2단계에서는 클라이언트/서버/미들웨어별 배치 위치와 Redis 기반 개략 아키텍처의 설계 방식을 알 수 있었다.</p>
<p>3단계에서는 상세 구현 및 분산 환경 고려사항을, 
4단계에서는 심화 주제 및 클라이언트 대응 전략을 알아보자.</p>
<hr>
<h1 id="3단계">3단계</h1>
<h1 id="4단계">4단계</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[정성데이터의 정량화]]></title>
            <link>https://velog.io/@cold_as_hell/%EC%A0%95%EC%84%B1%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%EC%A0%95%EB%9F%89%ED%99%94</link>
            <guid>https://velog.io/@cold_as_hell/%EC%A0%95%EC%84%B1%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%EC%A0%95%EB%9F%89%ED%99%94</guid>
            <pubDate>Fri, 17 Jul 2026 12:48:42 GMT</pubDate>
            <description><![CDATA[<p>해시태그를 위로 올려 놓기 위해 선공개 하는 게시글...</p>
<p>7월 활동기록(일부)에 대해 추후 업데이트 할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[복습] 목표는 누구? 목표는 누구???]]></title>
            <link>https://velog.io/@cold_as_hell/%EB%B3%B5%EC%8A%B5-%ED%83%80%EA%B2%9F%EB%84%98%EB%B2%84</link>
            <guid>https://velog.io/@cold_as_hell/%EB%B3%B5%EC%8A%B5-%ED%83%80%EA%B2%9F%EB%84%98%EB%B2%84</guid>
            <pubDate>Thu, 16 Jul 2026 11:23:29 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43165">링크</a></p>
<blockquote>
<p>numbers 배열의 <strong>각 숫자 앞에 + 또는 - 를 붙여 target 값을 만드는 모든 경우의 수</strong>를 구해보자.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/f50a7f00-b165-4913-aac9-4d7f753d0b66/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/15b76733-b3fe-4094-abcc-a4832f80f242/image.png" alt=""></p>
<hr>
<h1 id="풀이">풀이</h1>
<p>여러 방법으로 풀 수 있다.</p>
<h2 id="1-재귀-풀이">1. 재귀 풀이</h2>
<blockquote>
<p>f(idx,result)=f(idx+1, result+numbers[idx])+f(idx+1, result−numbers[idx])</p>
</blockquote>
<p>우리가 구하고자 하는 값을 f(idx,result) 로 표현하자. 
현재 인덱스인 idx와 누적합인 result를 합해 target을 구하자는 것이다.</p>
<p>이때, 이 값은 누적합과 &quot;idx번째 숫자를 더한 경우의 수&quot;, &quot;idx번째 숫자를 뺀 경우의 수&quot;를 각각 구해서 더해서 구한다.</p>
<p>n의 길이만큼 prefixSum 함수를 사용하며 이를 구해보자.</p>
<pre><code class="language-python">def solution(numbers, target):
    n = len(numbers)

    def prefixSum(idx, result):

        # 기저조건
        if idx == n: 
            if result == target: 
                return 1 
            else: 
                return 0

        return (
            prefixSum(idx + 1, result + numbers[idx]) 
            +  prefixSum(idx + 1, result - numbers[idx]) # + - 인 경우 모두 구하고, 함수 실행 결과값 더하기.
        )

    return prefixSum(0, 0) # 재귀로 n (numbers) 모두 순회</code></pre>
<hr>
<h2 id="2-데카르트곱-풀이">2. 데카르트곱 풀이</h2>
<p>데카르트 곱(Cartesian Product)이란 여러 집합에서 원소를 하나씩 뽑아 만들 수 있는 모든 조합을 뜻한다.</p>
<p>이를 이용해 타겟 넘버를 풀이하려면 itertools.product()를 사용해 각 자리마다 +, - 두 가지 선택지를 갖는 모든 부호 조합을 생성하고,  그 중 target을 만족하는 경우의 수를 세면 된다.</p>
<pre><code class="language-python">from itertools import product

def solution(numbers, target):
    n = len(numbers)
    answer = 0

    # 각 자리마다 (+, -) 두 가지 선택지가 있는 조합을 전부 생성
    for signs in product([1, -1], repeat=n):
        total = sum(s * num for s, num in zip(signs, numbers))
        if total == target:
            answer += 1

    return answer
</code></pre>
<hr>
<h1 id="3-dp-풀이">3. dp 풀이</h1>
<p>target과의 차이(remaining)를 key로, 그 remaining을 만드는 경우의 수를 value로 갖는 딕셔너리를 만들어보자.</p>
<p>target에서 시작해 numbers를 하나씩 처리하며, 각 숫자를 더한 경우와 뺀 경우로 remaining을 갱신하고 이 경우의 수를 딕셔너리에 누적한다.</p>
<pre><code class="language-python">from collections import defaultdict

def solution(numbers, target):
    ways = defaultdict(int)
    ways[target] = 1  # 초기 상태(부분 문제): 숫자 0개 사용, remaining=target인 경우 1가지

    for n in numbers:
        next_ways = defaultdict(int)
        for remaining, cnt in ways.items():
            # 이전 단계(작은 부분 문제)의 결과(cnt)를 그대로 이용해
            # 현재 숫자를 더하거나 뺀 다음 단계(큰 부분 문제)의 결과를 누적
            next_ways[remaining - n] += cnt
            next_ways[remaining + n] += cnt
        ways = next_ways  # dp 테이블 갱신: 한 단계 더 큰 부분 문제로 확장

    return ways[0] 
    ```</code></pre>
<hr>
<h1 id="생각한-것">생각한 것</h1>
<h2 id="복습을-통한-사고확장">복습을 통한 사고확장</h2>
<p>처음 문제를 풀이할 때는 재귀 풀이 방법밖에 떠올리지 못했는데,
문제를 다시 보니 데카르트 곱, DP 등 다양한 방법으로도 해결할 수 있을 듯 하다는 생각이 들었다.</p>
<p>꾸준히 알고리즘을 복습하면서 사고가 이전보다 확장되었음을 느낀다.</p>
<h2 id="알고리즘-선택-기준">알고리즘 선택 기준</h2>
<p>오늘 포스팅에서 다뤄보았듯, 알고리즘의 풀이에는 정말 다양한 방법이 있다.</p>
<p>그리고 문득, 알고리즘을 선택할 때 시간 복잡도와 공간 복잡도를 고려하는 것도 중요하지만, 실행 환경과 요구사항에 맞는 알고리즘을 선택할 수 있는 능력 역시 중요하다는 생각이 들었다.</p>
<p>실행 환경이나 메모리 제약, 코드의 유지보수성 등 프로젝트마다 고려해야 할 요소는 모두 다르다.
같은 문제를 해결하는 알고리즘이라도 대량의 데이터를 반복적으로 처리해야 하는 서비스에서는 중복 계산을 줄이는 DP가 더 적합할 수 있고,
빠르게 기능을 구현해야하거나 알고리즘의 흐름을 직관적으로 표현하는 것이 중요한 상황에서는 재귀가 더 적절한 선택이 될 수도 있듯 말이다.</p>
<p>여러 관점에서 문제를 바라보고, 각 알고리즘의 장단점을 이해하여 상황에 맞는 해결 방법을 선택할 수 있는 사람이 되고싶다.</p>
<p>그러기 위해서는 cs와 알고리즘을 함께 익혀가야 할 듯한데,
시스터디(혹은 씨스터디) 친구들이 있어 꾸준히 이런 일을 할 수 있는 것 같아 참 좋다.
ㄹㅇㄹㅇㄹㅇ이다.</p>
<p>굿.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2]]></title>
            <link>https://velog.io/@cold_as_hell/2</link>
            <guid>https://velog.io/@cold_as_hell/2</guid>
            <pubDate>Fri, 10 Jul 2026 03:43:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/6ce99815-c3c1-4344-b479-92a924859874/image.jpg" alt=""></p>
<p>이 챕터의 면접관이 되어,
원활한 면접진행을 위해 좀 더 내용을 열심히 읽어보았다.</p>
<hr>
<p>대규모 서비스를 설계하려면 필요한 저장 공간, 네트워크 대역폭, 서버 수 등을 미리 예측해야 한다.</p>
<p>하지만 설계 초기부터 모든 상황을 고려하여 정확한 수치를 계산하는 것은 현실적으로 어렵고, 오히려 시간과 비용이 많이 든다. 따라서 시스템 설계에서는 빠르게 규모를 예측하는 <strong>개략적인 규모 추정(Back-of-the-envelope Estimation)</strong> 을 활용한다.</p>
<p>&#39;개략적&#39;이라는 표현이 다소 생소할 수 있으니 먼저 뜻을 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/df2aa0bc-3aec-4974-a373-07a90af0b41e/image.png" alt=""></p>
<blockquote>
<p>&quot;개략적인 규모 추정은 보편적으로 통용되는 성능 수치를 바탕으로 사고 실험을 통해 추정치를 계산하여, 설계가 요구사항을 만족하는지 판단하는 과정이다.&quot;</p>
<p>— Jeff Dean</p>
</blockquote>
<p>즉, <strong>정확한 값을 계산하는 것이 목적이 아니라, 설계 방향이 적절한지 빠르게 판단하는 것이 목적</strong>이다.</p>
<p>이를 위해서는 규모 확장성을 표현하는 데 필요한 기본기를 갖추고 있어야 한다. 특히 <strong>2의 제곱수, 응답 지연, 가용성</strong> 과 같은 개념을 이해해야 저장 공간, 처리량, 서버 수 등을 합리적으로 추정할 수 있다.</p>
<hr>
<h1 id="2의-제곱수">2의 제곱수</h1>
<p>컴퓨터가 2진수를 사용하기 때문에, <strong>데이터의 크기나 저장 용량 단위도 2의 제곱수(1KB = 2¹⁰Byte, 1MB = 2²⁰Byte …)를 기준으로 정의된다.</strong></p>
<p><strong>따라서 분산 시스템에서 다루는 저장 공간, 메모리, 네트워크 사용량 등을 추정하려면 2의 제곱수와 데이터 용량 단위의 관계를 이해해야 한다.</strong></p>
<h2 id="단위">단위</h2>
<p>시스템 설계에서는 빠르게 규모를 추정해야 하는 경우가 많기 때문에 계산을 단순화하여 <strong>2¹⁰≈1천, 2²⁰≈100만</strong>처럼 근사치를 사용하는 경우도 있다.</p>
<table>
<thead>
<tr>
<th>2의 제곱</th>
<th>근사치</th>
<th>데이터 용량 단위</th>
<th>약어</th>
</tr>
</thead>
<tbody><tr>
<td>2¹⁰</td>
<td>약 1천</td>
<td>킬로바이트(Kilobyte)</td>
<td>1KB</td>
</tr>
<tr>
<td>2²⁰</td>
<td>약 100만</td>
<td>메가바이트(Megabyte)</td>
<td>1MB</td>
</tr>
<tr>
<td>2³⁰</td>
<td>약 10억</td>
<td>기가바이트(Gigabyte)</td>
<td>1GB</td>
</tr>
<tr>
<td>2⁴⁰</td>
<td>약 1조</td>
<td>테라바이트(Terabyte)</td>
<td>1TB</td>
</tr>
<tr>
<td>2⁵⁰</td>
<td>약 1000조</td>
<td>페타바이트(Petabyte)</td>
<td>1PB</td>
</tr>
</tbody></table>
<p>ex)
Q. 사용자 100만 명이 사용하는 서비스에서 게시글 하나의 크기가 2KB라면 필요한 저장 공간은?
A. &gt; A. 2KB × 1,000,000 = 2,000,000KB  ≈ 1,953MB ≈ 1.91GB ≈ 약 2GB</p>
<hr>
<h1 id="모든-프로그래머가-알아야-하는-응답지연-값">모든 프로그래머가 알아야 하는 응답지연 값</h1>
<p>저장 공간을 추정하기 위해 데이터 크기와 단위를 이해했다면, 이번에는 <strong>연산이 얼마나 빠르게 수행되는지</strong>도 알아야 한다.</p>
<p>대규모 시스템에서는 CPU 연산뿐만 아니라 메모리 접근, 디스크 I/O, 네트워크 통신 등 다양한 작업이 발생한다. 각 작업의 응답 지연(latency)을 이해하면 어떤 부분이 병목이 될 수 있는지 판단하고, 시스템의 성능을 대략적으로 예측할 수 있다.</p>
<h2 id="통상적인-컴퓨터에서-구현된-연산들의-응답지연-값">통상적인 컴퓨터에서 구현된 연산들의 응답지연 값</h2>
<table>
<thead>
<tr>
<th>연산</th>
<th align="right">응답 시간</th>
</tr>
</thead>
<tbody><tr>
<td>L1 캐시 참조</td>
<td align="right">0.5ns</td>
</tr>
<tr>
<td>분기 예측 오류 (Branch Mispredict)</td>
<td align="right">5ns</td>
</tr>
<tr>
<td>L2 캐시 참조</td>
<td align="right">7ns</td>
</tr>
<tr>
<td>뮤텍스(Mutex) 락/언락</td>
<td align="right">100ns</td>
</tr>
<tr>
<td>주 메모리 참조</td>
<td align="right">100ns</td>
</tr>
<tr>
<td>Zippy로 1KB 압축</td>
<td align="right">10,000ns (10μs)</td>
</tr>
<tr>
<td>1Gbps 네트워크로 2KB 전송</td>
<td align="right">20,000ns (20μs)</td>
</tr>
<tr>
<td>메모리에서 1MB 순차 읽기(Read)</td>
<td align="right">250,000ns (250μs)</td>
</tr>
<tr>
<td>같은 데이터센터 내 메시지 왕복 지연시간(RTT)</td>
<td align="right">500,000ns (500μs)</td>
</tr>
<tr>
<td>디스크 탐색(Seek)</td>
<td align="right">10,000,000ns (10ms)</td>
</tr>
<tr>
<td>네트워크에서 1MB 순차 읽기(Read)</td>
<td align="right">10,000,000ns (10ms)</td>
</tr>
<tr>
<td>디스크에서 1MB 순차 읽기(Read)</td>
<td align="right">30,000,000ns (30ms)</td>
</tr>
<tr>
<td>캘리포니아(CA) ↔ 네덜란드 패킷 왕복 지연시간</td>
<td align="right">150,000,000ns (150ms)</td>
</tr>
</tbody></table>
<p>2010년에 공개된 컴퓨터 연산의 응답 지연 값.
일부 수치가 현재와 달라졌지만, 각 연산의 상대적인 처리 속도를 이해하는 기준으로는 여전히 유용하다.</p>
<p><a href="https://colin-scott.github.io/blog/2012/12/24/latency-trends/?utm_source=chatgpt.com">2020년에는 이 수치를 시각화하기 위한 도구도 나왔다.</a></p>
<hr>
<h1 id="가용성에-관계된-수치들">가용성에 관계된 수치들</h1>
<p>가용성 : 시스템이 사용자가 필요할 때 정상적으로 서비스를 제공할 수 있는 정도. 즉, 시스템이 오랜시간동안 지속적으로 중단없이 운영될 수 있음을 의미한다.</p>
<p>고가용성 : 가용성을 매우높이기 위한 설계방식. 서비스 장애 예방과 조치에 보다 높은 초점을 맞추었다.</p>
<p>가용률 : 가용성 100%는 시스템이 단 한 번도 중단되지 않았음을 의미한다. </p>
<p>다만 현실적으로는 계획된 점검, 예기치 못한 장애 등으로 인해 100% 가용성을 달성하기는 매우 어려워, 실제 서비스에서는 99.9%, 99.99%, 99.999% 와 같은 수준을 목표로 설계하는 경우가 많다.</p>
<p><strong>수치사용 예시</strong></p>
<table>
<thead>
<tr>
<th align="center">가용률</th>
<th align="center">하루당 허용 장애시간</th>
<th align="center">주당 허용 장애시간</th>
<th align="center">월당 허용 장애시간</th>
<th align="center">연간 허용 장애시간</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>99%</strong></td>
<td align="center">14.40분</td>
<td align="center">1.68시간</td>
<td align="center">7.31시간</td>
<td align="center">3.65일</td>
</tr>
<tr>
<td align="center"><strong>99.9%</strong></td>
<td align="center">1.44분</td>
<td align="center">10.08분</td>
<td align="center">43.83분</td>
<td align="center">8.77시간</td>
</tr>
<tr>
<td align="center"><strong>99.99%</strong></td>
<td align="center">8.64초</td>
<td align="center">1.01분</td>
<td align="center">4.38분</td>
<td align="center">52.60분</td>
</tr>
<tr>
<td align="center"><strong>99.999%</strong></td>
<td align="center">864ms (0.864초)</td>
<td align="center">6.05초</td>
<td align="center">26.30초</td>
<td align="center">5.26분</td>
</tr>
<tr>
<td align="center"><strong>99.9999%</strong></td>
<td align="center">86.4ms (0.0864초)</td>
<td align="center">604.8ms (0.6048초)</td>
<td align="center">2.63초</td>
<td align="center">31.56초</td>
</tr>
</tbody></table>
<hr>
<h2 id="예제-트위터-qps와-저장소-요구량-추정">예제) 트위터 QPS와 저장소 요구량 추정</h2>
<h3 id="풀이핵심">풀이핵심</h3>
<p>개략적인 규모 추정 면접에서 가장 중요한 것은 문제를 해결해 나가는 사고 과정이다. 
<strong>면접관은 정답보다 올바른 절차와 논리적인 접근 방식을 더 중요하게 평가한다.</strong></p>
<p>예를 들어 99987 / 9.1 과 같은 계산 문제가 주어졌을 때, 정확한 계산 결과를 구하는 것이 목적이 아니다.
적절한 근사치를 활용해 빠르게 추정하고, 그 근거를 설명하는 능력이 더 중요하다.</p>
<p>※ QPS(Queries Per Second): 1초에 몇 개의 요청이 오는가</p>
<hr>
<h3 id="추정예시">추정예시</h3>
<ul>
<li>월간 능동 사용자(MAU, Monthly Active Users)는 <strong>3억 명</strong>이다.</li>
<li>이 중 <strong>50%</strong>의 사용자가 매일 서비스를 이용한다.</li>
<li>사용자는 하루 평균 <strong>2개의 트윗</strong>을 작성한다.</li>
<li>전체 트윗의 <strong>10%</strong>는 이미지나 동영상과 같은 미디어를 포함한다.</li>
<li>모든 데이터는 <strong>5년간 보관</strong>한다.</li>
</ul>
<hr>
<h4 id="일간-능동-사용자dau-계산">일간 능동 사용자(DAU) 계산</h4>
<ul>
<li>DAU = MAU × 50%</li>
<li>= 3억 × 50%</li>
<li>= <strong>1억 5천만 명</strong></li>
</ul>
<hr>
<h4 id="평균-qps-계산">평균 QPS 계산</h4>
<p>하루 동안 생성되는 트윗 수는 다음과 같다.</p>
<ul>
<li>1억 5천만 명 × 2개 = <strong>3억 건/일</strong></li>
</ul>
<p>이를 하루의 초(24 × 60 × 60 = 86,400초)로 나누면,</p>
<ul>
<li>QPS = 3억 ÷ 86,400 ≒ <strong>3,500 QPS</strong></li>
</ul>
<p>즉, 평균적으로 초당 약 <strong>3,500개의 요청</strong>을 처리해야 한다.</p>
<hr>
<h4 id="최대-qpspeak-qps-추정">최대 QPS(Peak QPS) 추정</h4>
<p>실제 서비스에서는 특정 시간대에 사용자가 몰리므로 평균 QPS만으로는 부족하다.</p>
<p>일반적으로 <strong>최대 QPS는 평균 QPS의 약 2배</strong>로 가정하여 용량을 산정한다.</p>
<ul>
<li>Peak QPS = 3,500 × 2</li>
<li>= <strong>약 7,000 QPS</strong></li>
</ul>
<hr>
<h4 id="미디어-저장소-요구량-계산">미디어 저장소 요구량 계산</h4>
<p>평균 트윗 크기는 다음과 같이 가정한다.</p>
<ul>
<li>tweet_id : <strong>64Byte</strong></li>
<li>텍스트 : <strong>140Byte</strong></li>
<li>미디어 : <strong>1MB</strong></li>
</ul>
<p>미디어를 포함하는 트윗은 전체의 10%이므로 하루 저장되는 미디어 용량은</p>
<ul>
<li>1억 5천만 명 × 2개 × 10% × 1MB</li>
<li>= <strong>약 30TB/일</strong></li>
</ul>
<hr>
<h4 id="5년간-저장소-요구량-계산">5년간 저장소 요구량 계산</h4>
<p>모든 미디어를 5년 동안 보관한다고 가정하면</p>
<ul>
<li>30TB × 365일 × 5년</li>
<li>= <strong>약 55PB(Petabyte)</strong></li>
</ul>
<p>의 저장 공간이 필요하다.</p>
<hr>
<h2 id="예제-윤아-쇼핑몰-qps-급증과-대응방안">예제) 윤아 쇼핑몰 QPS 급증과 대응방안</h2>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/220fadb7-f889-4f65-a64d-678d02fd540c/image.png" alt=""></p>
<h3 id="대답">대답</h3>
<blockquote>
<p>Peak QPS를 기준으로 병목을 감지하고, 운영 지표를 모니터링하여 운영 정책으로 대응하자.</p>
</blockquote>
<p>라이브 방송이 시작되면 채팅기능이 해제되며, 인기 상품을 구매하기 위한 동시 접속자가 늘어나면서 평소보다 QPS가 크게 증가할 것으로 예상됩니다.</p>
<p>이 쇼핑몰의 평소 하루 평균 접속자 수가 <strong>12만 명</strong>이라는 점을 고려하면, 이 정도 규모의 서비스를 안정적으로 운영하기 위해 기본적으로 <strong>트래픽 분산 구조와 대규모 트래픽 처리에 적합한 캐싱 전략</strong>이 적용되어 있을 것이라고 추정되는데, 정확한 수치는 문제에서 제시되지 않았으므로 <strong>용량 산정을 위한 기준값</strong>을 설정하겠습니다. </p>
<p>라이브 방송은 순간적인 트래픽 집중이 발생하는 이벤트이므로, <strong>일정 수준의 여유를 고려하여 접속 요청이 평소 대비 약 2배 증가하는 상황</strong>을 기준으로 추정하겠습니다. 
또한 라이브 방송의 특성상 시청자들의 참여도가 높기 때문에 <strong>모든 사용자가 최소 1회 이상 채팅을 입력</strong>한다고 가정하면, 웹 요청뿐 아니라 채팅 요청까지 동시에 발생하여 <strong>피크 큐피에스(Peak QPS)</strong>가 평상시보다 크게 증가할 것으로 예상됩니다.</p>
<p>이처럼 웹 요청, 스트리밍, 채팅, 주문을 모두 단일 서버가 처리하는 구조에서는 급격히 증가한 요청으로 인해 서버의 처리 한계를 초과하거나 네트워크 대역폭이 포화될 수 있습니다. 그 결과 응답 시간이 증가하고, 스트리밍 품질 저하, 방송 끊김, 버퍼링, 채팅 지연과 같은 문제가 발생할 수 있습니다.</p>
<p>이러한 상황을 감지하기 위해서는 <strong>운영 지표(QPS, 응답 시간(Response Time), CPU·메모리 사용률, 네트워크 사용량, 에러율 등)</strong>를 지속적으로 수집하는 모니터링 파이프라인을 구축하겠습니다. 운영 지표를 실시간 대시보드에서 확인하고, 평상시 기준치(Baseline)를 초과하는 경우 자동으로 알림을 받을 수 있도록 구성하여 이상 징후를 빠르게 감지하겠습니다.</p>
<p>이와 같이 <strong>평상시 운영 지표를 기준선으로 관리하고, 피크 큐피에스를 실시간으로 감지하여 병목 지점을 신속하게 파악한 뒤 운영 정책을 조정</strong>하겠습니다. 예를 들어 라이브 방송 시간에는 <strong>요청 제한</strong> 정책을 적용하거나, 중요 요청을 우선 처리하도록 우선순위를 조정하는 등 서비스 상황에 맞는 운영 정책을 통해 대규모 트래픽에 대응하겠습니다.</p>
<hr>
<h1 id="독서를-마치며">독서를 마치며...</h1>
<p>대용량 시스템을 설계하고 구축하는 과정에서 효율성과 합리적인 자원 관리를 위해서는 개략적인 규모 측정이 중요한 능력일 것이라 생각된다.</p>
<p>그런 이유로 내가 면접관인 면접에서는 아마 2의 제곱수 개념 (ex. 100mb는 몇 kb인가) 등의 암기형 문제보다는 이러한 값들이 개략적인 규모를 산정하고 시스템을 설계할 때 어떻게 활용되는지를 묻는 문제가 나올 듯 하다.</p>
<p>면접자 오이데~
<img src="https://velog.velcdn.com/images/cold_as_hell/post/fd192c33-b367-46df-95e4-18397147f79a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[잼얘하기 - 6]]></title>
            <link>https://velog.io/@cold_as_hell/GEO</link>
            <guid>https://velog.io/@cold_as_hell/GEO</guid>
            <pubDate>Wed, 08 Jul 2026 12:24:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/7c6a5174-7745-4aa0-8d4e-3ed309055543/image.png" alt=""></p>
<p>GEO를 잘 알면 서비스를 노출시키고 돈을 버는데 도움이 된다.</p>
<hr>
<h1 id="seo만-하던-회사들이-갑자기-geo를-공부하는-이유">SEO만 하던 회사들이 갑자기 GEO를 공부하는 이유</h1>
<p>우리 회사 서비스를 만들었다.</p>
<p>개발도 끝났다.</p>
<p>배포도 끝났다.</p>
<p>버그도 거의 없다.</p>
<p>이제 남은 건 사람들이 찾아오는 일이다.</p>
<p>예전 같았으면 마케팅팀이 말했다.</p>
<blockquote>
<p>&quot;SEO 좀 신경 써주세요.&quot;</p>
</blockquote>
<p>개발자는 메타태그 넣고,
사이트맵 만들고,
robots.txt 만들고,
구조화 데이터도 넣었다.</p>
<p>며칠 뒤.</p>
<p>Google 검색.</p>
<pre><code>우리 서비스가 3페이지에 있습니다.</code></pre><p>좋다.</p>
<p>그런데 2026년.</p>
<p>사용자는 검색창 대신 이렇게 묻는다.</p>
<blockquote>
<p>&quot;국내에서 사용할 수 있는 전자계약 SaaS 추천해줘.&quot;</p>
</blockquote>
<p>AI는 대답한다.</p>
<pre><code>A사
B사
C사</code></pre><p>...</p>
<p>우리 회사는?</p>
<p>없다.</p>
<hr>
<h1 id="분명-검색하면-나오는데">&quot;분명 검색하면 나오는데?&quot;</h1>
<p>맞다.</p>
<p>검색하면 나온다.</p>
<p>하지만 AI는 추천하지 않는다.</p>
<p>이게 SEO와 GEO의 차이다.</p>
<p>SEO는</p>
<blockquote>
<p>검색 결과에 노출되는 것.</p>
</blockquote>
<p>GEO는</p>
<blockquote>
<p>AI의 답변에 포함되는 것.</p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="이런-느낌이다">이런 느낌이다.</h1>
<p>예전</p>
<pre><code>Google

↓

우리 회사 홈페이지</code></pre><p>지금</p>
<pre><code>Google
        ↘

ChatGPT

Claude

Gemini
        ↓

우리 회사를 추천</code></pre><p>검색엔진이 하나 더 생긴 게 아니다.</p>
<p><strong>추천 엔진이 하나 생긴 것이다.</strong></p>
<hr>
<h1 id="ai는-기업을-어떻게-추천할까">AI는 기업을 어떻게 추천할까?</h1>
<p>많은 사람이</p>
<blockquote>
<p>&quot;LLM이 학습한 데이터로만 답하지 않나요?&quot;</p>
</blockquote>
<p>라고 생각한다.</p>
<p>물론 학습 데이터도 사용한다.</p>
<p>하지만 최근 AI는 필요에 따라 웹 검색도 하고, 여러 출처를 종합해 답변하기도 한다.</p>
<p>결국 AI가 참고하기 쉬운 형태로 기업 정보를 제공할수록 추천될 가능성이 높아진다.</p>
<hr>
<h1 id="geo는-결국-기업-정보를-잘-설명하는-기술이다">GEO는 결국 기업 정보를 잘 설명하는 기술이다.</h1>
<p>예를 들어 회사 홈페이지에</p>
<pre><code>혁신적인 플랫폼을 제공합니다.</code></pre><p>라고만 적혀 있다면</p>
<p>AI는 이해하기 어렵다.</p>
<p>반면</p>
<pre><code>서비스명

무엇을 하는 서비스인지

누가 사용하는지

어떤 문제를 해결하는지

가격

API 지원 여부

도입 사례</code></pre><p>처럼 구조적으로 정리되어 있다면</p>
<p>AI가 인용하기 쉬워진다.</p>
<hr>
<h1 id="개발자가-할-수-있는-geo">개발자가 할 수 있는 GEO</h1>
<p>생각보다 많다.</p>
<ul>
<li>시맨틱 HTML 작성</li>
<li>구조화 데이터(Schema.org)</li>
<li>robots.txt, sitemap 관리</li>
<li>명확한 URL 구조</li>
<li>API 문서 공개</li>
<li>FAQ 작성</li>
<li>제품 문서(Docs) 관리</li>
<li>변경 이력(Changelog) 공개</li>
</ul>
<p>...</p>
<p>어디서 많이 본 것 같지 않은가?</p>
<p>맞다.</p>
<p>대부분 SEO를 위해 하던 일이다.</p>
<hr>
<h1 id="seo와-geo는-경쟁-관계가-아니다">SEO와 GEO는 경쟁 관계가 아니다.</h1>
<p>많은 사람이</p>
<blockquote>
<p>&quot;이제 SEO 끝난 거 아닌가?&quot;</p>
</blockquote>
<p>라고 말한다.</p>
<p>오히려 반대다.</p>
<p>SEO가 잘 되어 있어야 GEO도 잘 된다.</p>
<p>검색엔진이 이해하기 쉬운 사이트는</p>
<p>AI도 이해하기 쉽기 때문이다.</p>
<hr>
<h1 id="개발자-입장에서-geo가-중요한-이유">개발자 입장에서 GEO가 중요한 이유</h1>
<p>예전에는</p>
<pre><code>서비스를 만든다.

↓

검색에 노출된다.

↓

사용자가 방문한다.</code></pre><p>였다.</p>
<p>이제는</p>
<pre><code>서비스를 만든다.

↓

AI가 이해한다.

↓

AI가 추천한다.

↓

사용자가 방문한다.</code></pre><p>라는 단계가 하나 추가되고 있다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>예전에는 기업들이 Google에게 선택받기 위해 SEO를 했다.</p>
<p>이제는 ChatGPT, Claude, Gemini 같은 생성형 AI에게도 <strong>&quot;우리 회사가 어떤 서비스를 하는지&quot;</strong> 명확하게 설명해야 하는 시대가 되었다.</p>
<p>SEO는 <strong>검색엔진에게 발견되기 위한 최적화</strong>이고,</p>
<p>GEO는 <strong>생성형 AI에게 이해되고 추천받기 위한 최적화</strong>다.</p>
<p>앞으로 기업의 온라인 경쟁력은 <strong>SEO와 GEO를 함께 고려하는 것</strong>에서 시작될 가능성이 크다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1]]></title>
            <link>https://velog.io/@cold_as_hell/1</link>
            <guid>https://velog.io/@cold_as_hell/1</guid>
            <pubDate>Tue, 07 Jul 2026 03:55:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/e04eb104-d66a-4be4-afd0-8d0c346127ca/image.jpg" alt=""></p>
<p>1장은 CDN 까지의 개념을 집중적으로 살펴봤다.</p>
<hr>
<h1 id="사용자-수에-따른-규모-확장성">사용자 수에 따른 규모 확장성</h1>
<p>사용자 수에 따른 프로그램을 설계해야한다는 내용이다.</p>
<hr>
<h2 id="단일서버">단일서버</h2>
<blockquote>
<p>모든 컴포넌트가 단 한대의 서버에서 실행되는 간단한 시스템.</p>
</blockquote>
<p>웹, 앱, 데이터베이스, 캐시 등이 전부 서버 한대에서 실행된다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/3edb1e37-41b7-48bd-8125-93bd3c176afe/image.png" alt=""></p>
<p>즉, 사용자가 도메인 이름으로 접속하면 자동으로 DNS 조회를 통해 서버의 IP 주소를 받아오고, 해당 주소로 HTTP 요청이 전달된다. 이후 웹 서버가 HTML 페이지나 JSON 형태의 응답을 생성하여 반환하기까지의 모든 과정이 하나의 서버에서 실행되는 것이다.</p>
<hr>
<h2 id="데이터베이스">데이터베이스</h2>
<blockquote>
<p>여러 사람이 동시에 공유하여 사용할 목적으로 통합 관리되는 데이터의 집합.</p>
</blockquote>
<p>이를 관리하는데, 여러 서버를 둘 수 도 있다.</p>
<p>EX) 웹/모바일 트래픽 처리 용도와 데이터베이스 서버를 따로두는 경우 : 독립적으로 확장 가능.</p>
<h3 id="데이터베이스의-종류">데이터베이스의 종류</h3>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/825fb32c-574d-42d6-9f08-409b9f7fad8f/image.png" alt=""></p>
<p>데이터베이스는 크게 관계형과 비관계형으로 나뉜다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>관계형 데이터베이스(RDBMS)</th>
<th>비관계형 데이터베이스(NoSQL)</th>
</tr>
</thead>
<tbody><tr>
<td>대표 제품</td>
<td>MySQL, Oracle Database, PostgreSQL</td>
<td>CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB</td>
</tr>
<tr>
<td>데이터 <BR> 저장 <BR>방식</td>
<td>테이블(Table), 행(Row), 열(Column)</td>
<td>키-값 저장소(Key-Value Store), 그래프 저장소(Graph Store), 칼럼 저장소(Column Store), 문서 저장소(Document Store)</td>
</tr>
<tr>
<td>데이터 <BR>조회</td>
<td>SQL을 사용하여 여러 테이블을 JOIN 가능</td>
<td>일반적으로 JOIN 연산 미지원</td>
</tr>
</tbody></table>
<p>관계형 데이터는 아래와 같은 상황에서 쓰기 좋다.</p>
<p>1) 엄격한 데이터 일관성과 트랜잭션 보장이 필요할 때 (ACID)
2) 데이터 간의 관계가 복잡하고 구조가 명확할 때 (Schema)
3) 다양하고 복잡한 조건의 데이터 조회가 필요할 때 (JOIN)
4) 비즈니스 규칙이 자주 바뀌거나 데이터가 계속 변할 때</p>
<p>이런 이유로 대부분의 프로그램에서 관계형을 채택 하지만 아래와 같은 경우에는 비관계형을 사용하는것이 좋다.</p>
<p>1) 아주 낮은 응답 지연시간 요구
2) 다루는 데이터가 관계형 데이터가 아님 (= 비정형)
3) 데이터 직렬화 혹은 역직렬화만이 필요
4) 분산 저장 및 수평 확장이 필요한 대용량 데이터를 다룰 때</p>
<blockquote>
<p><strong>데이터가 대용량이고 빠르게 쌓이는 비정형 데이터라면 NoSQL, 데이터의 정확성과 신뢰성, 복잡한 관계가 중요하다면 RDBMS를 쓰는 것이 좋다.</strong></p>
</blockquote>
<hr>
<h2 id="수직적-규모-확장스케일업-vs-수평적-규모확장스케일아웃">수직적 규모 확장(스케일업) vs 수평적 규모확장(스케일아웃)</h2>
<p>사용자가 증가하면 서버 확장이 필요하다.
서버 확장은 수직적 규모확장과 수평적 규모확장으로 가능하다. </p>
<p><strong>스케일업</strong> : CPU, RAM, SSD 등 증설해 <strong>기존 서버의 성능을 높이는 방식</strong></p>
<ul>
<li>스케일업시 장애에 대한 자동복구나 다중화 방안 제시되지 않아 장애발생시 프로그램 중단된다는 단점이 있음.</li>
</ul>
<p><strong>스케일아웃</strong> : 동일한 역할을 하는 <strong>서버를 여러 대 추가하여</strong> 처리량을 늘리는 방식</p>
<ul>
<li>스케일업의 단점으로 인해, 대규모 어플리케이션에서는 스케일 아웃 사용이 적절함.</li>
</ul>
<p>정리하자면,</p>
<p>스케일업이 유리 : 서버로 유입되는 트레픽 양이 적을 때
스케일아웃이 유리 : 서버로 유입되는 트레픽 양이 많을 때</p>
<hr>
<h2 id="트래픽-부하-분산-방법">트래픽 부하 분산 방법</h2>
<p>스케일업과 스케일아웃은 서버의 성능과 처리량을 향상시키지만, 대규모 서비스에서는 이것만으로 충분하지 않다.</p>
<p><strong>증가한 트래픽을 여러 웹 서버에 분산하고, 정적 콘텐츠를 효율적으로 제공하기 위한 추가적인 아키텍처</strong> 가 필요하다. </p>
<p>이유: 서버가 여러대일때, 쓰이지 않는 서버가 있으면 비효율.
서버 성능이 좋더라도, 하나에서 처리하는 것보다 여러대에서 처리하는 것이 효율.</p>
<h3 id="로드밸런서">로드밸런서</h3>
<p>부하분산집합(load balancing set)에 속한 웹 서버들에게 트래픽 부하를 고르게 분산.
사용자는 DNS주소를 통해 도메인에 접속함으로, 주소를 다르게 입력하지 않아도 괜찮다.</p>
<p>  <img src="https://velog.velcdn.com/images/cold_as_hell/post/a09d660e-4c52-4e48-a78f-851caf87e207/image.png" alt=""></p>
<h3 id="데이터베이스-다중화">데이터베이스 다중화</h3>
<p>데이터 원본은 주 서버(Master)에, 사본은 부 서버(Slave)에 저장하는 방식</p>
<ul>
<li><strong>쓰기 연산(Write):</strong> 마스터(Master) 서버에서만 처리 (insert, delete, update 등)</li>
<li><strong>읽기 연산(Read):</strong> 슬레이브(Slave) 서버들로 분산하여 처리. 통상 어플리케이션은 읽기 비중이 훨씬 높기 때문에 슬레이브 서버의 수가 더 많음.</li>
</ul>
<h4 id="⚠️-서버가-다운되면-어떻게-대응하는가">⚠️ 서버가 다운되면 어떻게 대응하는가?</h4>
<ul>
<li><strong>부(Slave) 서버가 다운된 경우:</strong> 읽기 연산은 한시적으로 모두 주 서버로 전달되며, 나머지 부 서버들이 부하를 분산함. 그동안 새로운 부 서버가 장애 서버를 대체함.</li>
<li><strong>주(Master) 서버가 다운된 경우:</strong> 한 대의 부 서버가 새로운 주 서버로 승격(Failover)되어 일시적으로 모든 연산을 수행함. <ul>
<li><em>실무 고려사항:</em> 부 서버의 데이터가 최신 상태가 아닐 수 있으므로(복제 지연), 누락된 데이터는 복구 스크립트를 돌려 채워넣어야 함.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/6cd13421-8675-4de9-ae6a-180b31731d8d/image.png" alt=""></p>
<hr>
<h2 id="캐시">캐시</h2>
<p>불러오는 비용이 크거나 자주 불러와야 하는 데이터를 메모리 안에 두고, 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소.  </p>
<h3 id="캐시-계층">캐시 계층</h3>
<p>&quot;느리고 무거운 원본 저장소(DB/디스크)에 접근하는 횟수를 줄이기 위해, 빠르고 가벼운 임시 저장소(메모리/RAM)에 데이터를 미리 올려두는 아키텍처 설계 기법&quot;</p>
<p>컴퓨터 엔지니어링의 대원칙인 &quot;자주 쓰는 데이터는 더 가깝고 빠른 곳에 둔다&quot;를 웹 서버와 데이터베이스 레이어에 적용했다.</p>
<p><img src="https://velog.velcdn.com/images/cold_as_hell/post/170fc7b0-bb43-494a-9e2a-f0d2adb4c208/image.png" alt=""></p>
<h3 id="사용시-유의할점">사용시 유의할점</h3>
<p>캐시는 어떤 상황에 바람직한가
어떤 데이터를 캐시에 두어야 하는가
캐시에 보관된 데이터는 어떻게 만료(expire)되는가? 
<a href="https://velog.io/@cold_as_hell/%EC%BA%90%EC%8B%9C-%EB%B6%88%EC%9D%BC%EC%B9%98%EC%99%80-%ED%95%B4%EA%B2%B0-%EC%A0%84%EB%9E%B5">일관성은어떻게 유지되는가?</a>
단일 장애 지점에 어떻게 대처할 것인가?
캐시 메모리는 얼마나 크게 잡을 것인가? 
데이터 방출(eviction) 정책은 무엇인가?</p>
<hr>
<h2 id="콘텐츠-전송-네트워크-cdn">콘텐츠 전송 네트워크 (CDN)</h2>
<p> 전 세계에 분산된 서버를 통해 웹 콘텐츠를 사용자와 가까운 위치에서 빠르게 전달하는 기술</p>
<p>  <img src="https://velog.velcdn.com/images/cold_as_hell/post/3fe1a4f9-ef7e-4cf3-85be-9c276584abc2/image.png" alt=""></p>
<p> ex) 
 react 라이브러리를 사용해 새로운 서비스를 개발한 민성.
 MZ한 에셋을 사용해 로드오브레전드를 개발한 김낍삽.</p>
<hr>
<h1 id="생각한-점">생각한 점</h1>
<p>트래픽이 적을 때 자원이 낭비되는 오버 엔지니어링을 경계하고, 트래픽이 많을 때는 병목 없이 유연하게 확장되는 비용 효율적인 아키텍처를 고민해야겠습니다.</p>
]]></description>
        </item>
    </channel>
</rss>