<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-khy</title>
        <link>https://velog.io/</link>
        <description>개발 블로그</description>
        <lastBuildDate>Sun, 23 Nov 2025 03:24:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-khy</title>
            <url>https://velog.velcdn.com/images/dev-gromit/profile/47a9a1dd-7495-4f5b-935b-6ef6af30c4f8/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-khy. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-gromit" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 키-값 저장소 설계 실습]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%ED%82%A4-%EA%B0%92-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%84%A4%EA%B3%84-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%ED%82%A4-%EA%B0%92-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%84%A4%EA%B3%84-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Sun, 23 Nov 2025 03:24:36 GMT</pubDate>
            <description><![CDATA[<h1 id="coordinator-기반-분산-key-value-저장소-실습">Coordinator 기반 분산 Key-Value 저장소 실습</h1>
<h2 id="실습-목적">실습 목적</h2>
<ul>
<li>Redis 서버 여러 대(예: 3대)를 하나의 “분산 저장소”처럼 보이게 만든다.</li>
<li>Coordinator가 데이터 처리의 중심이 되어 <strong>일관성 있게 여러 노드에 데이터 읽기/쓰기를 관리</strong>한다.</li>
<li>복잡한 Paxos/Raft 없이, <strong>단순 Majority-Writes, First-Response-Read</strong> 방식으로 구현한다.</li>
<li>분산 저장소에서 _Coordinator가 무엇을 하는지 이해한다.</li>
</ul>
<h2 id="설계-이유">설계 이유</h2>
<h3 id="1--coordinator-역할을-분명하게-체감할-수-있음">1.  Coordinator 역할을 분명하게 체감할 수 있음</h3>
<p>Redis 여러 개를 직접 운영하면 단순한 Key-Value 저장소가 바로 분산 시스템이 되지 않는다. 각 Redis는 다른 Redis가 뭘 하는지 모른다. 그래서 “중앙에서 조율하는 존재”가 필요하다.
이 조율을 담당하는 것이 <strong>Coordinator</strong> 이다.</p>
<h4 id="coordinator는-다음을-담당">Coordinator는 다음을 담당:</h4>
<p><strong>Write 요청 시</strong></p>
<ul>
<li>클라이언트로부터 key, value를 받음</li>
<li>모든 Redis 노드에 set 요청</li>
<li>최소 2/3 이상의 Redis가 성공하면 commit 성공</li>
<li>실패하면 “실패”로 처리하며 rollback(optional)</li>
</ul>
<p><strong>Read 요청 시</strong></p>
<ul>
<li>모든 Redis 노드에 get 요청</li>
<li>가장 먼저 응답 온 값을 반환</li>
<li>서버 간 값 불일치 감지 가능 → 이를 바탕으로 self-healing 가능</li>
</ul>
<h3 id="3-현실-세계의-분산-key-value-시스템과-유사">3. 현실 세계의 분산 Key-Value 시스템과 유사</h3>
<p>Cassandra, DynamoDB, Riak, MongoDB 등의 동작 일부를 축약해 체험할 수 있음.</p>
<h2 id="실습">실습</h2>
<pre><code class="language-python">from flask import Flask, request, jsonify
import redis
import threading
import time

app = Flask(__name__)

# Redis 노드 목록
REDIS_NODES = [
    redis.Redis(host=&#39;127.0.0.1&#39;, port=6379),
    redis.Redis(host=&#39;127.0.0.1&#39;, port=6380),
    redis.Redis(host=&#39;127.0.0.1&#39;, port=6381),
]

MAJORITY = 2  # 3대 중 2대 성공하면 commit 성공


# -------------------------------------------------------------------
# Write: 모든 노드에 SET 요청 → Majority 성공 시 OK
# -------------------------------------------------------------------
@app.route(&quot;/set&quot;, methods=[&quot;POST&quot;])
def set_value():
    data = request.json
    key = data[&quot;key&quot;]
    value = data[&quot;value&quot;]

    success_count = 0

    for node in REDIS_NODES:
        try:
            node.set(key, value)
            success_count += 1
        except Exception as e:
            print(f&quot;[WARN] SET 실패: {e}&quot;)

    if success_count &gt;= MAJORITY:
        return jsonify({&quot;status&quot;: &quot;OK&quot;, &quot;success_nodes&quot;: success_count})
    else:
        return jsonify({&quot;status&quot;: &quot;FAIL&quot;, &quot;success_nodes&quot;: success_count}), 500


# -------------------------------------------------------------------
# Read: 모든 노드에 GET 요청 후 가장 빠르게 응답한 값 반환
# -------------------------------------------------------------------
@app.route(&quot;/get&quot;, methods=[&quot;GET&quot;])
def get_value():
    key = request.args.get(&quot;key&quot;)

    responses = []
    threads = []

    def fetch(node):
        try:
            val = node.get(key)
            if val is not None:
                responses.append(val.decode())
        except:
            pass

    # 병렬로 GET 요청
    for node in REDIS_NODES:
        t = threading.Thread(target=fetch, args=(node,))
        t.start()
        threads.append(t)

    # 응답을 300ms만 기다림 → 가장 빠른 응답만 사용
    start = time.time()
    while time.time() - start &lt; 0.3:
        if responses:
            break
        time.sleep(0.01)

    # fallback: 모든 스레드 기다리기
    for t in threads:
        t.join(timeout=0.1)

    if responses:
        return jsonify({&quot;status&quot;: &quot;OK&quot;, &quot;value&quot;: responses[0]})
    else:
        return jsonify({&quot;status&quot;: &quot;NOT_FOUND&quot;}), 404


if __name__ == &quot;__main__&quot;:
    app.run(port=5000)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 키-값 저장소 설계]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%ED%82%A4-%EA%B0%92-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%ED%82%A4-%EA%B0%92-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 16 Nov 2025 02:42:27 GMT</pubDate>
            <description><![CDATA[<h1 id="키-값-저장소">키-값 저장소</h1>
<ul>
<li>고유 식별자인 키에 값을 할당하는 데이터베이스를 키-값 저장소라고 부른다.</li>
<li>키-값 저장소의 대표적인 시스템은 레디스가 있다.<h2 id="단일-서버-키-값-저장소">단일 서버 키-값 저장소</h2>
</li>
<li>한 대 서버만 사용하는 키-값 저장소에서는 키-값 쌍 전부를 메모리에 해시 테이블로 저장하는 게 제일 간단한 설계이다.</li>
<li>해시 테이블로 저장하는 방법은 O(1) 이어서 매우 빠르지만 시스템이 확장될 수록 모든 데이터를 메모리에 두는 것은 어렵다. 그나마 할 수 있는 방법은 두 가지다.<ol>
<li>데이터 압축</li>
<li>자주 쓰이는 데이터만 메모리에 두고 나머지는 디스크에 저장<h1 id="분산-키-값-저장소">분산 키-값 저장소</h1>
분산 시스템을 설계할 때는 CAP 정리를 이해해야 한다.<h2 id="cap-정리">CAP 정리</h2>
</li>
</ol>
</li>
<li>데이터 일관성(consistency): 분산 시스템에 접속하는 모든 클라이언트는 어떤 노드에 접속했느냐에 상관없이 언제나 같은 데이터를 보아야 한다.<ul>
<li><strong>모든 노드 전체가 동일한 데이터를 가진다</strong>는 게 아니라 <strong>해당 데이터가 저장되어야 하는 노드 집합(=샤드 + 그 샤드의 replica)</strong> 내에서 일관성을 유지하는 것</li>
</ul>
</li>
<li>가용성(availability): 분산 시스템에 접속하느 클라이언트는 일부 노드에 장애가 발생하더라도 항상 응답을 받을 수 있어야 한다.</li>
<li>파티션 감내(partition tolerance): 파티션은 두 노드 사이에 통신 장애가 발생하였음을 의미한다. 파티션 감내는 네트워크에 파티션이 생기더라도 시스템은 계속 동작하여야 한다는 것을 뜻한다.</li>
</ul>
<p>CAP 정리는 이들 가운데 어떤 두 가지를 충족하려면 나머지 하나는 반드시 희생되어야 한다는 것을 의미한다.</p>
<ul>
<li>CP 시스템: 일관성 &amp; 파티션 감내를 지원하는 키-값 저장소 (가용성 희생)</li>
<li>AP 시스템: 가용성 &amp; 파티션 감내를 지원하는 키-값 저장소 (일관성 희생)</li>
<li>CA 시스템: 파태션 감내를 희생하는 키-값 저장소. 그러나 통상 네트워크 장애는 피할 수 없는 일로 여겨지므로, 분산 시스템은 파티션 감내를 무적권 지원하도록 설계해야 한다. 따라서 CA 시스템은 없다.</li>
</ul>
<h3 id="cp-ap-ca-시스템은-왜-하나를-희생하라고-하지--셋-다-충족시키면-안되나">CP, AP, CA 시스템은 왜 하나를 희생하라고 하지?  셋 다 충족시키면 안되나?</h3>
<p>CAP 정리는 셋을 동시에 만족할 수 없는 것이 아니라, ‘네트워크 파티션이 발생한 상황’에서는 셋을 동시에 만족할 수 없다는 이론이다. <strong>네트워크가 나뉘는 장애 상황에서는 Consistency와 Availability를 동시에 만족할 수 없기 때문이다.</strong></p>
<h3 id="실-세계의-분산-시스템">실 세계의 분산 시스템</h3>
<p>세 대의 복제(replica) 노드 n1, n2, n3에 데이터를 복제하여 보관하는 상황을 예시로 들어본다.</p>
<p>n3에 장애가 발생하여 n1 및 n2와 통신할 수 없는 상황이 발생했다고 가정하면 아래와 같은 일들이 일어난다.</p>
<ul>
<li>클라이언트가 n1이나 n2에 저장한 데이터는 n3에 전달 X</li>
<li>n3에 저장되었으나 아지 n1이나 n2에 전달되지 않은 데이터가 존재</li>
</ul>
<p>이 상황에서는 가용성 또는 일관성을 선택해야 한다.</p>
<p>일관성을 선택</p>
<ul>
<li>세 서버 사이에 생길 수 있는 데이터 불일치 문제를 피하기 위해 n1과 n2에 대해 쓰기 연산을 중단 -&gt; 가용성 깨짐</li>
<li>은행권에서는 데이터 일관성을 양보하지 않는다.</li>
</ul>
<p>가용성</p>
<ul>
<li>낡은 데이터를 반환할 위험이 있더라도 계속 읽기 연산을 허용한다.</li>
<li>n1과 n2는 계속 쓰기 연산을 허용하고, 파티션 문제가 해결된 뒤에 새 데이터를 n3에 전송한다.</li>
<li>커뮤니티 사이트, 이커머스(결제 도메인은 모르겠지만)에서는 일관성 보다 가용성을 택하는게 더 옳은 선택이라고 생각</li>
</ul>
<h3 id="데이터-파티션">데이터 파티션</h3>
<p>대규모 어플리케이션에서 엄청난 양의 데이터를 저장하는 좋은 방법은 데이터를 작은 파티션들로 분할한 다음 여러 대 서버에 저장하는 것이다.
데이터를 파티션 단위로 나눌 때 중요한 문제는 다음 두 가지이다.</p>
<ul>
<li>데이터를 여러 서버에 고르게 분산할 수 있는가</li>
<li>노드가 추가되거나 삭제될 때 데이터의 이동을 최소화할 수 있는가
이 문제를 해결하기 위한 좋은 방법은 안정 해시이다.</li>
</ul>
<h3 id="데이터-다중화">데이터 다중화</h3>
<p>높은 가용성과 안정성을 확보하기 위해서는 데이터를 N개 서버에 비동기적으로 다중화할 필요가 있다. 안정 해시를 활용한 데이터 다중화 방법은 다음과 같다.</p>
<ul>
<li>안정 해시 링에서 시계방향으로 순회하며 만나는 첫 N개 서버에 데이터 사본을 보관한다.</li>
<li>안정 해시 링에 가상 노드를 사용하고 있는 경우 중복된 서버에 데이터를 보관하지 않도록 한다.</li>
</ul>
<h3 id="데이터-일관성">데이터 일관성</h3>
<p>여러 노드에 다중화된 데이터는 적절히 동기화가 되어야 한다. 정족수 합의 프로토콜을 사용하면 읽기/쓰기 연산 모두에 일관성을 보장할 수 있다.</p>
<h4 id="정족수-합의-프로토콜">정족수 합의 프로토콜</h4>
<ul>
<li>N = 사본 개수</li>
<li>W = 쓰기 연산에 대한 정족수. 쓰기 연산이 성공한 것으로 간주되려면 적어도 W개의 서버로부터 쓰기 연산이 성공했다는 응답을 받아야 한다.</li>
<li>R = 읽기 연산에 대한 정족수.</li>
</ul>
<p>중재자가 데이터를 읽기/쓰기를 하면서 각 노드에서 연산에 대한 성공 응답을 받고, 정해진 프로토콜에 따라 일정 수의 성공 응답을 받아야 해당 연산은 성공했다고 판단한다.</p>
<p>N, W, R 구하는 예시</p>
<ul>
<li>R=1, W=N: 빠른 읽기 연산에 최적화된 시스템</li>
<li>W=1, R=N: 빠른 쓰기 연산에 최적화된 시스템</li>
<li>W+R &gt; N: 강한 일관성이 보장됨</li>
<li>W+R &lt;= N: 강한 일관성이 보장되지 않음.</li>
</ul>
<h4 id="중재자란">중재자란</h4>
<p>중재자(Coordinator): 여러 노드(혹은 서비스)들이 서로 충돌 없이, 일관성 있는 상태로 동작하도록 조율하는 역할
중재자의 3가지 핵심 역할</p>
<ol>
<li>요청을 올바른 노드로 라우팅: 분산 서비스는 데이터가 여러 노드로 나뉘어 저장됨(샤딩).이때 클라이언트가 직접 노드를 선택하게 하면 혼란이 생김. 그래서 중재자가 대신 해준다.</li>
<li>일관성 유지: 중재자는 다음을 담당한다:<ul>
<li>복제 요청을 모든 replica에 전달</li>
<li>복제가 완료됐는지 확인</li>
<li>실패 시 롤백 또는 재시도</li>
<li>읽기/쓰기 일관성 보장</li>
</ul>
</li>
<li>동시성/경쟁 상태 해결: 여러 노드가 같은 자원에 접근하면 충돌이 발생할 수 있다. 중재자는 “누가 먼저 처리할지”를 결정한다.</li>
</ol>
<h3 id="일관성-모델">일관성 모델</h3>
<ul>
<li>강한 일관성: 모든 읽기 연산은 가장 최근에 갱신된 결과를 반환한다.</li>
<li>약한 일관성: 읽기 연산은 가장 최근에 갱신된 결과를 반환하지 못할 수 있다.</li>
<li>결과적 일관성: 약한 일관성의 한 형태로, 갱신 결과가 결국에는 모든 사본에 반영되는 모델이다.<h4 id="결과적-일관성-모델">결과적 일관성 모델</h4>
강한 일관성을 달성하는 일반적인 방법은 모든 사본에 현재 쓰기 연산의 결과가 반영될 때 까지 읽기/쓰기를 금지하는 것. 이 방법은 고가용성 시스템에서는 적합하지 않다. 그래서 다이나모 또는 카산드라 같은 저장소는 결과적 일관성 모델을 택하고 있다.</li>
</ul>
<p>결과적 일관성 모델은 쓰기 연산이 병렬적으로 발생하면 시스템에 저장된 값의 일관성이 깨질 수 있어서 이 문제는 클라이언트가 해결해야한다. 해결 방법으로 데이터 버저닝 기법이 있다.</p>
<p><strong>데이터버저닝</strong></p>
<ul>
<li>데이터를 업데이트할 때마다 “이 값이 몇 번째 버전인지” 번호를 붙여서 충돌을 감지하고 해결하는 방법</li>
<li>클라이언트에서 쓰기 연산 시 데이터의 버전을 함께 보낸다. 저장소에 저장할 때 버전이 겹치면 쓰기 연산에 실패하고, 클라이언트에서는 이에 대한 후속 처리를 한다.</li>
</ul>
<p><strong>벡터 클럭</strong></p>
<ul>
<li>각 노드 마다 데이터 업데이트에 참여한 버전과 메타데이터(타임 스탬프)를 관리한다.</li>
<li>주기적으로 노드 별로 버전이 다른 데이터를 찾아서 메타데이터를 확인하여 모든 노드에 최신 데이터가 입력될 수 있도록 하여 일관성을 맞출 수 있다.</li>
<li>단일 버전 번호는 “어느 업데이트가 최신인지”만 알려주지만, <strong>Vector Clock은 업데이트가 ‘원인-결과’ 관계인지 ‘충돌’인지까지 알려준다.</strong></li>
</ul>
<h3 id="장애-감지">장애 감지</h3>
<p>분산 시스템에서는 보통 두 대 이상의 서버가 똑같이 서버 A의 장애를 보고해야 해당 서버에 실제로 장애가 발생했다고 간주한다.</p>
<p>가십 프로토콜 같은 분산형 장애 감지 솔루션을 채택하는 편이 장애 감지를 하기에 좋다.
가십 프로토콜의 동작 원리는 다음과 같다.</p>
<ul>
<li><strong>1) 각 노드는 주기적으로 랜덤한 다른 노드를 하나 선택한다.</strong><ul>
<li>“오늘 누구한테 소문을 퍼뜨릴까?” 하는 느낌.</li>
</ul>
</li>
<li><strong>2) 선택된 노드에게 자기 상태 정보를 전달한다.</strong><ul>
<li>예: 최신 데이터 버전 정보, 헬스 체크 정보, 멤버십 정보 등.</li>
</ul>
</li>
<li><strong>3) 상대 노드는 전달받은 정보를 자신의 정보와 비교한다.</strong><ul>
<li>더 최신이면 반영하고,</li>
<li>뒤쳐졌으면 상대에게 요청해서 최신 정보를 받아옴.</li>
</ul>
</li>
<li><strong>4) 그리고 그 노드도 다시 랜덤한 노드에게 같은 정보를 전파한다.</strong><ul>
<li>마치 소문이 퍼지는 방식 그대로임.</li>
</ul>
</li>
<li><strong>5) 이런 소문 퍼뜨리기가 반복되면 전체 클러스터에 정보가 퍼지게 된다.</strong><ul>
<li>O(log N) ~ O(N) 정도의 시간 안에 사실상 모든 노드가 동기화됨.</li>
</ul>
</li>
<li><strong>6) 일부 노드가 실패해도 전체 정보는 계속 퍼지므로 매우 높은 내결함성을 가짐.</strong><ul>
<li>노드 몇 개가 죽어도 소문은 계속 퍼진다.</li>
</ul>
</li>
</ul>
<h2 id="시스템-아키텍쳐">시스템 아키텍쳐</h2>
<h3 id="쓰기-경로">쓰기 경로</h3>
<ol>
<li>쓰기 요청이 커밋 로그 파일에 기록된다.</li>
<li>데이터가 메모리 캐시에 기록된다.</li>
<li>메모리 캐시가 가득차거나 사전에 정의된 어떤 임계치에 도달하면 데이터는 디스크에 있는 SSTable에 기록된다.</li>
</ol>
<h3 id="읽기-경로">읽기 경로</h3>
<ol>
<li>읽기 요청을 받은 노드는 데이터가 메모리 캐시에 있는지부터 살핀다. 있으면 반환한다.</li>
<li>캐시에 없으면 블룸 필터를 검사한다.</li>
<li>블룸 필터를 통해 어떤 SSTable에 키가 보관되어 있는지 알아낸다.</li>
<li>SSTable에서 데이터를 가져온다.</li>
<li>해당 데이터를 클라이언트에게 반환한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 안정 해시 실습]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Sat, 08 Nov 2025 14:38:50 GMT</pubDate>
            <description><![CDATA[<h1 id="안정-해시-구현">안정 해시 구현</h1>
<p>안정 해시를 직접 만들어본다.</p>
<pre><code class="language-python">import hashlib
import bisect

class ConsistentHashRing:
    def __init__(self, replicas=10):
        # replicas: 각 노드를 가상 노드로 몇 개 복제할지 (부하 균형용)
        self.replicas = replicas

        # ring: 해시 값(key) -&gt; 실제 노드 이름(node) 매핑
        self.ring = {}

        # sorted_keys: 해시 링의 모든 key를 정렬된 상태로 저장 (이진 탐색용)
        self.sorted_keys = []

    def _hash(self, key):
        # 주어진 문자열(key)을 MD5 해시로 변환 → 16진수 → 정수형으로 반환
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def add_node(self, node):
        # 실제 노드를 추가할 때, replicas(예: 100개) 만큼 가상 노드 생성
        for i in range(self.replicas):
            # 노드 이름 + 인덱스 조합으로 가상 노드 구분
            key = f&quot;{node}:{i}&quot;

            # 해당 key의 해시값 계산
            h = self._hash(key)

            # 해시 링에 등록 (해시값 -&gt; 노드)
            self.ring[h] = node

            # 해시 값을 정렬 리스트에 삽입 (bisect: 이진 탐색 기반 정렬 삽입)
            bisect.insort(self.sorted_keys, h)

    def remove_node(self, node):
        # 노드를 제거할 때는 가상 노드들도 모두 제거
        for i in range(self.replicas):
            key = f&quot;{node}:{i}&quot;
            h = self._hash(key)
            del self.ring[h]
            self.sorted_keys.remove(h)

    def get_node(self, key):
        # 링이 비어 있으면 None 반환
        if not self.ring:
            return None

        # 요청 키의 해시값 계산
        h = self._hash(key)

        # sorted_keys에서 h보다 큰 첫 번째 위치를 찾음 (이진 탐색)
        # % len(...) 을 해서 해시 링의 끝을 넘어가면 처음으로 순환되게 함
        idx = bisect.bisect(self.sorted_keys, h) % len(self.sorted_keys)

        # 해당 위치의 노드를 반환
        return self.ring[self.sorted_keys[idx]]
</code></pre>
<p>위 코드를 아래와 같이 스크립트를 작성하여 실행한다.</p>
<pre><code class="language-python">ring = ConsistentHashRing()

servers = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;]
for s in servers:
    ring.add_node(s)

users = [f&quot;user{i}&quot; for i in range(1, 21)]

# 초기 분배
print(&quot;=== Initial distribution ===&quot;)
initial_node = list()
for u in users:
    node = ring.get_node(u)
    initial_node.append((u, node))
    print(u, &quot;-&gt;&quot;, ring.get_node(node))

# 서버 증설
ring.add_node(&quot;D&quot;)
print(&quot;\n=== After adding server D ===&quot;)
after_node = list()
for u in users:
    node = ring.get_node(u)
    after_node.append((u, node))
    print(u, &quot;-&gt;&quot;, ring.get_node(u))

diff_node = dict()
for i in range(len(initial_node)):
    if initial_node[i] != after_node[i]:
        diff_node[initial_node[i][0]] = [initial_node[i][1], after_node[i][1]]

print(&quot;\ndiff-node-user: &quot; )
for k, n in diff_node.items():
    print(k, &#39;--&gt;&#39;, n)</code></pre>
<p>위 스크립트를 실행시키면 아래와 같이 출력된다.</p>
<pre><code>/Users/youkihoon/PyCharmMiscProject/.venv/bin/python /Users/youkihoon/PyCharmMiscProject/huge-system-design/khyou/ConsistentHasing.py 
=== Initial distribution ===
user1 -&gt; B
user2 -&gt; C
user3 -&gt; B
user4 -&gt; B
user5 -&gt; C
user6 -&gt; B
user7 -&gt; B
user8 -&gt; C
user9 -&gt; C
user10 -&gt; B
user11 -&gt; C
user12 -&gt; C
user13 -&gt; C
user14 -&gt; C
user15 -&gt; C
user16 -&gt; C
user17 -&gt; B
user18 -&gt; C
user19 -&gt; B
user20 -&gt; C

=== After adding server D ===
user1 -&gt; B
user2 -&gt; C
user3 -&gt; B
user4 -&gt; D
user5 -&gt; C
user6 -&gt; B
user7 -&gt; D
user8 -&gt; A
user9 -&gt; C
user10 -&gt; D
user11 -&gt; C
user12 -&gt; A
user13 -&gt; A
user14 -&gt; C
user15 -&gt; A
user16 -&gt; C
user17 -&gt; B
user18 -&gt; D
user19 -&gt; B
user20 -&gt; A

diff-node-user: 
user4 --&gt; [&#39;B&#39;, &#39;D&#39;]
user7 --&gt; [&#39;B&#39;, &#39;D&#39;]
user10 --&gt; [&#39;B&#39;, &#39;D&#39;]
user18 --&gt; [&#39;C&#39;, &#39;D&#39;]

Process finished with exit code 0
</code></pre><p>replicas 를 늘리면 Server에 User가 적절히 분배되지만, Server가 바뀌는 User가 늘어나게 된다. 반대로 replicas 를 줄이면 Server에 User가 적절히 분배되지는 않지만, Server가 바뀌는 User가 줄어든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 안정 해시 설계]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 26 Oct 2025 12:54:51 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>분산된 환경에서는 해시 키를 많이 사용한다.</p>
<ul>
<li>분산 캐시(예: Redis): 서비스에 캐시 서버가 여러 대 있을 때, 어떤 서버에 데이터를 저장할지 결정해야 함.</li>
<li>데이터베이스 샤딩 (Sharding): 데이터베이스가 너무 커서 여러 DB 인스턴스로 나누고 싶을 때.
분산환경에서 일반적으로 사용하는 해시 방식(hash(key) % N)에는 <strong>치명적인 문제</strong>가 하나 있다. <strong>서버 개수가 바뀌면(추가되거나 삭제되면) 거의 모든 키의 매핑이 바뀐다</strong>는 것이다. </li>
</ul>
<p>예를 들어</p>
<pre><code>hash(key) % 3   → 서버 A, B, C 중 하나</code></pre><p>이 상태에서 서버 D를 추가하면</p>
<pre><code>hash(key) % 4</code></pre><p>가 되어 <strong>기존 키의 75%가 다른 서버로 이동</strong>해야 한다.
이건 <strong>캐시 서버나 샤딩 구조</strong>에서는 재앙이다.</p>
<p>→ 안정 해시는 이런 <strong>“리밸런싱 문제”를 최소화하기 위해 등장했다.</strong></p>
<h1 id="안정-해시">안정 해시</h1>
<p>수평적 규모 확장성을 달성하기 위해서는 요청 또는 데이터를 서버에 균등하게 나누는 것이 중요하다. 안정 해시는 이 목표를 달성하기 위해 보편적으로 사용하는 기술이다.</p>
<h2 id="해시-공간과-해시-링">해시 공간과 해시 링</h2>
<h3 id="안정-해시의-핵심-아이디어">안정 해시의 핵심 아이디어</h3>
<p>안정 해시는 “서버와 키를 같은 해시 공간에 매핑”해서 서버 추가/삭제 시 재분배되는 키의 양을 최소화한다.</p>
<p>개념 요약</p>
<ol>
<li>0 ~ 2³² (혹은 큰 해시 공간)을 원형 링(Circle)으로 만든다. </li>
<li>서버(Node)를 해시 함수로 링 위에 위치시킨다.
 예: hash(&quot;ServerA&quot;) = 12345</li>
<li>키(Key)도 같은 해시 함수를 사용해 링 위에 위치시킨다.
 예: hash(&quot;User123&quot;) = 20000</li>
<li>키는 자신보다 ‘시계 방향’으로 가장 가까운 서버에 할당된다.</li>
</ol>
<p>즉, 링 위에서 “바로 다음 서버”가 담당하는 것이다.</p>
<h3 id="서버-추가삭제-시-변화">서버 추가/삭제 시 변화</h3>
<h4 id="서버-추가">서버 추가</h4>
<ul>
<li>새 서버를 링 위의 특정 지점에 추가하면,
  <strong>그 서버가 위치한 구간의 일부 키만 재할당</strong>된다.</li>
<li>나머지 키는 그대로 유지됨.<h4 id="서버-제거">서버 제거</h4>
</li>
<li>제거된 서버가 담당하던 구간의 키들만
  “다음 서버”로 이동.</li>
</ul>
<p>즉, 전체 키의 1/N 정도만 이동하면 된다. (N = 서버 개수)</p>
<p>하지만 이 접근법에는 두 가지 문제가 존재한다.</p>
<ol>
<li>파티션의 크기를 균등하게 유지하지 못한다. (파티션: 인접한 서버 사이의 해시 공간)</li>
<li>키의 균등 분포를 달성하기 어렵다.
이 두 가지 문제를 보완하기 위한 기법이 가상 노드 또는 복제라 불리는 기법이다.</li>
</ol>
<h2 id="가상-노드">가상 노드</h2>
<h3 id="작동-원리">작동 원리</h3>
<ul>
<li><p>각 서버를 해시 링에 <strong>여러 번 등록</strong>한다.</p>
<pre><code>Server A → hash(&quot;A#1&quot;), hash(&quot;A#2&quot;), hash(&quot;A#3&quot;) …
Server B → hash(&quot;B#1&quot;), hash(&quot;B#2&quot;), hash(&quot;B#3&quot;) …</code></pre></li>
<li><p>키를 매핑할 때는 링 전체를 보고 가장 가까운 노드를 찾는다.</p>
</li>
<li><p>이렇게 하면 <strong>부하가 자연스럽게 분산</strong>된다.</p>
</li>
<li><p>보통 서버당 수백 개의 vnode를 두면 충분히 균등한 분포가 나온다고한다.</p>
</li>
</ul>
<h1 id="번외">번외</h1>
<h2 id="해시-키를-사용하는-대표적인-상황들">해시 키를 사용하는 대표적인 상황들</h2>
<h3 id="1-분산-캐시-예-redis-memcached">1. 분산 캐시 (예: Redis, Memcached)</h3>
<p>서비스에 캐시 서버가 여러 대 있을 때, 어떤 서버에 데이터를 저장할지 결정해야 함.</p>
<pre><code class="language-python">cache_server = hash(&quot;user:1234&quot;) % 4  # 총 4개의 Redis 노드</code></pre>
<p><strong>설명:</strong></p>
<ul>
<li>&quot;user:1234&quot;가 <strong>해시 키</strong></li>
<li>이 키를 해시해서 특정 Redis 노드에 매핑</li>
<li>나중에 같은 키로 조회하면 동일한 노드로 가서 데이터를 찾을 수 있음</li>
</ul>
<p><strong>정리:</strong> 캐시 일관성을 유지하고, 노드 간 데이터를 균등하게 분배하기 위해 해시 키 사용.</p>
<h3 id="2-데이터베이스-샤딩-sharding">2. 데이터베이스 샤딩 (Sharding)</h3>
<p>데이터베이스가 너무 커서 여러 DB 인스턴스로 나누고 싶을 때.</p>
<pre><code class="language-java">int shard = hash(userId) % 8; // 8개의 샤드(DB)</code></pre>
<p><strong>설명:</strong></p>
<ul>
<li>userId가 <strong>샤드 키(Shard Key)</strong></li>
<li>샤드키를 해시해서 실제 <strong>해시 키</strong>로 바꿔 어떤 샤드(DB)에 저장할지 결정.</li>
<li>해시를 쓰면 userId가 순차적이어도 데이터가 균등하게 분산됨.</li>
</ul>
<p><strong>정리:</strong> 해시 키는 “데이터가 저장될 샤드(서버)를 결정”하는 데 사용됨.</p>
<h3 id="로드-밸런싱-load-balancing">로드 밸런싱 (Load Balancing)</h3>
<p>API 서버가 여러 대일 때, 같은 사용자 요청은 항상 같은 서버로 보내고 싶을 때.</p>
<pre><code>server_index = hash(user_session_id) % 5
→ 5개의 웹 서버 중 하나 선택</code></pre><p><strong>설명:</strong></p>
<ul>
<li>user_session_id를 해시 키로 사용.</li>
<li>같은 세션의 요청은 항상 같은 서버로 가게 되어, 세션 일관성이 유지.</li>
<li><em>정리:*</em> 해시 키로 요청을 특정 서버에 고정시켜서 세션 유실을 방지.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 처리율 제한 장치 실습]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Mon, 20 Oct 2025 13:17:37 GMT</pubDate>
            <description><![CDATA[<h1 id="슬라이딩-윈도우-카운터-알고리즘">슬라이딩 윈도우 카운터 알고리즘</h1>
<p>슬라이딩 윈도우 카운터 알고리즘 = 고정 윈도 카운터 알고리즘 + 윈도 로깅 알고리즘</p>
<h3 id="고정-위도우-카운터-알고리즘">고정 위도우 카운터 알고리즘</h3>
<p>타임라인을 <strong>고정된 시간 간격</strong>의 윈도우로 나누고, 각 윈도우에서 요청 수를 센다. 카운터가 임계치에 도달하면 새 윈도우가 열릴 때까지 요청을 거부하며, 윈도우 경계에서 트래픽이 집중되면 할당량보다 더 많은 요청을 허용할 수 있다는 단점이 있다.</p>
<h3 id="윈도우-로깅-알고리즘">윈도우 로깅 알고리즘</h3>
<p>요청이 올 때마다 <strong>타임스탬프를 로그에 저장</strong>하고, 현재 시간 기준으로 만료된 타임스탬프를 제거하여 유효한 요청 수만 유지한다. 가장 정확하게 처리율을 제한하지만, 모든 요청의 타임스탬프를 저장해야 하므로 <strong>가장 많은 메모리</strong>를 사용한다.</p>
<h3 id="슬라이딩-윈도우-카운터-알고리즘-1">슬라이딩 윈도우 카운터 알고리즘</h3>
<p>고정 윈도우의 단점을 보완하기 위해, 현재 윈도우와 직전 윈도우의 카운터를 <strong>겹치는 비율</strong>에 따라 가중 평균하여 요청 수를 추정한다. 이는 윈도우 경계의 문제점을 줄이면서도 메모리 효율이 비교적 좋다.</p>
<p>고정 윈도우 카운터, 윈도우 로깅 알고리즘의 단점을 보완하고 장점을 갖춘 슬라이딩 윈도우 카운터 알고리즘으로 처리율 제한 장치를 구현해보자. (토큰 버킷 알고리즘은 실무에서 적용해봤어서 제외했다.)</p>
<h2 id="코드">코드</h2>
<pre><code class="language-python">import time
import redis

lua_script = &quot;&quot;&quot;
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

-- 오래된 항목 제거
redis.call(&#39;ZREMRANGEBYSCORE&#39;, key, 0, now - window)

-- 현재 개수 확인
local count = redis.call(&#39;ZCARD&#39;, key)

if count &lt; limit then
    redis.call(&#39;ZADD&#39;, key, now, tostring(now))
    redis.call(&#39;EXPIRE&#39;, key, window)
    return 1 -- 허용
else
    return 0 -- 거절
end
&quot;&quot;&quot;

def allow_request(rate_limiter, user_id):
    key = f&quot;rate_limit:{user_id}&quot;
    now = time.time()
    allowed = rate_limiter(keys=[key], args=[now, 10, 5]) # 윈도우 10초, 최대 5회
    return allowed == 1

if __name__ == &quot;__main__&quot;:
    # Redis 연결
    r = redis.StrictRedis(host=&#39;localhost&#39;, port=6379, db=0)
    rate_limiter = r.register_script(lua_script)

    user = &quot;user123&quot;
    for i in range(20):
        allowed= allow_request(rate_limiter, user)
        print(f&quot;Request {i+1}: {&#39;✅ allowed&#39; if allowed else &#39;❌ blocked&#39;}&quot;)
        time.sleep(1)</code></pre>
<h3 id="코드-동작-방식">코드 동작 방식</h3>
<ol>
<li>local key = KEYS[1] → Python에서 전달받은 첫 번째 키(rate_limit:user123)를 로컬 변수 key에 저장한다.</li>
<li>local now = tonumber(ARGV[1]) → 현재 시각(초 단위, float)을 숫자로 변환해 now 변수에 저장한다.</li>
<li>local window = tonumber(ARGV[2]) → 제한할 윈도우 크기(예: 10초)를 숫자로 변환해 window 변수에 저장한다.</li>
<li>local limit = tonumber(ARGV[3]) → 허용 가능한 최대 요청 수(예: 5)를 숫자로 변환해 limit 변수에 저장한다.</li>
<li>redis.call(&#39;ZREMRANGEBYSCORE&#39;, key, 0, now - window) → window 시간보다 오래된 요청 기록을 모두 삭제한다.</li>
<li>local count = redis.call(&#39;ZCARD&#39;, key) → 현재 윈도우 내에 남아있는 요청 수를 가져와 count 변수에 저장한다.</li>
<li>if count &lt; limit then → 현재 요청 수가 제한값보다 작으면 허용할지 여부를 판단하기 시작한다.</li>
<li>redis.call(&#39;ZADD&#39;, key, now, tostring(now)) → 허용된 요청의 타임스탬프를 ZSET에 추가한다.</li>
<li>redis.call(&#39;EXPIRE&#39;, key, window) → 키(key)에 TTL을 설정하여 윈도우 시간 후 자동 삭제되도록 한다.</li>
<li>return 1 -- 허용 → 요청이 허용되었음을 의미하는 1을 반환한다.</li>
<li>else → return 0 -- 거절 → 요청이 거절되었음을 의미하는 0을 반환한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/dev-gromit/post/24cab0c8-b7da-4894-8726-204a0c211dce/image.png" alt=""></p>
<p>실행하면 위와 같이 결과가 출력된다. 이제 분산환경에서도 위 코드가 잘 동작하는지 확인해보자.</p>
<h1 id="분산환경에서의-처리율-제한-장치-설계">분산환경에서의 처리율 제한 장치 설계</h1>
<h2 id="아키텍처">아키텍처</h2>
<p>Flask(Python Web Framework) 에서는 사용자 ID 기반으로 처리율 제한을 한다. 분산환경에서 처리 가능하도록 카운터 저장소는 Redis를 사용한다. Nginx는 클라이언트 요청을 받아서 3개의 웹서버에 요청을 분산하는 로드 밸런서 역할을 한다.</p>
<p><img src="https://velog.velcdn.com/images/dev-gromit/post/5d198033-abde-449d-a935-0fd209254d9d/image.png" alt=""></p>
<h2 id="코드-1">코드</h2>
<pre><code class="language-python">import redis
import time
from flask import Flask, request, jsonify

app = Flask(__name__)

# 전역 변수로 프로세스 이름 저장
PROCESS_NAME = None

# Redis 연결 (모든 프로세스가 같은 Redis 사용)
redis_client = redis.Redis(host=&#39;localhost&#39;, port=6379, db=0)

# rate limit 설정
MAX_REQUESTS = 10       # 허용 요청 수
WINDOW_SIZE = 60       # 초 단위

lua_script = &quot;&quot;&quot;
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

-- 오래된 항목 제거
redis.call(&#39;ZREMRANGEBYSCORE&#39;, key, 0, now - window)

-- 현재 개수 확인
local count = redis.call(&#39;ZCARD&#39;, key)

if count &lt; limit then
    redis.call(&#39;ZADD&#39;, key, now, tostring(now))
    redis.call(&#39;EXPIRE&#39;, key, window)
    return 1 -- 허용
else
    return 0 -- 거절
end
&quot;&quot;&quot;

rate_limiter = redis_client.register_script(lua_script)

def is_request_allowed(user_id):
    key = f&quot;rate_limit:{user_id}&quot;
    now = time.time()
    allowed = rate_limiter(keys=[key], args=[now, WINDOW_SIZE, MAX_REQUESTS])  
    return allowed == 1

@app.route(&quot;/&quot;)
def index():
    user_id = request.args.get(&quot;userId&quot;)
    if not user_id:
        return jsonify({&quot;error&quot;: &quot;userId query parameter is required&quot;}), 400

    allowed = is_request_allowed(user_id)

    if allowed:
        return jsonify({
            &quot;status&quot;: &quot;ok&quot;,
            &quot;userId&quot;: user_id,
            &quot;message&quot;: &quot;Request accepted&quot;,
            &quot;processName&quot;: PROCESS_NAME
        }), 200
    else:
        return jsonify({
            &quot;status&quot;: &quot;rate_limited&quot;,
            &quot;userId&quot;: user_id,
            &quot;message&quot;: &quot;Too many requests, try again later&quot;,
            &quot;processName&quot;: PROCESS_NAME
        }), 429


if __name__ == &quot;__main__&quot;:
    import sys
    if len(sys.argv) &lt; 2:
        print(&quot;Usage: python rate_limiter.py &lt;port&gt;&quot;)
        exit(1)

    port = int(sys.argv[1])
    PROCESS_NAME = &quot;myapp_&quot; + str(port)
    app.run(host=&quot;127.0.0.1&quot;, port=port)</code></pre>
<h2 id="nginx-설정">NginX 설정</h2>
<pre><code>upstream flask_servers {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name {domain};   # 서버 공인 IP 또는 도메인

    location / {
        proxy_pass http://flask_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}</code></pre><h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/dev-gromit/post/8cb7a621-fa0a-48d6-87f2-501d6155b841/image.gif" alt=""></p>
<h2 id="부하-테스트">부하 테스트</h2>
<p>부하 테스트에도 문제가 없는지 확인해보기 위해 60초 동안 1,000건이 허용되게 하여 jmeter로 테스트 해보았다.
1,001건을 요청하니 마지막 1건이 실패하는 것을 확인했다.</p>
<p><img src="https://velog.velcdn.com/images/dev-gromit/post/be3067f6-82ad-4704-ae49-1a4a027cd25f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 처리율 제한 장치의 설계]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98%EC%9D%98-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98%EC%9D%98-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Thu, 09 Oct 2025 06:40:35 GMT</pubDate>
            <description><![CDATA[<h1 id="처리율-제한-장치">처리율 제한 장치</h1>
<p>네트워크 시스템에서 처리율 제한 장치는 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치이다.</p>
<p>처리율 제한 장치가 필요한 이유</p>
<ul>
<li>DoS 공격에 의한 자원 고갈을 방지할 수 있다.</li>
<li>비용을 절감한다. 비용이 큰 API의 호출을 제한함으로써 비용을 절감할 수 있다.</li>
<li>서버 과부하를 막는다.</li>
</ul>
<h2 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h2>
<p>면접관과의 소통을 통해 알아낼 수 있는 내용은 다음과 같다.</p>
<ol>
<li>클라이언트측 제한 장치인지, 서버측 제한 장치인지</li>
<li>IP주소를 기준으로 제한할지, 사용자 ID 기준으로 제한할지</li>
<li>시스템 규모는 어느 정도인지</li>
<li>분산 환경에서 동작해야 하는지</li>
<li>처리율 제한 장치는 독립된 서비스인지, 애플리케이션 코드에 포함되는지</li>
</ol>
<h2 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계 개략적 설계안 제시 및 동의 구하기</h2>
<h3 id="처리율-제한-장치를-어디에-둘-것인가">처리율 제한 장치를 어디에 둘 것인가</h3>
<h4 id="클라이언트측">클라이언트측</h4>
<p>클라이언트측에 위치하면 얼마든지 위변조 당할 수 있기 때문에 클라이언트측은 좋지 않다.</p>
<h4 id="애플리케이션에서-처리">애플리케이션에서 처리</h4>
<p>서버측에서는 애플리케이션에서 각각 사용량 제한을 처리 할 수 있다. 이러한 경우에는 애플리케이션에서 각각 처리하므로 사용량 제한에 필요한 데이터를 Redis와 같은 데이터 저장소에 저장해야 한다.</p>
<h4 id="api-게이트웨이에서-처리">API 게이트웨이에서 처리</h4>
<p>MSA의 경우, 처리율 제한 정치는 보통API 게이트웨이라 불리는 컴포넌트에 구현된다. API 게이트웨이는 다음과 같은 기능을 제공하는 서비스다.</p>
<ul>
<li>처리율 제한</li>
<li>SSL 종단</li>
<li>사용자 인증</li>
<li>IP 허용 목록 관리
API 게이트웨이는 보통 클라우드 업체가 유지 보수를 담당한다.</li>
</ul>
<h2 id="처리율-제한-알고리즘">처리율 제한 알고리즘</h2>
<p>처리율 제한 알고리즘은 다음과 같다. 알고리즘에 대해서는 따로 정리하지 않는다.</p>
<ul>
<li>토큰 버킷</li>
<li>누출 버킷</li>
<li>고정 윈도 카운터</li>
<li>이동 윈도 로그</li>
<li>이동 윈도 카운터</li>
</ul>
<h2 id="개략적인-아키턱처">개략적인 아키턱처</h2>
<p>얼마나 많은 요청이 접수되었는지를 추적할 수 있는 카운터를 추적 대상별로 두고(사용자별로 추적 or IP 등), 이 카운터의 값이 어떤 한도를 넘어서면 한도를 넘어 도착한 요청은 거부하는 것이다.</p>
<p>처리율 제한 처리는 API 게이트웨이와 같은 미들웨어에서 하고, 카운터는 레디스에서 관리한다.</p>
<p>레디스를 사용하는 경우에 동시성 문제가 있을 수 있다. 동시성 문제는 레디스의 원자적 연산인 INCR, EXPIRE를 사용하면 해결 가능하다.</p>
<ul>
<li>INCR: 메모리에 저장된 카운터의 값을 1만큼 증가시킨다.</li>
<li>EXPIRE: 카운터에 타임아웃 값을 설정한다. 설정된 시간이 지나면 카운터는 자동으로 삭제된다.</li>
</ul>
<pre><code class="language-java">@Service
public class RateLimiterService {
    private final StringRedisTemplate redisTemplate;

    public boolean isAllowed(String userId, int limit, int windowSeconds) {
        String key = &quot;rate:&quot; + userId;
        Long count = redisTemplate.opsForValue().increment(key);

        // 메모리에 키가 없었으면 1을 반환함. 따라서 count가 1이면 ttl 설정 필요.
        if (count == 1) {
            redisTemplate.expire(key, Duration.ofSeconds(windowSeconds));
        }
        return count &lt;= limit;
    }
}</code></pre>
<h2 id="3단계-상세-설계">3단계 상세 설계</h2>
<h3 id="처리율-한도-초과-트래픽의-처리">처리율 한도 초과 트래픽의 처리</h3>
<p>어떤 요청이 한도 제한에 걸리면 API는 HTTP 429 응답을 클라이언트에게 보낸다. 경우에 따라서는 한도 제한에 걸린 메시지를 나중에 처리하기 위해 큐에 보관할 수 있다.</p>
<h3 id="분산-환경에서의-처리율-제한-장치의-구현">분산 환경에서의 처리율 제한 장치의 구현</h3>
<p>분산 환경에서 처리율 제한 장치를 구현할 때는 경쟁 조건과 동기화 이슈를 고려해야 한다.</p>
<p><strong>경쟁 조건 해결 방법</strong></p>
<ul>
<li>INCR, EXPIRE 사용</li>
<li>redis의 루아 스크립트 활용</li>
</ul>
<p>루아 스크립트를 활용하는 예시 코드는 다음과 같다.</p>
<pre><code class="language-java">String script = &quot;&quot;&quot;
    local key = KEYS[1]
    local limit = tonumber(ARGV[1])
    local ttl = tonumber(ARGV[2])
    local count = redis.call(&#39;INCR&#39;, key)
    if count == 1 then
        redis.call(&#39;EXPIRE&#39;, key, ttl)
    end
    return count &lt;= limit
&quot;&quot;&quot;;
Boolean allowed = redisTemplate.execute(
    new DefaultRedisScript&lt;&gt;(script, Boolean.class),
    Collections.singletonList(key),
    limit, windowSeconds
);</code></pre>
<p><strong>동기화 이슈 해결 방법</strong>
처리율 제한 장치를 여러 개 사용해야 하는 경우를 대비하여 카운터는 처리율 조절 장치에 관리하지 않고, 레디스와 같은 중앙 집중형 데이터 저장소를 사용해야 한다.</p>
<h3 id="성능-최적화">성능 최적화</h3>
<ul>
<li>처리율 제한 장치는 여러 데이터 센터를 지원해야 한다.<ul>
<li>여러 데이터 센터를 지원하지 않으면 데이터 센터에서 멀리 떨어진 곳에서는 속도가 현저히 느려진다.</li>
</ul>
</li>
<li>제한 장치 간에 데이터를 동기화할 때 최종 일관성 모델을 사용해야 한다.<ul>
<li>최종 일관성 모델: 분산 환경에서 가용성을 높이기 위해 즉각적 일관성을 희생하지만, 결국 모든 노드가 동일한 상태로 수렴하도록 보장하는 모델 (예: Redis Cluster)</li>
</ul>
</li>
</ul>
<h3 id="모니터링">모니터링</h3>
<p>처리율 제한 장치가 효과적으로 동작하고 있는지 보기 위해 데이터를 모아야 한다. 모니터링을 통해 확인하려는 것은 다음 두가지다.</p>
<ul>
<li>채택된 처리율 제한 알고리즘이 효과적이다.</li>
<li>정의한 처리율 제한 규칙이 효과적이다.
예를 들어 처리율 제한 규칙이 너무 빡빡하게 설정되었다면 많은 유효 요청이 처리되지 못하고 버려질 것이다. 그런 일이 벌어진다면 규칙을 다소 완화할 필요가 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책] 개발자의 글쓰기 - 김철수]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B9%80%EC%B2%A0%EC%88%98</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B9%80%EC%B2%A0%EC%88%98</guid>
            <pubDate>Wed, 08 Oct 2025 06:12:13 GMT</pubDate>
            <description><![CDATA[<h1 id="문장과-단락을-구조화하는-법">문장과 단락을 구조화하는 법</h1>
<h2 id="서술식-개조식-도식">서술식, 개조식, 도식</h2>
<h3 id="서술식">서술식</h3>
<ul>
<li>&#39;~다.&#39;로 끝나는 완전한 문장으로 구성된 글을 말한다.</li>
<li>개발 가이드 문서를 보통 서술식으로 작성한다.<h3 id="개조식">개조식</h3>
</li>
<li>종결 어미(예: ~다) 대신 명사(예: 완료, 증대 등)나 용언의 명사형(예: ~했음)으로 끝내는 것을 말한다.</li>
<li>신문의 헤드라인을 쓰거나 어떤 사항을 나열할 때 사용한다.<h3 id="도식">도식</h3>
</li>
<li>도식은 사물의 구조나 관계, 상태를 그림이나 서식으로 보여주는 것이다.<h2 id="글쓰기-방법-별-예시">글쓰기 방법 별 예시</h2>
<h3 id="서술식-1">서술식</h3>
<blockquote>
<p>코드 리뷰는 개발 품질을 높이기 위한 중요한 과정이다. 리뷰를 통해 개발자는 자신의 코드를 객관적으로 점검할 수 있다. 또한, 팀원 간의 지식 공유와 코드 일관성 유지에도 도움이 된다. 리뷰 문화가 잘 정착된 팀은 버그 발생률이 낮고, 유지보수 효율이 높다.</p>
</blockquote>
</li>
</ul>
<h3 id="개조식-1">개조식</h3>
<blockquote>
<ul>
<li>코드 품질 향상</li>
</ul>
</blockquote>
<ul>
<li>코드에 대한 객관적 점검 가능</li>
<li>팀 내 지식 공유 및 일관성 유지</li>
<li>버그 감소, 유지보수 효율 증대</li>
</ul>
<h3 id="대조식">대조식</h3>
<table>
<thead>
<tr>
<th><strong>목적</strong></th>
<th><strong>주요 효과</strong></th>
<th><strong>부수 효과</strong></th>
</tr>
</thead>
<tbody><tr>
<td>코드 리뷰</td>
<td>코드 품질 향상</td>
<td>버그 감소</td>
</tr>
<tr>
<td>코드 리뷰</td>
<td>지식 공유</td>
<td>팀 일관성 유지</td>
</tr>
<tr>
<td>코드 리뷰</td>
<td>유지보수 효율 증대</td>
<td>개발 문화 성숙</td>
</tr>
</tbody></table>
<h1 id="좋은-이름의-기준-smart">좋은 이름의 기준, SMART</h1>
<p>패키지, 클래스, 모듈, 함수, 변수를 망라해 좋은 이름인지를 확인하는 5가지 기준을 SMART로 정했다.</p>
<ul>
<li>easy to Search: 검색하기 쉽고</li>
<li>easy to Mix: 조합하기 쉽고</li>
<li>easy to Agree: 수긍하기 쉽고</li>
<li>easy to Remember: 기억하기 쉽고</li>
<li>easy to Type: 입력하기 쉽고</li>
</ul>
<h2 id="검색하기-쉽게-이름-짓는-방법">검색하기 쉽게 이름 짓는 방법</h2>
<p>요즘에는 IDE가 잘 되어있어서 필요한 클래스, 함수, 변수를 검색을 통해 찾는다. 그렇기 때문에 검색하기 쉽게 이름 짓는 것이 중요하다. 검색하기 쉽게 이름 짓는 방법은 다음과 같다.</p>
<ul>
<li>고전적 범주화를 이용해 한 단계 상위 범주의 이름을 태그처럼 붙인다.</li>
<li>에러 메시지를 저장할 상수 이름을 짓는다고 하면 다음과 같이 짓는다.<ul>
<li>ERROR_SERVER_TIMEOUT</li>
<li>ERROR_NO_RESULT</li>
</ul>
</li>
<li>사용자를 구별할 때는 검색하기 쉽도록 user를 붙인다.<ul>
<li>userPayer</li>
<li>userBuyer</li>
</ul>
</li>
<li>여기서 주의할 점은 같은 접두어를 가진 함수나 변수의 개수가 너무 많으면 안 붙이는 것만 못하다는 점이다.</li>
</ul>
<h1 id="사용자-에러-메시지를-제대로-쓰는-법">사용자 에러 메시지를 제대로 쓰는 법</h1>
<h2 id="사용자-에러에-대처하는-메시지">사용자 에러에 대처하는 메시지</h2>
<p>회원가입을 하는데 아래와 같은 문구만 뜨면 뭘 어떻게 해야 할 지 알 수 없다.</p>
<blockquote>
<p>회원 가입을 진행할 수 없습니다.</p>
</blockquote>
<p>오류의 내용과 오류의 원인을 함께 알려줘야 사용자가 대처할 수 있다.</p>
<blockquote>
<p>휴대폰 번호를 잘못 입력하셔서 회원가입을 진행할 수 없습니다.</p>
</blockquote>
<p>에러를 해결할 방법을 사용자에게 정확히 알려주면 더 좋다.</p>
<blockquote>
<p>휴대폰 번호를 잘못 입력하셔서 회원가입을 진행할 수 없습니다. 휴대폰 번호 입력란에는 숫자만 입력하십시오.</p>
</blockquote>
<h2 id="에러-메시지-작성-방법">에러 메시지 작성 방법</h2>
<ul>
<li>에러 내용: 오류로 인한 문제와 종류</li>
<li>에러의 원인: 오류를 발생시킨 직접적이고 근본적인 원인</li>
<li>에러 해결 방법: 사용자가 오류를 해결할 가장 쉽고 빠른 방법</li>
</ul>
<h1 id="독자-관점에서-릴리스-문서와-장애-보고서-쓰기">독자 관점에서 릴리스 문서와 장애 보고서 쓰기</h1>
<h2 id="고객에게-유용한-정보를-쓰자">고객에게 유용한 정보를 쓰자</h2>
<p>체인지 로그는 개발자가 변경한 내용을 적는 것이다. 체인지 로그를 보는 독자는 개발자가 변경한 것이 궁금한게 아니라 뭔가 새로운 것, 바뀐 것, 그래서 자기에게 좋거나 유익한 정보들을 알고 싶어 한다.</p>
<p>다음과 같은 체인지 로그가 있다고 해보자.</p>
<blockquote>
<p>댓글에 애니메이션 스티커 때문에 화면이 멈추는 문제를 해결했습니다.</p>
</blockquote>
<p>이 체인지 로그는 개발자 관점에서 작성되었다. 이를 고객의 관점으로 바꿔서 작성해보자.</p>
<ul>
<li>개발자의 문제: 화면이 멈춘 것</li>
<li>고객의 문제: 애니메이션 스티커를 댓글에 사용할 수 없는 것</li>
</ul>
<ul>
<li>개발자의 문제 해결: 화면이 멈추지 않게 됐다.</li>
<li>고객의 문제 해결: 애니메이션 스티커를 댓글에 사용할 수 있다.</li>
</ul>
<blockquote>
<p>이제 애니메이션 스티커를 정상적으로 댓글에 사용할 수 있습니다. 댓글에서 애니메이션 스티커 때문에 화면이 멈추는 문제를 해결했습니다.</p>
</blockquote>
<p>이제 개발자의 문제는 짧게 줄인다.</p>
<blockquote>
<p>이제 애니매이션 스티커를 정상적으로 댓글에 사용할 수 있습니다. (화면 멈춤 문제 해결)</p>
</blockquote>
<p>개조식으로 바꾼다.</p>
<blockquote>
<p>애니메이션 스티커를 댓글에 정상 사용 가능 (화면 멈춤 문제 해결)</p>
</blockquote>
<h2 id="릴리스-문서는-문제-해결-보고서처럼-쓰자">릴리스 문서는 문제 해결 보고서처럼 쓰자</h2>
<h3 id="문제-문제점-해결책-후속-계획-순으로-적자">문제, 문제점, 해결책, 후속 계획 순으로 적자</h3>
<p>릴리스 문서는 결국 개발자가 문제점 하나를 선택해서 해결한 결과다. 따라서 여러 문제점 중에 어떤 문제점을 선택했는지를 독자에게 정확히 알려줘야 한다.</p>
<p>릴리스 문서 초안 예시는 다음과 같다.</p>
<ul>
<li>문제: 사용자가 급증하면 서버가 정지</li>
<li>문제점: 잘못된 시스템 설정, 프로그램 비 최적화, 잘못된 DB 설계</li>
<li>해결책: 시스템 설정 변경</li>
<li>후속 계획: 프로그램 최적화, DB 재설계</li>
</ul>
<h2 id="비즈니스를-이해하는-장애-보고서-쓰기">비즈니스를 이해하는 장애 보고서 쓰기</h2>
<h3 id="장애-보고서의-특징">장애 보고서의 특징</h3>
<ol>
<li>장애 보고서는 개발자가 원할 때 쓸 수 없다.</li>
<li>장애의 1차 원인은 대부분 다른 원인의 결과다.</li>
<li>장애 보고를 받는 윗사람은 대부분 개발자가 아니다.</li>
<li>장애를 해결했다고 해서 100% 해결한 것은 아니다.</li>
</ol>
<h3 id="장애-보고서를-쓸-때-사용해야-하는-글쓰기-기법">장애 보고서를 쓸 때 사용해야 하는 글쓰기 기법</h3>
<ol>
<li>질문에 대답하는 신속한 글쓰기: 장애에 대한 대화를 글로 옮기는 방법. 장애에 대해서 나눈 대화를 글로 옮긴다.</li>
<li>원인과 이유를 찾는 분석적 글쓰기: 5Whys 기법을 활용하여 문제의 원인이 되는 인과 관계를 탐색한다.</li>
<li>상사를 고려하는 비즈니스 관점의 글쓰기: 장애로 인한 손실 금액을 측정한다.</li>
<li>원하는 것을 얻는 정치적 글쓰기: 장애 재발생 확률을 고려하여 %로 기준을 정해 보고한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[강의] 대규모 시스템 설계 내용 정리 (4)]]></title>
            <link>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-4</link>
            <guid>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-4</guid>
            <pubDate>Tue, 07 Oct 2025 07:46:19 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🗓️-0929-월"><strong>🗓️ 09/29 (월)</strong></h2>
<h3 id="📌-게시판-서비스의-특성"><strong>📌 게시판 서비스의 특성</strong></h3>
<ul>
<li><p><strong>읽기 트래픽</strong>이 <strong>쓰기 트래픽보다 압도적으로 많음</strong></p>
</li>
<li><p>단순히 게시글 데이터만이 아니라, <strong>좋아요 수 / 댓글 수 / 조회 수 등 여러 데이터</strong>를 함께 보여줘야 함.</p>
</li>
</ul>
<h3 id="⚙️-문제점"><strong>⚙️ 문제점</strong></h3>
<ul>
<li><p>데이터가 분산되어 있는 환경에서 <strong>Client가 여러 서비스를 거쳐야 함</strong></p>
</li>
<li><p>ArticleService는 게시글을 읽기 위해 여러 서비스(댓글, 좋아요, 조회수 등)를 의존함.</p>
</li>
<li><p>반대로 각 서비스들도 데이터 유효성을 위해 ArticleService를 의존 → <strong>양방향 의존</strong> 발생.</p>
</li>
</ul>
<h3 id="💡-해결-방안-article-read-service"><strong>💡 해결 방안: Article Read Service</strong></h3>
<ul>
<li><p>읽기 전용 서비스를 분리하여 <strong>단방향 의존</strong>으로 개선.</p>
</li>
<li><p>그러나 여전히 다음 문제들이 남음:</p>
<ul>
<li><p>여러 서비스/DB에 분산된 데이터를 조합해야 함.</p>
</li>
<li><p>네트워크 비용 증가.</p>
</li>
<li><p>서비스 간 부하 전파.</p>
</li>
<li><p>조인/질의 비용 증가.</p>
</li>
</ul>
</li>
</ul>
<p>👉 이러한 문제는 <strong>CQRS 패턴</strong>으로 해결 가능.</p>
<hr>
<h3 id="⚙️-cqrs-command-query-responsibility-segregation"><strong>⚙️ CQRS (Command Query Responsibility Segregation)</strong></h3>
<ul>
<li><p><strong>명령(Command)</strong> 과 <strong>조회(Query)</strong> 책임을 분리하는 패턴.</p>
</li>
<li><p>데이터의 <strong>변경과 조회를 다른 경로로 수행</strong>한다.</p>
</li>
</ul>
<h4 id="📐-cqrs-적용-설계"><strong>📐 CQRS 적용 설계</strong></h4>
<ol>
<li><p><strong>Command 서버</strong>: CUD(Create, Update, Delete) 담당</p>
<p> <strong>Query 서버</strong>: R(Read) 담당 (별도의 서버로 분리)</p>
</li>
<li><p>Query 서버가 Command 서버에 직접 질의하면 부하 전파 →</p>
<p> <strong>Query 서버 전용 DB를 구축</strong></p>
</li>
<li><p>Query DB 데이터 동기화는 <strong>Message Broker(Kafka)</strong> 활용.</p>
<ul>
<li>이미 메시지 브로커 인프라가 존재하므로, <strong>Consumer만 추가</strong>하면 됨.</li>
</ul>
</li>
</ol>
<ol start="4">
<li><p>Query Model은 <strong>Command Model과 동일할 필요 없음</strong></p>
<ul>
<li>조회 최적화를 위해 <strong>비정규화된 데이터 모델</strong> 사용 가능.</li>
</ul>
</li>
</ol>
<ol start="5">
<li><p><strong>DB는 Redis (In-memory DB)</strong> 사용</p>
<ul>
<li><p>최신글 위주로 조회됨 → TTL=24시간 설정</p>
</li>
<li><p>24시간 이전 글은 Command 서버에서 직접 조회 (트래픽 부담 낮음)</p>
</li>
</ul>
</li>
</ol>
<h4 id="🧩-조회수-데이터-비정규화-제외-이유"><strong>🧩 조회수 데이터 비정규화 제외 이유</strong></h4>
<ul>
<li><p>조회수는 조회 트래픽에 비례해 <strong>변동이 매우 잦음</strong></p>
</li>
<li><p>변경마다 Query Model을 갱신하면 비효율적</p>
</li>
<li><p>이미 <strong>조회수 서비스</strong>에서 Redis로 관리 중</p>
</li>
<li><p>따라서 조회수는 <strong>조회수 서비스에 직접 요청</strong></p>
<ul>
<li>단, <strong>짧은 TTL 캐싱(Caffeine 등 인메모리 캐시)</strong> 으로 부하 완화</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🗓️-1003-금"><strong>🗓️ 10/03 (금)</strong></h2>
<h3 id="📊-게시글-목록-조회-최적화-전략"><strong>📊 게시글 목록 조회 최적화 전략</strong></h3>
<h4 id="🎯-목표"><strong>🎯 목표</strong></h4>
<ul>
<li>게시글 서비스의 DB 부하를 줄이면서, <strong>조회 성능을 극대화</strong></li>
</ul>
<h4 id="🧠-기본-접근-cacheable-캐싱"><strong>🧠 기본 접근: @Cacheable 캐싱</strong></h4>
<ul>
<li><p>게시글 작성/삭제 시 캐시 만료 필요 → <strong>캐시 만료가 잦고 히트율 저하</strong></p>
</li>
<li><p>만료 시간을 늘리면 최신 데이터 반영 어려움 → <strong>데이터 불일치 발생</strong></p>
</li>
<li><p>즉, 단순 캐시로는 해결 불가능.</p>
</li>
</ul>
<hr>
<h3 id="🔥-개선-접근-게시판-사용-패턴-기반-캐시-전략"><strong>🔥 개선 접근: 게시판 사용 패턴 기반 캐시 전략</strong></h3>
<ul>
<li><p><strong>Hot Data</strong>: 자주 조회되는 최신 글</p>
</li>
<li><p><strong>Cold Data</strong>: 거의 조회되지 않는 과거 글</p>
<p>  → Hot Data에만 캐시 적용해도 충분히 효율적.</p>
</li>
</ul>
<hr>
<h3 id="⚙️-설계-redis-기반-최신글-캐싱"><strong>⚙️ 설계: Redis 기반 최신글 캐싱</strong></h3>
<ol>
<li><p><strong>게시글 조회 서비스</strong>는 Kafka로부터 게시글 생성/삭제 이벤트 수신.</p>
</li>
<li><p>이벤트 수신 시, <strong>Redis Sorted Set</strong> 에 게시판별 게시글 목록 저장.</p>
<ul>
<li><p>정렬 기준: 생성 시각 (최신순)</p>
</li>
<li><p>최대 유지 개수: <strong>1000건</strong></p>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>클라이언트가 목록 조회 시:</p>
<ul>
<li><p>최신 1000건 이내 데이터 → <strong>Redis에서 즉시 응답</strong></p>
</li>
<li><p>그 외의 과거 데이터 → <strong>게시글 서비스(DB)에서 조회</strong></p>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="🧭-핵심-요약"><strong>🧭 핵심 요약</strong></h3>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>내용</strong></th>
</tr>
</thead>
<tbody><tr>
<td>읽기 트래픽 최적화</td>
<td>CQRS로 Command/Query 서버 분리</td>
</tr>
<tr>
<td>데이터 분산 문제</td>
<td>메시지 브로커 기반으로 비동기 데이터 동기화</td>
</tr>
<tr>
<td>조회 성능 개선</td>
<td>Redis 캐시 + Sorted Set 구조</td>
</tr>
<tr>
<td>조회수 처리</td>
<td>별도 서비스 요청 + 인메모리 캐시 적용</td>
</tr>
<tr>
<td>캐시 전략</td>
<td>최신글(Hot Data)만 캐싱, 과거글은 DB 직접 조회</td>
</tr>
</tbody></table>
<hr>
<p>메모장에 정리한 내용 GPT로 재정리함
<img src="https://velog.velcdn.com/images/dev-gromit/post/f1e49fe4-03f9-4e98-bd34-1f462a391245/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[강의] 대규모 시스템 설계 내용 정리 (3)]]></title>
            <link>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-3</link>
            <guid>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-3</guid>
            <pubDate>Tue, 07 Oct 2025 07:43:36 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="⚙️-대규모-시스템-설계-강의-정리-섹션-45"><strong>⚙️ 대규모 시스템 설계 강의 정리 (섹션 4~5)</strong></h1>
<blockquote>
<p>인프런 강의 <strong>섹션 4~5: 어뷰징 방지, Kafka 주요 개념, 인기글 시스템, Transactional Messaging</strong> 정리</p>
</blockquote>
<blockquote>
<p>기간: 9월 2일 ~ 9월 10일</p>
</blockquote>
<hr>
<h2 id="📅-9월-2일-화--조회수-어뷰징-방지-설계"><strong>📅 9월 2일 (화) — 조회수 어뷰징 방지 설계</strong></h2>
<h3 id="🔹-왜-어뷰징-방지가-필요한가"><strong>🔹 왜 어뷰징 방지가 필요한가</strong></h3>
<p>조회수는 단순한 숫자처럼 보여도 서비스 신뢰도와 직결되는 중요한 데이터다.</p>
<p>하지만 <strong>특정 사용자가 반복적으로 조회 요청을 보내면 조회수가 비정상적으로 증가</strong>할 수 있다.</p>
<p>→ 즉, <strong>조회수 조작(어뷰징)</strong>을 방지해야 한다.</p>
<hr>
<h3 id="🔹-사용자-식별-방법"><strong>🔹 사용자 식별 방법</strong></h3>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>식별 기준</strong></th>
</tr>
</thead>
<tbody><tr>
<td>로그인 사용자</td>
<td>사용자 ID</td>
</tr>
<tr>
<td>비로그인 사용자</td>
<td>IP, USER-AGENT, 쿠키, 토큰 등</td>
</tr>
</tbody></table>
<p>이 정보를 조합해 사용자를 임시로 식별할 수 있다.</p>
<hr>
<h3 id="🔹-redis를-활용한-어뷰징-방지-정책"><strong>🔹 Redis를 활용한 어뷰징 방지 정책</strong></h3>
<p>Redis는 TTL(만료 시간)을 설정할 수 있고, <strong>원자적 명령어 제공(setIfAbsent)</strong> 덕분에</p>
<p>조회수 증가 요청을 “락(lock)”처럼 관리할 수 있다.</p>
<h4 id="✅-정책-설계"><strong>✅ 정책 설계</strong></h4>
<ol>
<li>사용자가 특정 게시글을 조회할 때 Redis에 다음 구조로 데이터를 저장</li>
</ol>
<pre><code>key: view:{articleId}:{userIdentifier}
value: 1
TTL: 10분</code></pre><ol>
<li></li>
<li><p>이미 동일 키가 존재하면 조회수 증가를 무시한다.</p>
<ul>
<li><p>Redis 명령어: SET key value NX EX 600</p>
<p>  (NX → 존재하지 않을 때만 저장, EX → TTL 600초)</p>
</li>
</ul>
</li>
</ol>
<hr>
<h3 id="🔹-redis를-이용한-분산-락"><strong>🔹 Redis를 이용한 분산 락</strong></h3>
<ul>
<li><p>조회수 서비스는 여러 서버 애플리케이션으로 구성된 <strong>분산 환경</strong>에서 동작한다.</p>
</li>
<li><p>Redis를 중앙 저장소로 사용하면 <strong>여러 서버 간에도 중복 조회 제어 가능</strong></p>
</li>
<li><p>즉, “한 사용자-게시글 조합에 대한 조회”를 전역 락처럼 관리할 수 있다.</p>
</li>
</ul>
<p>📘 정리하면:</p>
<blockquote>
<p>Redis TTL + setIfAbsent = 분산 환경에서의 어뷰징 방지 + 간단한 분산 락 구현</p>
</blockquote>
<hr>
<h2 id="📅-9월-10일-수--kafka를-활용한-스트림-처리-및-인기글-시스템-설계"><strong>📅 9월 10일 (수) — Kafka를 활용한 스트림 처리 및 인기글 시스템 설계</strong></h2>
<hr>
<h3 id="🧩-kafka-주요-개념-정리"><strong>🧩 Kafka 주요 개념 정리</strong></h3>
<table>
<thead>
<tr>
<th><strong>개념</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Producer</strong></td>
<td>Kafka로 데이터를 전송하는 클라이언트. 데이터를 “생산”함</td>
</tr>
<tr>
<td><strong>Consumer</strong></td>
<td>Kafka에서 데이터를 구독해 읽는 클라이언트. 데이터를 “소비”함</td>
</tr>
<tr>
<td><strong>Broker</strong></td>
<td>Kafka에서 Producer와 Consumer 사이의 데이터를 중개하는 서버 단위</td>
</tr>
<tr>
<td><strong>Kafka Cluster</strong></td>
<td>여러 Broker로 구성된 분산형 시스템. 고성능, 고가용성, 복제, 장애 복구 지원</td>
</tr>
<tr>
<td><strong>Topic</strong></td>
<td>데이터가 구분되는 논리 단위. (ex. “article-view-events”)</td>
</tr>
<tr>
<td><strong>Partition</strong></td>
<td>Topic이 분산되는 단위. 병렬 처리 가능하지만 파티션 간 순서는 보장되지 않음</td>
</tr>
<tr>
<td><strong>Offset</strong></td>
<td>각 파티션 내 데이터의 고유 위치(시퀀스)</td>
</tr>
<tr>
<td><strong>Consumer Group</strong></td>
<td>여러 Consumer를 하나의 그룹으로 묶어 병렬 처리 및 오프셋 관리 수행</td>
</tr>
</tbody></table>
<hr>
<h3 id="💡-consumer-group-개념-예시"><strong>💡 Consumer Group 개념 예시</strong></h3>
<table>
<thead>
<tr>
<th><strong>그룹명</strong></th>
<th><strong>목적</strong></th>
</tr>
</thead>
<tbody><tr>
<td>popular-articles-group</td>
<td>인기글 점수 계산</td>
</tr>
<tr>
<td>view-optimizer-group</td>
<td>조회수 캐시 최적화</td>
</tr>
</tbody></table>
<ul>
<li><p>그룹 내 컨슈머들은 데이터를 <strong>중복 없이 분담 처리</strong></p>
</li>
<li><p>그룹 간에는 서로 <strong>독립적으로 이벤트를 소비</strong></p>
</li>
</ul>
<hr>
<h2 id="⭐-인기글hot-article-시스템-설계"><strong>⭐ 인기글(Hot Article) 시스템 설계</strong></h2>
<h3 id="🔹-요구사항"><strong>🔹 요구사항</strong></h3>
<ul>
<li><p>일 단위로 상위 10건 인기글 선정</p>
</li>
<li><p>기준: 좋아요 수 + 댓글 수 + 조회수 기반 점수</p>
</li>
<li><p>최근 7일 인기글 내역 제공</p>
</li>
</ul>
<hr>
<h3 id="🔹-기존-배치-처리의-한계"><strong>🔹 기존 배치 처리의 한계</strong></h3>
<ul>
<li><p>대규모 데이터에서 시간 부족 및 시스템 부하 발생</p>
</li>
<li><p>API 기반 수집은 장애 전파 위험이 높음</p>
</li>
</ul>
<p>→ 따라서 <strong>실시간 스트림 처리 구조</strong>가 적합하다.</p>
<hr>
<h3 id="🔹-kafka-기반-스트림-처리"><strong>🔹 Kafka 기반 스트림 처리</strong></h3>
<ul>
<li><p>각 서비스(좋아요, 조회수, 댓글 등)는 이벤트를 Kafka로 발행</p>
</li>
<li><p>“인기글 서비스”는 Kafka의 여러 토픽을 구독해 점수를 실시간으로 계산</p>
</li>
<li><p>장애 전파 없이 비동기 이벤트 스트림으로 동작</p>
</li>
</ul>
<h4 id="✅-장점"><strong>✅ 장점</strong></h4>
<ul>
<li><p>서비스 간 <strong>결합도 낮음</strong></p>
</li>
<li><p>API 호출 없이 <strong>비동기 이벤트 기반 통신</strong></p>
</li>
<li><p>Kafka 자체의 <strong>내결함성</strong>과 <strong>확장성</strong> 활용</p>
</li>
</ul>
<hr>
<h3 id="🔹-인기글-저장소-설계"><strong>🔹 인기글 저장소 설계</strong></h3>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>선택</strong></th>
<th><strong>이유</strong></th>
</tr>
</thead>
<tbody><tr>
<td>데이터 특성</td>
<td>휘발성 (최근 7일만 유지)</td>
<td>TTL 필요</td>
</tr>
<tr>
<td>저장소</td>
<td><strong>Redis Sorted Set</strong></td>
<td>점수 기반 정렬 지원</td>
</tr>
</tbody></table>
<ul>
<li><p>Redis ZADD 명령을 활용해 <strong>(score, article_id)</strong> 저장</p>
</li>
<li><p>상위 10건 조회: ZREVRANGE popular:YYYYMMDD 0 9 WITHSCORES</p>
</li>
<li><p>TTL 설정으로 7일 이후 자동 삭제</p>
</li>
</ul>
<blockquote>
<p>인기글 계산은 하루 한 번만 수행되고, 상위 10건의 PK만 유지하면 된다.</p>
</blockquote>
<hr>
<h2 id="🧱-transactional-messaging-트랜잭셔널-메시징"><strong>🧱 Transactional Messaging (트랜잭셔널 메시징)</strong></h2>
<h3 id="🔹-문제-상황"><strong>🔹 문제 상황</strong></h3>
<p>Producer가 Kafka로 이벤트를 전송하는 과정과</p>
<p>내부 비즈니스 로직(DB 업데이트 등)을 하나의 트랜잭션으로 관리해야 한다.</p>
<p>하지만 Kafka와 DB는 별개 시스템이라,</p>
<p><strong>“부분 성공”</strong> 문제가 발생할 수 있다.</p>
<p>→ 즉, DB는 커밋됐는데 Kafka 전송 실패 / 혹은 반대 상황이 생김.</p>
<hr>
<h3 id="🔹-세-가지-해결-방법"><strong>🔹 세 가지 해결 방법</strong></h3>
<table>
<thead>
<tr>
<th><strong>방법</strong></th>
<th><strong>설명</strong></th>
<th><strong>단점</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>1. Two Phase Commit (2PC)</strong></td>
<td>모든 참여자의 응답을 모은 후 commit 신호 전송</td>
<td>지연 크고 장애 시 대기, Kafka/MySQL 미지원</td>
</tr>
<tr>
<td><strong>2. Transactional Outbox</strong></td>
<td>DB 트랜잭션에 Outbox 테이블 포함 → Message Relay가 전송</td>
<td>Outbox 테이블 관리 필요</td>
</tr>
<tr>
<td><strong>3. Transaction Log Tailing (CDC)</strong></td>
<td>DB 트랜잭션 로그를 직접 추적해 이벤트 전송</td>
<td>CDC 기술 필요, 구현 복잡</td>
</tr>
</tbody></table>
<hr>
<h3 id="🔹-transactional-outbox-패턴-설계"><strong>🔹 Transactional Outbox 패턴 설계</strong></h3>
<h4 id="✅-테이블-설계"><strong>✅ 테이블 설계</strong></h4>
<ul>
<li><p>outbox 테이블은 트랜잭션을 지원하는 DB에 생성</p>
</li>
<li><p>주요 컬럼: event_id, event_type, payload, status, shard_key</p>
</li>
<li><p>shard_key는 비즈니스 데이터와 동일 샤드에서 트랜잭션 처리 보장</p>
</li>
</ul>
<h4 id="✅-동작-흐름"><strong>✅ 동작 흐름</strong></h4>
<ol>
<li><p>서비스 로직 + Outbox 이벤트 기록 → <strong>단일 트랜잭션 커밋</strong></p>
</li>
<li><p>Message Relay 모듈이 10초마다 Outbox 테이블 조회</p>
</li>
<li><p>미전송 이벤트를 Kafka로 전송 후 상태 변경</p>
</li>
<li><p>중복 Polling 방지를 위해 “10초 지난 이벤트만 조회”</p>
</li>
<li><p>Consumer는 반드시 <strong>멱등성(idempotency)</strong> 보장</p>
</li>
</ol>
<hr>
<h3 id="🔹-message-relay-개선-방안"><strong>🔹 Message Relay 개선 방안</strong></h3>
<ul>
<li><p>Outbox 테이블은 샤드별로 존재하므로, 모든 샤드에 접근해야 함</p>
</li>
<li><p>이를 개선하기 위해 <strong>Coordinator 구조</strong> 도입 가능</p>
</li>
</ul>
<h4 id="🧠-coordinator-역할"><strong>🧠 Coordinator 역할</strong></h4>
<ol>
<li><p>중앙 저장소(redis 등)에 주기적으로 ping (3초 간격)</p>
</li>
<li><p>애플리케이션 식별자 + 타임스탬프로 실행 중인 인스턴스 파악</p>
</li>
<li><p>각 인스턴스에 샤드를 동적으로 분배</p>
</li>
<li><p>9초 이상 ping 없으면 종료된 것으로 판단하고 재분배</p>
</li>
</ol>
<blockquote>
<p>이 구조는 고가용성과 부하 분산을 함께 고려한 설계이며,</p>
</blockquote>
<blockquote>
<p>실제 대규모 시스템에서 Outbox Relay 모듈의 확장성 문제를 해결한다.</p>
</blockquote>
<hr>
<h2 id="🧭-정리"><strong>🧭 정리</strong></h2>
<table>
<thead>
<tr>
<th><strong>주제</strong></th>
<th><strong>핵심 포인트</strong></th>
</tr>
</thead>
<tbody><tr>
<td>어뷰징 방지</td>
<td>Redis TTL + NX(setIfAbsent)로 중복 조회 제어</td>
</tr>
<tr>
<td>Kafka</td>
<td>Topic/Partition/Offset/ConsumerGroup 개념 숙지</td>
</tr>
<tr>
<td>인기글 설계</td>
<td>스트림 처리 + Redis Sorted Set으로 효율적 인기글 관리</td>
</tr>
<tr>
<td>Transactional Messaging</td>
<td>Outbox 패턴으로 DB-이벤트 간 일관성 보장</td>
</tr>
<tr>
<td>Outbox 개선</td>
<td>Coordinator 기반 샤드 분산으로 확장성 확보</td>
</tr>
</tbody></table>
<hr>
<p>메모에 정리한 내용 GPT로 재정리함
<img src="https://velog.velcdn.com/images/dev-gromit/post/dff3161b-5eaa-4c94-9b5d-7ddceb096d78/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[강의] 대규모 시스템 설계 내용 정리 (2)]]></title>
            <link>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-2</link>
            <guid>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-2</guid>
            <pubDate>Tue, 07 Oct 2025 07:39:30 GMT</pubDate>
            <description><![CDATA[<h1 id="⚙️-대규모-시스템-설계-강의-정리-섹션-3"><strong>⚙️ 대규모 시스템 설계 강의 정리 (섹션 3)</strong></h1>
<blockquote>
<p>인프런 강의 <strong>섹션 3: 트리 구조, 좋아요 수, 조회수 처리 설계</strong> 학습 내용 정리</p>
</blockquote>
<blockquote>
<p>기간: 8월 30일 ~ 9월 1일</p>
</blockquote>
<hr>
<h2 id="📅-8월-30일-토--무한-depth-트리-구조-설계"><strong>📅 8월 30일 (토) — 무한 Depth 트리 구조 설계</strong></h2>
<h3 id="🔹-트리-구조의-문제점"><strong>🔹 트리 구조의 문제점</strong></h3>
<p>게시판, 댓글 등에서는 <strong>무한 Depth 구조(대댓글 등)</strong>가 자주 등장합니다.</p>
<p>이때, 단순히 부모-자식 관계를 테이블로 관리하면 <strong>계층 쿼리 성능이 급격히 저하</strong>됩니다.</p>
<h3 id="🔹-해결-방법-path-기반-정렬"><strong>🔹 해결 방법: Path 기반 정렬</strong></h3>
<ul>
<li><p>각 노드(댓글 등)에 path라는 문자열 컬럼을 둔다.</p>
</li>
<li><p>path 컬럼은 <strong>루트부터 현재 노드까지의 경로를 문자열로 저장</strong></p>
</li>
<li><p>이 path를 기준으로 <strong>오름차순 정렬</strong>하면 트리 구조를 쉽게 표현 가능</p>
</li>
</ul>
<p>예시:</p>
<table>
<thead>
<tr>
<th><strong>comment_id</strong></th>
<th><strong>parent_id</strong></th>
<th><strong>path</strong></th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>NULL</td>
<td>A</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>A.B</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>A.C</td>
</tr>
<tr>
<td>4</td>
<td>2</td>
<td>A.B.D</td>
</tr>
</tbody></table>
<p>→ ORDER BY path ASC 만으로 계층 구조 정렬 가능</p>
<h3 id="🔹-depth-제한"><strong>🔹 Depth 제한</strong></h3>
<ul>
<li><p>path 문자열의 길이로 Depth 제한을 둘 수 있음</p>
</li>
<li><p>길이 5 기준으로 한 Depth에 약 <strong>9억 개</strong>의 노드 표현 가능</p>
</li>
<li><p>path는 숫자뿐 아니라 <strong>대소문자 문자열까지 포함 가능</strong></p>
</li>
</ul>
<h3 id="🔹-62진수-기반-path-인코딩"><strong>🔹 62진수 기반 Path 인코딩</strong></h3>
<ul>
<li><p>각 Depth의 인덱스를 <strong>62진수(0-9, a-z, A-Z)</strong> 로 표현</p>
</li>
<li><p>예:</p>
<ul>
<li><p>Root: 0</p>
</li>
<li><p>첫 번째 자식: 1</p>
</li>
<li><p>두 번째 자식: 2</p>
</li>
<li><p>61번째 자식: Z</p>
</li>
<li><p>이후 자리수가 늘어나면서 계층 표현 가능</p>
</li>
</ul>
</li>
</ul>
<p>→ 문자열 기반 정렬이면서도 수학적으로 Depth 계산이 가능해 효율적</p>
<hr>
<h2 id="📅-8월-31일-일--좋아요-수like-count-설계"><strong>📅 8월 31일 (일) — 좋아요 수(Like Count) 설계</strong></h2>
<h3 id="🔹-좋아요-수의-특성"><strong>🔹 좋아요 수의 특성</strong></h3>
<ul>
<li><p><strong>실시간성</strong>이 중요하다.</p>
<p>  → 사용자에게 즉시 반영되어야 하므로 빠른 조회 필요</p>
</li>
<li><p>따라서 <strong>비정규화(denormalization)</strong> 구조가 유리하다.</p>
</li>
</ul>
<hr>
<h3 id="🔹-단순한-방식-게시글-테이블에-좋아요-수-저장"><strong>🔹 단순한 방식: 게시글 테이블에 좋아요 수 저장</strong></h3>
<ul>
<li><p>article 테이블에 like_count 컬럼 추가</p>
</li>
<li><p>좋아요 발생 시:</p>
<ol>
<li><p>like 테이블에 INSERT</p>
</li>
<li><p>article 테이블의 like_count UPDATE</p>
</li>
</ol>
</li>
</ul>
<p>하지만 이 방식은 문제를 일으킨다 👇</p>
<h4 id="⚠️-문제-1-record-lock"><strong>⚠️ 문제 1: Record Lock</strong></h4>
<ul>
<li><p>트랜잭션 내에서 article 테이블에 쓰기 잠금(lock)이 걸림</p>
</li>
<li><p>동시에 여러 사용자가 좋아요를 누르면 <strong>트랜잭션 대기 상태 발생</strong></p>
</li>
<li><p>좋아요 기능과 게시글 수정 기능이 서로 영향을 주게 됨</p>
</li>
</ul>
<h4 id="⚠️-문제-2-분산-트랜잭션"><strong>⚠️ 문제 2: 분산 트랜잭션</strong></h4>
<ul>
<li><p>좋아요 테이블과 게시글 테이블이 <strong>서로 다른 샤드(DB)</strong>에 위치 가능</p>
</li>
<li><p>예:</p>
<ul>
<li><p>like 테이블 → article_id 기반 샤딩</p>
</li>
<li><p>article 테이블 → board_id 기반 샤딩</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>서로 다른 DB 간 트랜잭션은 <strong>2PC(분산 트랜잭션)</strong> 필요</p>
</li>
<li><p>이는 <strong>복잡하고 느리며</strong>, 장애 전파 가능성 존재</p>
</li>
</ul>
<hr>
<h3 id="🔹-개선된-구조-좋아요-수-전용-테이블"><strong>🔹 개선된 구조: 좋아요 수 전용 테이블</strong></h3>
<ul>
<li><p>좋아요 수를 별도의 테이블(like_count)로 분리</p>
</li>
<li><p>like 테이블과 동일하게 <strong>article_id를 샤딩 키로 사용</strong></p>
</li>
</ul>
<table>
<thead>
<tr>
<th><strong>Table</strong></th>
<th><strong>Key</strong></th>
<th><strong>역할</strong></th>
</tr>
</thead>
<tbody><tr>
<td>like</td>
<td>article_id</td>
<td>사용자별 좋아요 기록</td>
</tr>
<tr>
<td>like_count</td>
<td>article_id</td>
<td>게시글별 좋아요 수 집계</td>
</tr>
</tbody></table>
<p>→ 이렇게 하면 분산 트랜잭션 없이 빠른 갱신 및 조회 가능</p>
<hr>
<h2 id="📅-9월-1일-월--조회수view-count-설계"><strong>📅 9월 1일 (월) — 조회수(View Count) 설계</strong></h2>
<h3 id="🔹-조회수의-특성"><strong>🔹 조회수의 특성</strong></h3>
<ul>
<li><p><strong>정확한 일관성보다 대략적인 실시간성</strong>이 중요</p>
</li>
<li><p>모든 조회 내역을 저장할 필요 없음 → 단순히 “조회 횟수”만 필요</p>
</li>
<li><p>따라서 트랜잭션보다는 <strong>속도와 효율성</strong> 우선</p>
</li>
</ul>
<hr>
<h3 id="🔹-문제점"><strong>🔹 문제점</strong></h3>
<ul>
<li><p>조회할 때마다 쓰기 작업 발생 → <strong>쓰기 트래픽이 많음</strong></p>
</li>
<li><p>디스크 기반 DB(MySQL 등)는 비용이 크고 성능 저하 가능</p>
</li>
</ul>
<hr>
<h3 id="🔹-해결-방안-redis-활용"><strong>🔹 해결 방안: Redis 활용</strong></h3>
<p><strong>In-memory DB</strong>인 Redis는 조회수 카운팅에 매우 적합하다.</p>
<h4 id="✅-장점"><strong>✅ 장점</strong></h4>
<ul>
<li><p>빠른 쓰기 성능 (메모리 기반)</p>
</li>
<li><p>클러스터 구성으로 <strong>확장성, 부하 분산, 고가용성, 안정성</strong> 확보</p>
</li>
<li><p><strong>자동 샤딩 지원</strong></p>
<ul>
<li>서버 추가 시 데이터 자동 분산</li>
</ul>
</li>
</ul>
<ul>
<li><strong>데이터 복제 및 영속성</strong> 기능 제공</li>
</ul>
<hr>
<h3 id="🔹-redis의-영속성-관리"><strong>🔹 Redis의 영속성 관리</strong></h3>
<ul>
<li><p>Redis는 디스크 기반 백업 기능 제공</p>
<ul>
<li><p><strong>AOF(Append Only File)</strong></p>
</li>
<li><p><strong>RDB(Snapshot)</strong></p>
</li>
</ul>
</li>
</ul>
<ul>
<li>이를 통해 일정 수준의 데이터 안정성을 확보 가능</li>
</ul>
<hr>
<h3 id="🔹-자체-백업-시스템-구축-방안"><strong>🔹 자체 백업 시스템 구축 방안</strong></h3>
<p>Redis만으로도 충분하지만, 서비스 레벨에서 <strong>보조 백업 로직</strong>을 두면 안정성이 향상된다.</p>
<h4 id="1️⃣-시간-단위-백업"><strong>1️⃣ 시간 단위 백업</strong></h4>
<ul>
<li><p>배치 또는 스케줄링 시스템으로 일정 주기마다 Redis 데이터를 백업</p>
</li>
<li><p>예: 매 5분마다 Redis의 조회수 데이터를 MySQL로 저장</p>
</li>
</ul>
<h4 id="2️⃣-개수-단위-백업"><strong>2️⃣ 개수 단위 백업</strong></h4>
<ul>
<li><p>조회수 누적이 일정 개수에 도달할 때마다 백업</p>
</li>
<li><p>조회 시점에 간단한 조건문으로 처리 가능</p>
</li>
</ul>
<hr>
<h2 id="🧭-마무리"><strong>🧭 마무리</strong></h2>
<p>이번 섹션에서는 <strong>대규모 트래픽 환경에서의 데이터 관리 전략</strong>을 다뤘습니다.</p>
<p>트리 구조, 좋아요, 조회수 모두 단순한 기능이지만,</p>
<p><strong>대량의 데이터가 쌓일 때 성능과 일관성, 확장성</strong>을 고려해야 한다는 점이 핵심이었습니다.</p>
<table>
<thead>
<tr>
<th><strong>주제</strong></th>
<th><strong>주요 포인트</strong></th>
</tr>
</thead>
<tbody><tr>
<td>트리 구조</td>
<td>문자열 기반 path 컬럼, 62진수 depth 표현</td>
</tr>
<tr>
<td>좋아요</td>
<td>Record Lock, 분산 트랜잭션 회피 → 별도 like_count 테이블</td>
</tr>
<tr>
<td>조회수</td>
<td>Redis 기반 In-memory 처리 + 주기적 백업</td>
</tr>
</tbody></table>
<hr>
<p>노트에 정리한 내용 GPT로 정리함
<img src="https://velog.velcdn.com/images/dev-gromit/post/c4fb1e53-c439-4dbf-87b2-97db79d30ce1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[강의] 대규모 시스템 설계 내용 정리 (1)]]></title>
            <link>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dev-gromit/%EA%B0%95%EC%9D%98-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 07 Oct 2025 07:35:25 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="🏗️-대규모-시스템-설계-강의-정리-섹션-2"><strong>🏗️ 대규모 시스템 설계 강의 정리 (섹션 2)</strong></h1>
<blockquote>
<p>인프런 강의 <strong>섹션 2: 데이터베이스 구조와 확장 전략</strong> 학습 내용 정리</p>
</blockquote>
<blockquote>
<p>기간: 8월 18일 ~ 8월 26일</p>
</blockquote>
<hr>
<h2 id="📅-8월-18일-월--샤딩sharding"><strong>📅 8월 18일 (월) — 샤딩(Sharding)</strong></h2>
<h3 id="🔹-샤딩이란"><strong>🔹 샤딩이란?</strong></h3>
<ul>
<li><p>데이터를 <strong>여러 DB에 분산 저장</strong>하는 기술</p>
</li>
<li><p>가용성을 높이기 위한 <strong>Master-Slave 구조</strong>와는 목적이 다름</p>
</li>
<li><p>샤딩은 <strong>데이터 분산과 확장성</strong>을 위한 구조</p>
</li>
</ul>
<h3 id="🔹-샤딩의-종류"><strong>🔹 샤딩의 종류</strong></h3>
<ul>
<li><p><strong>수직 샤딩(Vertical Sharding)</strong>: 테이블 단위로 기능별 DB 분리</p>
<p>  예) 사용자, 결제, 게시판 DB를 각각 분리</p>
</li>
<li><p><strong>수평 샤딩(Horizontal Sharding)</strong>: 한 테이블의 데이터를 여러 DB에 나누어 저장</p>
<p>  예) user_id 범위별로 다른 DB에 저장</p>
</li>
</ul>
<h3 id="🔹-샤딩-키sharding-key의-중요성"><strong>🔹 샤딩 키(Sharding Key)의 중요성</strong></h3>
<ul>
<li><p>어떤 기준으로 데이터를 분산할지 결정하는 핵심 포인트</p>
</li>
<li><p>잘못된 샤딩 키는 전체 DB를 탐색하게 만들어 성능 저하 유발</p>
</li>
<li><p>예시:</p>
<ul>
<li><p>DC Inside 같은 게시판 구조</p>
</li>
<li><p>“야구 갤러리”의 글과 댓글 데이터가 있을 때,</p>
<p>  → <strong>게시판 ID(board_id)</strong>를 샤딩 키로 설정하는 것이 효율적</p>
</li>
<li><p>만약 <strong>글 ID(article_id)</strong>를 샤딩 키로 쓴다면, 특정 게시판 조회 시 모든 DB를 뒤져야 할 수도 있음</p>
</li>
</ul>
</li>
</ul>
<h3 id="🔹-aws-dynamodb의-사례"><strong>🔹 AWS DynamoDB의 사례</strong></h3>
<ul>
<li><p>DynamoDB는 <strong>해싱 키만 지정하면 내부적으로 샤딩을 자동 처리</strong></p>
</li>
<li><p>애플리케이션에서 직접 샤딩 로직을 구현할 필요가 없음</p>
</li>
</ul>
<h3 id="🔹-pk-생성-전략-snowflake-알고리즘"><strong>🔹 PK 생성 전략: Snowflake 알고리즘</strong></h3>
<ul>
<li><p><strong>오름차순 + 유니크한 숫자</strong>를 생성하는 알고리즘</p>
</li>
<li><p>분산 환경에서 <strong>충돌 없이 정렬 가능한 ID</strong> 생성 가능</p>
</li>
<li><p>이후 강의에서 자세히 다룸</p>
</li>
</ul>
<hr>
<h2 id="📅-8월-21일-목--인덱스-구조와-페이지네이션"><strong>📅 8월 21일 (목) — 인덱스 구조와 페이지네이션</strong></h2>
<h3 id="🔹-innodb-인덱스-구조"><strong>🔹 InnoDB 인덱스 구조</strong></h3>
<ul>
<li><p><strong>InnoDB는 테이블마다 Clustered Index를 자동 생성</strong></p>
</li>
<li><p>Clustered Index의 leaf node는 실제 <strong>행 데이터(row)</strong>를 저장</p>
</li>
<li><p>일반적으로 <strong>Primary Key가 Clustered Index</strong>로 설정됨</p>
</li>
<li><p>우리가 생성하는 인덱스는 <strong>Secondary Index</strong></p>
</li>
</ul>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>Leaf Node 내용</strong></th>
<th><strong>데이터 접근 방식</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Clustered Index</td>
<td>실제 Row 데이터</td>
<td>직접 접근</td>
</tr>
<tr>
<td>Secondary Index</td>
<td>인덱스 컬럼 + PK 포인터</td>
<td>PK → Row 접근</td>
</tr>
</tbody></table>
<h3 id="🔹-커버링-인덱스covering-index"><strong>🔹 커버링 인덱스(Covering Index)</strong></h3>
<ul>
<li>조회 쿼리에서 필요한 컬럼이 모두 인덱스에 포함되어, <strong>테이블 접근 없이 인덱스만으로 조회 가능한 경우</strong></li>
</ul>
<pre><code>SELECT *
FROM (
    SELECT article_id
    FROM article
    WHERE board_id = 1
    ORDER BY article_id DESC
    LIMIT 30 OFFSET 149970
) t
LEFT JOIN article ON t.article_id = article.article_id;</code></pre><p>→ 서브쿼리에서 커버링 인덱스를 사용하고, 본 테이블을 LEFT JOIN하는 방식</p>
<h3 id="🔹-offset-기반-페이지네이션의-한계"><strong>🔹 Offset 기반 페이지네이션의 한계</strong></h3>
<ul>
<li><p><strong>Offset이 클수록 Index Scan 비용이 증가</strong></p>
</li>
<li><p>실제 데이터 접근 없이 인덱스만 타더라도 느려질 수밖에 없음</p>
</li>
</ul>
<h3 id="🔹-개선-방안"><strong>🔹 개선 방안</strong></h3>
<ol>
<li><p><strong>데이터 분리</strong></p>
<ul>
<li>예: 게시글을 <strong>연도별 테이블</strong>로 분리 (article_2024, article_2025 등)</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>메타데이터 관리</strong></p>
<ul>
<li>테이블별 게시글 개수를 미리 저장 → offset 범위 벗어나면 <strong>테이블 스킵</strong></li>
</ul>
</li>
</ol>
<ol start="3">
<li><p><strong>정책적 제약</strong></p>
<ul>
<li>일정 기간 이전의 데이터는 조회 불가</li>
</ul>
</li>
</ol>
<hr>
<h2 id="📅-8월-23일-토--count-쿼리-최적화"><strong>📅 8월 23일 (토) — Count 쿼리 최적화</strong></h2>
<h3 id="🔹-페이지-번호-기반-페이지네이션의-문제"><strong>🔹 페이지 번호 기반 페이지네이션의 문제</strong></h3>
<ul>
<li><p>게시글 개수를 표시하기 위해 COUNT(*) 쿼리를 실행</p>
</li>
<li><p>커버링 인덱스를 사용하더라도, <strong>모든 게시글 수를 세야 하므로 느림</strong></p>
</li>
</ul>
<h3 id="🔹-꼭-전체-개수가-필요할까"><strong>🔹 꼭 전체 개수가 필요할까?</strong></h3>
<ul>
<li><p>대부분의 서비스는 <strong>최대 이동 가능한 페이지 수가 제한됨</strong></p>
</li>
<li><p>예: 현재 1페이지에서 1~10페이지만 이동 가능</p>
</li>
</ul>
<h3 id="🔹-페이지-계산-공식"><strong>🔹 페이지 계산 공식</strong></h3>
<blockquote>
<p>최대 이동 가능한 페이지 수(k), 현재 페이지(n), 페이지당 게시글 수(m)</p>
</blockquote>
<pre><code>(((n - 1) / k) + 1) * m * k + 1</code></pre><h3 id="🔹-count-쿼리-최적화-예시"><strong>🔹 Count 쿼리 최적화 예시</strong></h3>
<pre><code>SELECT COUNT(*)
FROM (
    SELECT article_id
    FROM article
    WHERE board_id = {board_id}
    LIMIT {limit}
) t;</code></pre><p>→ 일부 데이터만 대상으로 count 수행 (성능 개선)</p>
<hr>
<h2 id="📅-8월-26일-화--pk-생성-전략"><strong>📅 8월 26일 (화) — PK 생성 전략</strong></h2>
<h3 id="🔹-1-db-auto-increment"><strong>🔹 1. DB Auto Increment</strong></h3>
<ul>
<li><p>단일 DB에서는 간편하지만, <strong>분산 환경에서는 PK 중복 발생 가능</strong></p>
</li>
<li><p>클라이언트 노출 시 <strong>보안 이슈</strong> 발생</p>
</li>
<li><p>UUID를 별도 Unique Index로 사용하는 것도 가능하나,</p>
<ul>
<li>Secondary → Clustered Index 접근이 필요해 <strong>조회 비용 증가</strong></li>
</ul>
</li>
</ul>
<hr>
<h3 id="🔹-2-유니크-문자열--난수-uuid-등"><strong>🔹 2. 유니크 문자열 / 난수 (UUID 등)</strong></h3>
<ul>
<li><p>UUID, Random String 등을 PK로 지정 가능</p>
</li>
<li><p>하지만 <strong>랜덤성으로 인해 성능 저하 발생</strong></p>
<ul>
<li><p>Clustered Index는 정렬 구조 유지 필요 → 중간 삽입 시 B+ Tree 재구성 발생</p>
</li>
<li><p>범위 조회 시 <strong>랜덤 I/O 증가</strong></p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="🔹-3-유니크-정렬-문자열-예-ulid"><strong>🔹 3. 유니크 정렬 문자열 (예: ULID)</strong></h3>
<ul>
<li><p>분산 환경에서도 중복 없음</p>
</li>
<li><p>정렬 가능 → 랜덤 I/O 감소</p>
</li>
<li><p>보안성도 확보</p>
</li>
<li><p>일반적으로 128비트 사용 → 데이터 크기에 따라 성능/공간 트레이드오프 존재</p>
</li>
</ul>
<hr>
<h3 id="🔹-4-유니크-정렬-숫자-예-snowflake"><strong>🔹 4. 유니크 정렬 숫자 (예: Snowflake)</strong></h3>
<ul>
<li><p><strong>64비트 정렬 숫자 기반 ID 생성 알고리즘</strong></p>
</li>
<li><p>분산 환경 중복 문제 해결</p>
</li>
<li><p>정렬 및 유니크 보장</p>
</li>
<li><p>대규모 시스템에서 PK로 널리 사용됨 (Twitter, Kakao 등)</p>
</li>
</ul>
<hr>
<h2 id="🧭-마무리"><strong>🧭 마무리</strong></h2>
<p>이번 섹션에서는 <strong>데이터베이스 확장성과 인덱스 구조, PK 설계 전략</strong>을 중심으로 다뤘습니다.</p>
<p>단순히 쿼리 튜닝을 넘어, <strong>데이터가 커질 때 시스템이 어떻게 확장될 수 있는지</strong>를 고민하게 되는 구간입니다.</p>
<blockquote>
<p>🔗 참고 링크: <a href="https://www.perplexity.ai/search/86307448-ffb7-4a83-ab26-302ce384e1ca">Perplexity 검색 결과</a></p>
</blockquote>
<hr>
<p>노트에 정리해둔 내용 GPT 사용하여 재정리함
<img src="https://velog.velcdn.com/images/dev-gromit/post/c792c4cf-8c49-4238-88b2-044bc47d74ae/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Graphql 내용 정리]]></title>
            <link>https://velog.io/@dev-gromit/Graphql-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dev-gromit/Graphql-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 06 Oct 2025 06:07:12 GMT</pubDate>
            <description><![CDATA[<h1 id="graphql-사용하는-이유">Graphql 사용하는 이유</h1>
<h3 id="graphql-이란">Graphql 이란</h3>
<p>클라이언트가 원하는 데이터를 명확히 정의하고 효율적으로 요청할 수 있도록, 통제권을 제공하는 API 쿼리 언어</p>
<h3 id="overfetching">Overfetching</h3>
<p>RestAPI는 서버에서 정의한 데이터를 모두 반환한다. 클라이언트에서 필요한 정보는 일부분인데 많은 데이터를 받아오게 되면, 불필요한 리소스 낭비가 발생한다.(네트워크 낭비, 클라이언트 리소스 낭비 등) Graphql을 사용함으로써 필요한 데이터만 조회할 수 있게되었다. (sql select 문에 아스타(*)를 쓰는게 아니라 컬럼을 정의해서 조회하는 것과 유사)</p>
<h3 id="underfetching">Underfetching</h3>
<p>RestAPI는 필요한 정보를 한 번에 가져오지 못한다. 사용자의 주문 목록을 조회할 때 RestAPI는 사용자를 먼저 조회하고 해당 정보로 주문 목록을 조회해야 한다. 이는 불필요하게 API를 두 번 호출해야돼서 리소스 낭비가 발생한다. Graphql을 사용하면 필요한 데이터를 한 번에 조회할 수 있다. (물론 서버에서 Http Response를 반환할 때 여러 연관 데이터를 모두 조회해서 반환해주어야 한다. Graphql은 연관데이터를 필드에 정의하고 한 번에 반환하는 걸 권장한다.)</p>
<h1 id="graphql-주요-개념">Graphql 주요 개념</h1>
<h2 id="단-하나의-엔드포인트">단 하나의 엔드포인트</h2>
<ul>
<li>REST처럼 여러 엔드포인트(/users, /posts, /comments)를 두지 않고, 단 하나의 엔드포인트(/graphql)에서 클라이언트가 <strong>필요한 데이터 구조를 직접 명시</strong>하여 요청할 수 있다.</li>
<li>HTTP REQUEST METHOD는 POST만 주로 사용한다.<h2 id="스키마">스키마</h2>
</li>
<li>GraphQL 서버의 데이터 구조(타입, 쿼리, 변형 가능 항목 등)를 정의하는 <strong>계약서</strong>이다.</li>
<li>REST의 OpenAPI(Swagger)와 유사한 역할을 한다.<h2 id="타입-시스템">타입 시스템</h2>
기본 스칼라 타입:</li>
<li>Int, Float, String, Boolean, ID
사용자 정의 타입:</li>
<li>type, input, enum, interface, union<pre><code>enum Role {
USER
ADMIN
}
</code></pre></li>
</ul>
<p>input CreateUserInput {
  name: String!
  email: String!
}</p>
<p>type User {
  id: ID!
  name: String!
  role: Role!
}</p>
<pre><code>## **Query (조회)**
- 데이터를 **조회(Read)** 하는 GraphQL의 핵심 개념
- SQL의 SELECT, REST의 GET과 유사한 역할을 한다.</code></pre><p>query {
  user(id: 1) {
    name
    posts {
      title
    }
  }
}</p>
<pre><code>## Mutation (변경)
- 데이터를 **변경(Create, Update, Delete)** 하는 작업
- REST의 POST/PUT/DELETE에 해당한다.</code></pre><p>mutation {
  createUser(input: { name: &quot;Alice&quot;, email: &quot;<a href="mailto:a@ex.com">a@ex.com</a>&quot; }) {
    id
    name
  }
}</p>
<pre><code>
# Graphql 사용 방법
## Test Tool
RestAPI의 테스트 툴로 PostMan이 있다면, Graphql에서는 Altair가 있다.
![](https://velog.velcdn.com/images/dev-gromit/post/7a36d6d7-99ee-4288-b23c-b6e0220c2e31/image.png)


# Graphql with Spring
## 세팅 방법
### Graphql 라이브러리 설치</code></pre><p>plugins {<br>    id &#39;java&#39;<br>    id &#39;org.springframework.boot&#39; version &#39;3.5.6&#39;<br>    id &#39;io.spring.dependency-management&#39; version &#39;1.1.7&#39;<br>}  </p>
<p>group = &#39;khyou&#39;<br>version = &#39;0.0.1-SNAPSHOT&#39;<br>description = &#39;graphql-demo&#39;  </p>
<p>java {<br>    toolchain {<br>        languageVersion = JavaLanguageVersion.of(21)<br>    }<br>}  </p>
<p>configurations {<br>    compileOnly {<br>        extendsFrom annotationProcessor<br>    }<br>}  </p>
<p>repositories {<br>    mavenCentral()<br>}  </p>
<p>dependencies {<br>    implementation &#39;org.springframework.boot:spring-boot-starter-graphql&#39;<br>    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;<br>    implementation &#39;org.springframework.boot:spring-boot-starter-websocket&#39;<br>    compileOnly &#39;org.projectlombok:lombok&#39;<br>    annotationProcessor &#39;org.projectlombok:lombok&#39;<br>    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;<br>    testImplementation &#39;org.springframework.graphql:spring-graphql-test&#39;<br>    testRuntimeOnly &#39;org.junit.platform:junit-platform-launcher&#39;<br>    implementation &quot;com.graphql-java:graphql-java-extended-scalars:22.0&quot;<br>}  </p>
<p>tasks.named(&#39;test&#39;) {<br>    useJUnitPlatform()<br>}</p>
<pre><code>
### Graphql Intellij 플러그인 설치
![](https://velog.velcdn.com/images/dev-gromit/post/9c41c608-198f-468a-91eb-083d7a95e1a4/image.png)


### Graphql 파일 위치
![](https://velog.velcdn.com/images/dev-gromit/post/17a0c0aa-b7fd-4f72-8f77-2e43adf67a92/image.png)


### application.yml</code></pre><p>server:<br>  port: 8081  </p>
<p>spring:<br>  graphql:<br>    schema:<br>      file-extensions: graphql<br>    websocket:<br>      path: graphql</p>
<pre><code>## 리졸버
- Controller 어노테이션을 사용한다.
- 쿼리는 @QueryMapping 어노테이션을, Mutation은 @MutationMapping 어노테이션을, 구독(websocket 사용)은 @SubscriptionMapping 어노테이션을 각 메서드에 입력한다.
- 파라미터는 @Argument 어노테이션을 붙인다.
</code></pre><p>@Controller<br>public class ProductResolver {<br>    private final ProductService productService;  </p>
<pre><code>public ProductResolver(ProductService productService) {  
    this.productService = productService;  
}  

@QueryMapping  
public List&lt;Product&gt; getProducts() {  
    return productService.getProducts();  
}  

@MutationMapping  
public Product addProduct(@Argument AddProductInput addProductInput) throws BadRequestException {  
    return productService.addProduct(addProductInput);  
}  

@SubscriptionMapping  
public Flux&lt;Product&gt; newProduct(@Argument String productName) {  
    return productService.messageFlux(productName);  
}  </code></pre><p>}</p>
<pre><code>
### 예외 처리
Graphql은 REST처럼 HTTP 상태코드로 에러를 표현하지 않고, **항상 200 OK 응답**을 내려보내되, 본문 안에 errors 필드로 에러를 전달한다.</code></pre><p>{
  &quot;data&quot;: null,
  &quot;errors&quot;: [
    {
      &quot;message&quot;: &quot;Post not found&quot;,
      &quot;path&quot;: [&quot;postById&quot;],
      &quot;extensions&quot;: {
        &quot;errorType&quot;: &quot;NOT_FOUND&quot;
      }
    }
  ]
}</p>
<pre><code>
에러는 GraphQL 응답 구조 안에 포함된다. 이걸 커스터마이징하기 위해 Spring은 GraphQlExceptionHandler, DataFetcherExceptionResolver 등을 제공한다.

### @GraphQlExceptionHandler</code></pre><p>import org.springframework.graphql.data.method.annotation.GraphQlExceptionHandler;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.stereotype.Controller;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;</p>
<p>@Controller
public class GlobalGraphQLExceptionHandler {</p>
<pre><code>@GraphQlExceptionHandler(PostNotFoundException.class)
public GraphQLError handlePostNotFound(PostNotFoundException ex) {
    return GraphqlErrorBuilder.newError()
            .message(ex.getMessage())
            .errorType(ErrorType.NOT_FOUND)
            .build();
}

@GraphQlExceptionHandler(IllegalArgumentException.class)
public GraphQLError handleInvalidArgument(IllegalArgumentException ex) {
    return GraphqlErrorBuilder.newError()
            .message(&quot;Invalid input: &quot; + ex.getMessage())
            .errorType(ErrorType.BAD_REQUEST)
            .build();
}</code></pre><p>}</p>
<pre><code>
### REST API + Graphql
REST API와 Graphql을 함께 제공하는 경우 Exception Handler는 아래와 같이 작성하면 된다.</code></pre><p>@ControllerAdvice
public class GlobalExceptionHandler {</p>
<pre><code>@ExceptionHandler(RuntimeException.class)
public ResponseEntity&lt;ApiErrorResponse&gt; handleRestException(RuntimeException ex) {
    return ResponseEntity.badRequest().body(new ApiErrorResponse(ex.getMessage()));
}

@GraphQlExceptionHandler(RuntimeException.class)
public GraphQLError handleGraphQLException(RuntimeException ex) {
    return GraphqlErrorBuilder.newError()
            .message(ex.getMessage())
            .errorType(ErrorType.INTERNAL_ERROR)
            .build();
}</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][프로그래머의 뇌]]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EB%87%8C</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EB%87%8C</guid>
            <pubDate>Thu, 02 Oct 2025 12:29:01 GMT</pubDate>
            <description><![CDATA[<h1 id="1-코드-더-잘-읽기">1. 코드 더 잘 읽기</h1>
<ul>
<li>LTM: 장기 기억 공간 (하드)</li>
<li>STM: 단기 기억 공간 (메모리)</li>
</ul>
<h2 id="코딩에-영향을-주는-인지-과정">코딩에 영향을 주는 인지 과정</h2>
<ul>
<li>지식이 없다는 것은 두뇌의 장기 기억 공간의 문제</li>
<li>지식이 아닌 어떤 정보가 부족할 때는 단기 기억 공간의 문제</li>
</ul>
<h2 id="1장-요약">1장 요약</h2>
<p>코드를 읽거나 작성할 때 발생하는 인지과정 세가지</p>
<ol>
<li>LTM에서 정보를 인출한다. 키워드의 의미 같은 것</li>
<li>메서드나 변수의 이름과 같이 코드를 읽는 과정에서 발생하는 정보를 STM에 일시적으로 저장한다.</li>
<li>작업 기억 공간에서 일어난다. 코드를 읽고 처리하는 일. 예: 인덱스 값이 어떻게 변하는지</li>
</ol>
<h1 id="2-신속한-코드-분석">2. 신속한 코드 분석</h1>
<ul>
<li>LTM에 지식이 부족하면 코드를 읽을 때 하위 수준의 정보들 이를 테면 문자나 키워드 같은 것에 의존해야 한다. 이럴 때 STM의 공간이 빠르게 소진된다.<ul>
<li>코드에서 친절하게 메서드의 이름을 dfs라고 지었다고 해보자. dfs를 알고 있으면, dfs 로직을 떠올리면서 코드를 읽기 때문에 신속하게 분석할 수 있다. 하지만 dfs에 대한 지식이 없으면 STM을 사용해야 하는데 STM은 저장 공간이 작기 때문에 코드 분석에 어려움을 겪는다.</li>
</ul>
</li>
<li>다른 개발자들이 내 코드를 신속하게 분석할 수 있도록 하기 위해서는 일반적으로 많이 사용하는 것들을 활용해야 한다.<ul>
<li>예: for문의 index의 이름은 i로 짓는다. 2중 for문의 index의 이름은 j로 짓는다. 디자인 패턴을 활용한다.</li>
</ul>
</li>
<li>코드는 우리 두뇌에서 처리하기 쉽게 만드는 특징들 가령 디자인 패턴, 주석문, 명확한 표식 같은 것들이 있다.</li>
</ul>
<h1 id="3-프로그래밍-문법-빠르게-배우기">3. 프로그래밍 문법 빠르게 배우기</h1>
<ul>
<li>문법에 대한 지식이 더 많을 수록 LTM을 더 많이 활용할 수 있기 때문에 문법을 외우는 것이 중요하다.</li>
<li>기억이 없어지는 것을 방지하기 위해, 새로운 정보를 기억하는 연습을 정기적으로 하는 것이 중요하다.</li>
<li>최상의 연습은 기억한 것을 두뇌로부터 인출하는 연습이다. 다른 곳에서 해당 정보를 찾기 전에 기억해내려고 노력해야 한다.</li>
</ul>
<h1 id="7-생각의-버그">7. 생각의 버그</h1>
<h2 id="두-번째-프로그래밍-언어가-첫-번째보다-쉬운-이유">두 번째 프로그래밍 언어가 첫 번째보다 쉬운 이유</h2>
<p>LTM에 저장된 프로그래밍 지식은 새로운 프로그래밍 개념을 배우는 데 두 가지 방식으로 도움이 될 수 있다.</p>
<ol>
<li>프로그래밍에 대해 이미 많이 알고 있다면 그것에 대해 더 많이 학습하는 것이 쉬워진다.</li>
<li>학습 전이는 완전히 낯선 상황에 이미 알고 있는 내용을 적용할 때 일어난다. 학습 전이로 인해 LTM에 저장된 지식이 학습을 지원하게 된다.</li>
</ol>
<h2 id="오개념-생각의-버그">오개념: 생각의 버그</h2>
<p>지식의 전이의 단점이다. 코드가 작동한다고 확심함에도 불구하고 여전히 오류가 발생한다면 코드에 대한 오개념이 문제일 가능성이 있다.</p>
<h3 id="새로운-프로그래밍-언어를-배울-때-오개념-방지하기">새로운 프로그래밍 언어를 배울 때 오개념 방지하기</h3>
<ol>
<li>자신이 옳다고 확신하더라도 여전히 틀릴 수도 있다는 것을 아는 것이 중요하다.</li>
<li>흔하게 발생하는 오개념에 대해 의도적으로 연구해봄으로써 그런 오개념에 빠지는 것을 방지할 수 있다.</li>
</ol>
<h1 id="10-복잡한-문제-해결을-더-잘하려면">10. 복잡한 문제 해결을 더 잘하려면</h1>
<ul>
<li>프로그래밍에 종사하는 많은 사람은 문제 해결이 일반적인 기술이라고 주장하지만, 프로그래밍에 대한 사전 지식이 현재 해결 중인 문제와 결합해 프로그래밍 문제를 얼마나 빨리 해결할 수 있는지에 영향을 미친다.</li>
<li>프로그래밍과 관련된 명시적 기억을 강화하려면 기존 코드, 가급적이면 코드 설계 방법에 대한 설명이 포함된 코드를 연구하라.</li>
</ul>
<h3 id="개발-작업-시-풀이된-예제-활용하기">개발 작업 시 풀이된 예제 활용하기</h3>
<ol>
<li>코드를 혼자 공부할 필요는 없다. 누군가와 함께하는 것이 더 유용하다. 함께 코드를 읽으면 서로 배울 수 있다.</li>
<li>혼자서 코드를 읽는 방법을 찾고 있다면 깃허브를 읽거나, 라이브러리의 소스코드를 읽는 것이 도움이 된다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 시스템 설계 면접 공략법]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%A9%B4%EC%A0%91-%EA%B3%B5%EB%9E%B5%EB%B2%95</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EB%A9%B4%EC%A0%91-%EA%B3%B5%EB%9E%B5%EB%B2%95</guid>
            <pubDate>Wed, 01 Oct 2025 13:04:55 GMT</pubDate>
            <description><![CDATA[<p>그 전에 2장 &#39;개략적인 규모 측정&#39;을 정리하자.</p>
<h1 id="개략적인-규모-측정">개략적인 규모 측정</h1>
<p>&quot;개략적인 규모 추정&quot;은 보편적으로 통용되는 성능 수치상에서 사고 실험을 행하여 추정치를 계산하는 행위로서, 어떤 설계가 요구사항에 부합할 것인지 보기 위한 것&quot;이다.</p>
<p>개략적인 규모 측정을 위해서는 2의 제곱수, 응답지연 값, 고가용성에 대해 알고있어야 한다.</p>
<p>최근 기술 도향이 반영된 응답 지연 값에서 알 수 있는 사실은 다음과 같다.</p>
<ul>
<li>메모리는 빠르지만 디스크는 아직도 느리다.</li>
<li>디스크 탐색은 최대한 피하라</li>
<li>단순한 암축 알고리즘은 빠르다.</li>
<li>데이터를 인터넷으로 전송하기 전에 가능하면 압축하라</li>
<li>데이터 센터는 보통 여러 지역에 분산되어 있고, 센터들 간에 데이터를 주고받는 데는 시간이 걸린다.</li>
</ul>
<h1 id="시스템-설계-면접-공략법">시스템 설계 면접 공략법</h1>
<h2 id="1단계-문제-이해-및-설계-범위-확정">1단계 문제 이해 및 설계 범위 확정</h2>
<ul>
<li>요구사항을 완전히 이해하지 않고 답을 내놓는 행위는 아주 엄청난 부정적 신호다. 면접은 퀴즈 쇼가 아니며, 정답 따위는 없다는 걸 상기하자.</li>
<li>깊이 생각하고 질문하여 요구사항과 가정들을 분명히 하라.</li>
<li>이 단계에서 나올만한 질문들의 예시는 다음과 같다. (면접자 to 면접관)<ul>
<li>구체적으로 어떤 기능들을 만들어야 하나?</li>
<li>제품 사용자 수는 얼마나 되나?</li>
<li>회사의 규모는 얼마나 빨리 커지리라 예상하나?<h2 id="2단계-개략적인-설계안-제시-및-동의-구하기">2단계 개략적인 설계안 제시 및 동의 구하기</h2>
이 단계에서 초점을 맞추어야 할 것은 개략적인 설계안을 제시하고 면접관의 동의를 얻는 것이다.</li>
</ul>
</li>
<li>설계안에 대한 최초 청사진을 제시하고 의견을 구하라.</li>
<li>화이트보드나 종이에 핵심 컴포넌트를 포함하는 다이어그램을 그려라.</li>
<li>이 최초 설계안이 시스템 규모에 관계된 제약사항들을 만족하는지를 개략적으로 계산해보라.<h2 id="3단계-상세-설계">3단계 상세 설계</h2>
이제 면접관과 해야 할 일은 설계 대상 컴포넌트 사이의 우선순위를 정하는 것이다.</li>
<li>시스템의 병목 구간이나 자원 요구량 추정치에 초점이 맞춰져있을 수 있다.</li>
<li>단축 URL 생성기라면 해시 함수의 설계를, 채팅 시스템이라면 지연시간을 줄이고 사용자의 온/오프라인 상태를 표시할 것인지를 듣고자 할 것이다.<h2 id="4단계-마무리">4단계 마무리</h2>
해야 할 것</li>
<li>질문을 통해 확인하라. 스스로 내린 가정이 옳다 믿고 진행하지 마라.</li>
<li>문제의 요구사항을 이해하라.</li>
<li>정답이나 최선의 답안 같은 것은 없다는 걸 명심하라.</li>
<li>면접관이 여러분의 사고 흐름을 이해할 수 있도록 하라. (최대한 얘기하면서, 말 많이 하면서 설계하라)
하지 말아야 하는 것</li>
<li>요구사항이나 가정들을 분명히 하지 않은 상태에서 설계를 제시하지 마라.</li>
<li>처음부터 특정 컴포넌트의 세부사항을 너무 깊이 설명하지 말라.</li>
<li>진행 중에 막혔다면, 힌트를 청하기를 주저하지 말라.</li>
<li>다시 말하지만, 소통을 주저하지 말라. 침묵 속에 설계를 진행하지 마라.</li>
</ul>
<h1 id="번외">번외</h1>
<h2 id="graph-db">Graph DB</h2>
<h3 id="1-그래프-db란">1. 그래프 DB란?</h3>
<p>그래프 데이터베이스는 데이터와 데이터 간의 관계(연결) 를 최우선으로 관리하는 데이터베이스이다.</p>
<p>전통적인 관계형 데이터베이스(RDB)는 테이블, 행(Row), 열(Column) 구조로 데이터를 저장하지만, 그래프 DB는 다음과 같은 그래프 구조를 사용한다.</p>
<ul>
<li><p>노드(Node): 개체(Entity)를 나타냄 (예: 사람, 제품, 도시 등)</p>
</li>
<li><p>엣지(Edge / Relationship): 노드 간의 관계를 나타냄 (예: “친구다”, “구매했다”, “연결되어 있다”)</p>
</li>
<li><p>속성(Property): 노드나 엣지에 붙는 추가 정보 (예: 사람 노드 → 이름, 나이 / 관계 엣지 → 시작일, 친밀도 등)</p>
</li>
</ul>
<p>즉, 그래프 DB는 데이터 그 자체뿐만 아니라 데이터 간의 연결성을 1급 객체로 다룬다는 점이 핵심</p>
<h3 id="2-왜-그래프-db를-쓸까">2. 왜 그래프 DB를 쓸까?</h3>
<p>관계형 DB에서도 JOIN을 사용해 관계를 표현할 수 있지만, 관계가 깊어질수록 성능이 급격히 나빠진다.</p>
<p>그래프 DB는 이런 경우에 관계 탐색을 효율적으로 수행한다.</p>
<ul>
<li><p>소셜 네트워크 분석
  예: “이 사람의 친구의 친구 중에 같이 일한 적 있는 사람?” → RDB에서는 여러 번 JOIN 필요, 그래프 DB에서는 관계 탐색만으로 빠르게 해결.</p>
</li>
<li><p>추천 시스템
  예: “내가 본 영화와 비슷한 영화를 본 사람들의 다른 취향”</p>
</li>
<li><p>네트워크/경로 탐색
  예: “서울에서 부산까지 가장 빠른 경로는?” → 도로 네트워크를 그래프로 표현.</p>
</li>
</ul>
<h3 id="3-장점">3. 장점</h3>
<ul>
<li><p>복잡한 관계 쿼리에 강함: 관계형 DB보다 JOIN 비용이 훨씬 적음.</p>
</li>
<li><p>직관적인 데이터 모델: 실제 관계를 그래프 구조로 시각화 가능.</p>
</li>
<li><p>확장성: 관계가 많은 데이터를 다루기에 적합.</p>
</li>
</ul>
<h3 id="4-단점">4. 단점</h3>
<ul>
<li><p>트랜잭션/집계 연산은 RDB보다 약한 경우 많음.</p>
</li>
<li><p>표준화 부족: SQL처럼 범용 표준 쿼리 언어가 없음 (대신 Cypher, Gremlin 같은 언어 사용).</p>
</li>
<li><p>특정 도메인에 특화됨: 모든 데이터를 그래프로 표현하는 건 적합하지 않을 수 있음.</p>
</li>
</ul>
<h3 id="5-대표적인-그래프-db">5. 대표적인 그래프 DB</h3>
<ul>
<li><p>Neo4j: 가장 유명한 그래프 DB, Cypher 쿼리 언어 사용.</p>
</li>
<li><p>Amazon Neptune: AWS에서 제공하는 관리형 그래프 DB.</p>
</li>
<li><p>OrientDB, ArangoDB: 멀티 모델 DB로 그래프 기능 포함.</p>
</li>
<li><p>JanusGraph: 대규모 분산 그래프 DB.**</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB Master & Slave 실습]]></title>
            <link>https://velog.io/@dev-gromit/DB-Master-Slave-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@dev-gromit/DB-Master-Slave-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Sat, 27 Sep 2025 09:08:42 GMT</pubDate>
            <description><![CDATA[<p>linux 에서 docker compose로 mysql db 두 대를 띄운다.
아래 절차에 따라 하면 된다.</p>
<h2 id="1️⃣-docker--docker-compose-설치"><strong>1️⃣ Docker &amp; Docker Compose 설치</strong></h2>
<pre><code># 패키지 업데이트
sudo yum update -y

# docker 설치
sudo amazon-linux-extras enable docker
sudo yum install -y docker

# docker 시작 및 부팅 시 자동 실행
sudo systemctl start docker
sudo systemctl enable docker

# ec2-user를 docker 그룹에 추가 (sudo 없이 실행 가능)
sudo usermod -aG docker ec2-user
# ⚠️ 이 작업 후에는 재로그인 해야 반영됨

# docker compose plugin 설치 (Amazon Linux 2는 v2 권장)
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64 \
  -o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose

# 설치 확인
docker --version
docker compose version</code></pre><h2 id="2-디렉토리-구조">2. 디렉토리 구조</h2>
<pre><code>mkdir -p ~/mysql-replication/{master_conf,slave_conf}
cd ~/mysql-replication</code></pre><pre><code>~/mysql-replication/
 ├─ docker-compose.yml
 ├─ master_conf/
 │    └─ my.cnf
 └─ slave_conf/
      └─ my.cnf</code></pre><h2 id="3--docker-composeyml-작성">3.  <strong>docker-compose.yml</strong> <strong>작성</strong></h2>
<pre><code>version: &#39;3.9&#39;
services:
  master:
    image: mysql:8.0
    container_name: mysql-master
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: testdb
    ports:
      - &quot;3306:3306&quot;
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - ./master_conf:/etc/mysql/conf.d
      - ./master_data:/var/lib/mysql

  slave:
    image: mysql:8.0
    container_name: mysql-slave
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
    ports:
      - &quot;3307:3306&quot;
    command: --default-authentication-plugin=mysql_native_password
    depends_on:
      - master
    volumes:
      - ./slave_conf:/etc/mysql/conf.d
      - ./slave_data:/var/lib/mysql</code></pre><h2 id="4-설정-파일">4. 설정 파일</h2>
<h3 id="master_confmycnf"><strong>master_conf/my.cnf</strong></h3>
<pre><code>[mysqld]
server-id=1
log-bin=mysql-bin
binlog-do-db=testdb

# 메모리 줄이기
innodb_buffer_pool_size=64M
innodb_log_buffer_size=8M
max_connections=50</code></pre><h3 id="slave_confmycnf"><strong>slave_conf/my.cnf</strong></h3>
<pre><code>[mysqld]
server-id=2
relay-log=relay-log-bin

# 메모리 줄이기
innodb_buffer_pool_size=64M
innodb_log_buffer_size=8M
max_connections=50</code></pre><h1 id="master-slave-db-설정">Master Slave DB 설정</h1>
<ol>
<li>컨테이너 실행<pre><code>docker compose up -d</code></pre></li>
<li>마스터 접속 → 복제 계정 생성<pre><code>CREATE USER &#39;repl&#39;@&#39;%&#39; IDENTIFIED BY &#39;replpass&#39;;
GRANT REPLICATION SLAVE ON *.* TO &#39;repl&#39;@&#39;%&#39;;
FLUSH PRIVILEGES;
</code></pre></li>
</ol>
<p>SHOW MASTER STATUS;</p>
<pre><code>→ File / Position 값 확인 (예: mysql-bin.000001, 154) &lt;- 중요


3. 슬레이브 접속 → 복제 연결</code></pre><p>docker exec -it mysql-slave mysql -uroot -prootpass</p>
<pre><code></code></pre><p>CHANGE REPLICATION SOURCE TO
  SOURCE_HOST=&#39;master&#39;,
  SOURCE_USER=&#39;repl&#39;,
  SOURCE_PASSWORD=&#39;replpass&#39;,
  SOURCE_LOG_FILE=&#39;mysql-bin.000001&#39;, # 2번에서 확인한 값
  SOURCE_LOG_POS=154; # 2번에서 확인한 값</p>
<p>START REPLICA;
SHOW REPLICA STATUS\G;</p>
<pre><code>
- SHOW SLAVE STATUS 를 통해 SLAVE 설정이 잘 되었는 지 확인 가능하다.

# Replication URL을 활용한 Mater Slave DB 사용

## **1. Spring Boot에서 JDBC Replication URL 설정**

application.yml (또는 application.properties)에 아래처럼 적는다.</code></pre><p>spring:
  datasource:
    url: jdbc:mysql:replication://master:3306,slave:3306/testdb
    username: root
    password: rootpass
    driver-class-name: com.mysql.cj.jdbc.Driver</p>
<pre><code>
## **2. Connection의 readOnly 여부 확인하기**
JDBC 드라이버는 **Connection.setReadOnly(true/false)** 기준으로 Master/Slave를 선택한다.
예제 코드:</code></pre><p>import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;</p>
<p>@Component
public class ReplicationTest implements CommandLineRunner {</p>
<pre><code>@Autowired
private DataSource dataSource;

@Override
public void run(String... args) throws Exception {
    try (Connection conn = dataSource.getConnection()) {
        // 1. 기본 모드 (readOnly=false) → Master
        System.out.println(&quot;Default readOnly = &quot; + conn.isReadOnly());
        try (Statement stmt = conn.createStatement()) {
            stmt.executeUpdate(&quot;INSERT INTO test_table (name) VALUES (&#39;master-write&#39;)&quot;);
            System.out.println(&quot;✅ INSERT 성공 (Master)&quot;);
        }

        // 2. readOnly=true → Slave
        conn.setReadOnly(true);
        System.out.println(&quot;Now readOnly = &quot; + conn.isReadOnly());
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(&quot;SELECT COUNT(*) FROM test_table&quot;)) {
            if (rs.next()) {
                System.out.println(&quot;✅ SELECT 성공 (Slave) → row count: &quot; + rs.getInt(1));
            }
        }

        // 3. readOnly=true 상태에서 쓰기 시도 → 실패 (Slave는 쓰기 불가)
        try (Statement stmt = conn.createStatement()) {
            stmt.executeUpdate(&quot;INSERT INTO test_table (name) VALUES (&#39;slave-write&#39;)&quot;);
        } catch (Exception e) {
            System.out.println(&quot;🚨 Slave에 쓰기 시도 실패 = &quot; + e.getMessage());
        }
    }
}</code></pre><p>}</p>
<p>```</p>
<h2 id="3-기대-결과"><strong>3. 기대 결과</strong></h2>
<ul>
<li>conn.setReadOnly(false) 상태 → Master에서 INSERT 성공</li>
<li>conn.setReadOnly(true) 상태 → Slave에서 SELECT 정상 동작</li>
<li>conn.setReadOnly(true) 상태에서 INSERT 시도 → 에러 발생 (The MySQL server is running with the --read-only option so it cannot execute this statement)</li>
</ul>
<p>이렇게 되면 Replication URL이 정상적으로 Master/Slave를 구분해서 사용하고 있다는 걸 검증할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev-gromit/post/9748ad07-a2ab-4c6f-a734-e462f0f0f7cb/image.png" alt=""></p>
<h2 id="master-slave-직접-구축-시의-어려움"><strong>Master Slave 직접 구축 시의 어려움</strong></h2>
<p>직접 Master-Slave 구조에서 자동 승격을 구현하려면 다음과 같은 추가 도구가 필요하다:</p>
<ul>
<li><p><strong>Orchestrator</strong>: MySQL 전용 클러스터 관리 도구. 장애 감지 및 자동 승격 지원</p>
</li>
<li><p><strong>MHA (Master High Availability Manager)</strong>: MySQL Master 장애 복구 도구</p>
</li>
<li><p><strong>ProxySQL / HAProxy</strong>: DB Proxy를 두어 애플리케이션 연결을 자동으로 새로운 Master로 라우팅</p>
</li>
</ul>
<p>이런 도구들을 직접 설치하고 운영하면:</p>
<ul>
<li><p>장애 감지 속도와 정확성 튜닝 필요</p>
</li>
<li><p>네트워크 분할(파티션) 같은 복잡한 장애 상황 처리 필요</p>
</li>
<li><p>운영자가 직접 모니터링 및 유지보수 해야 함</p>
</li>
</ul>
<p>따라서 학습 목적이 아니라면 <strong>직접 구현은 높은 운영 비용과 리스크</strong>를 수반한다.</p>
<h3 id="csp-서비스를-활용한-접근">CSP 서비스를 활용한 접근</h3>
<p>AWS, GCP, Azure 같은 <strong>클라우드 서비스 제공업체(CSP)</strong>에서는 자동 승격 기능을 이미 서비스 형태로 제공합니다.</p>
<p>예를 들어:</p>
<ul>
<li><p><strong>AWS RDS / Aurora</strong>: 장애 시 자동 Failover 지원, DNS 레벨에서 연결 자동 전환</p>
</li>
<li><p><strong>Google Cloud SQL</strong>: 고가용성 모드(HA)에서 자동 승격 제공</p>
</li>
<li><p><strong>Azure Database for MySQL</strong>: 자동 장애 감지 및 Failover 기능 포함</p>
</li>
</ul>
<p>이를 활용하면:</p>
<ul>
<li><p><strong>운영 부담 최소화</strong>: 장애 감지, 승격, 연결 전환을 CSP가 대신 처리</p>
</li>
<li><p><strong>검증된 안정성</strong>: 이미 대규모 서비스 환경에서 사용되는 기술을 그대로 사용 가능</p>
</li>
<li><p><strong>비용 대비 효율성</strong>: 직접 운영팀을 두는 것보다 훨씬 효율적</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책][대규모 시스템 설계] 사용자 수에 따른 규모 확장성]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</guid>
            <pubDate>Wed, 17 Sep 2025 11:35:04 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스">데이터베이스</h1>
<h2 id="어떤-데이터-베이스를-사용할-것인가">어떤 데이터 베이스를 사용할 것인가?</h2>
<h3 id="비-관계형-데이터베이스가-바람직한-경우">비-관계형 데이터베이스가 바람직한 경우</h3>
<p>대부분의 개발자에게는 관계형 데이터베이스가 최선일 것이지만 다음과 같은 경우에 비-관계형 데이터베이스를 고려해 보아야 한다.</p>
<ul>
<li>아주 낮은 응답 지연시간이 요구되는 경우</li>
<li>다루는 데이터가 비정형인 경우</li>
<li>데이터를 직렬화하거나 역직렬화 할 수 있기만 하면 되는 경우</li>
<li>아주 많은 양의 데이터를 저장할 필요가 있는 경우</li>
</ul>
<h4 id="왜-아주-많은-양의-데이터를-저장할-필요가-있을-때-비-관계형-데이터베이스가-바람직할까">왜 아주 많은 양의 데이터를 저장할 필요가 있을 때 비-관계형 데이터베이스가 바람직할까?</h4>
<ol>
<li>수평적 확장성 (Horizontal Scalability)</li>
</ol>
<ul>
<li>전통적인 관계형 데이터베이스(RDBMS)는 대체로 수직적 확장(Scale-up) 방식 → 더 큰 서버, 더 좋은 CPU/메모리 필요.</li>
<li>NoSQL은 기본적으로 수평적 확장(Scale-out) 구조를 지원 → 저렴한 서버 여러 대를 묶어 클러스터링 → 빅데이터 환경에 적합.</li>
</ul>
<ol start="2">
<li>유연한 스키마 (Schema Flexibility)</li>
</ol>
<ul>
<li>RDBMS는 테이블 구조(스키마)가 고정 → 데이터 구조 변경 시 마이그레이션 비용이 큼.</li>
<li>NoSQL은 스키마리스(schema-less) 혹은 유연한 스키마 → 새로운 필드를 자유롭게 추가 가능 → 비정형 데이터(로그, JSON, IoT 센서 데이터 등)에 유리.</li>
</ul>
<ol start="3">
<li>대규모 데이터 처리 성능 (High Throughput)</li>
</ol>
<ul>
<li>RDBMS는 JOIN, 트랜잭션 기능은 강력하지만, 데이터 양이 폭발적으로 많아지면 병목이 발생하기 쉬움.</li>
<li>NoSQL은 읽기/쓰기 성능 최적화에 초점 → 특정 쿼리 패턴(예: key-value 조회, document 조회)에 대해 초고속 응답 제공.</li>
</ul>
<ol start="4">
<li>분산 저장 및 고가용성 (Distributed Storage &amp; High Availability)</li>
</ol>
<ul>
<li>NoSQL은 데이터 복제(Replication)와 샤딩(Sharding)을 기본적으로 지원 → 데이터가 여러 서버에 자동 분산 저장.</li>
<li>장애 발생 시 다른 노드에서 데이터 제공 가능 → <strong>장애 허용성(Fault-tolerance)</strong>이 뛰어남.</li>
</ul>
<h1 id="수직적-규모-확장-vs-수평적-규모-확장">수직적 규모 확장 vs 수평적 규모 확장</h1>
<h2 id="수평적-규모-확장이-더-나은-이유">수평적 규모 확장이 더 나은 이유</h2>
<p>서버로 유입되는 트래픽 양이 적을 때는 수직적 확장이 좋은 방법이다. 하지만 대규모 시스템을 설계할 때는 수평적 규모 확장이 더 나은 방법이다. 이유는 다음과 같다.</p>
<ul>
<li>수직적 규모 확장에는 한계가 있다. 한 대의 서버에 CPU나 메모리를 무한대로 증설할 방법은 없다.</li>
<li>수직적 규모 확장법은 장애에 대한 자동복구 방안이나 다중화 방안을 제시하지 않는다. 서버에 장애가 발생하면 웹사이트/앱은 완전히 중단된다. (고가용성 제공 불가)</li>
</ul>
<h2 id="로드밸런서">로드밸런서</h2>
<p>로드밸런서는 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 한다. 사용자는 로드밸런서의 공개 IP 주소로 접속한다. 따라서 웹 서버는 공개 IP를 가질 필요가 없고, 서버 간 통신에는 사설 IP 주소를 사용하면 된다.</p>
<p>로드밸랜서를 사용하는 이유는 다음과 같다.</p>
<ul>
<li>서버 1이 다운되면 모든 트래픽을 서버 2로 보낸다. 이로써 장애를 대비할 수 있다.</li>
<li>트래픽을 분산시켜 준다.</li>
</ul>
<h2 id="데이터베이스-다중화">데이터베이스 다중화</h2>
<p>데이터베이스 다중화 방식은 주 데이터베이스와 부 데이터베이스 여러대를 구성하는 방식으로 이루어진다. 주로 주 데이터베이스에 쓰기 연산을 하고, 부 데이터베이스에 읽기 연산을 하는 식이다.</p>
<p>데이터베이스 서버 가운데 하나가 다운되면 무슨 일이 벌어질까?</p>
<ul>
<li>부 서버가 한 대 뿐인데 다운되면, 모든 읽기와 쓰기 연산은 주 데이터베이스가 하게된다.</li>
<li>주 데이터베이스 서버가 다운되면, 부 데이터베이스 중 한 대가 주 데이터베이스가 되게 된다. 이렇게 되면 부 데이터베이스에 있는 데이터가 최신 데이터가 아니기 때문에 주 데이터베이스가 복구되면 데이터를 최신화 시켜주거나 다른 방식을 사용하여 최신화 해주어야 한다.</li>
</ul>
<h1 id="캐시">캐시</h1>
<h2 id="캐시-사용-시-유의할-점">캐시 사용 시 유의할 점</h2>
<ul>
<li>캐시는 데이터 갱신은 자주 일어나지 않지만 참조는 빈번하게 일어난다면 고려해볼 만하다.</li>
<li>휘발되어도 괜찮은 데이터만 캐싱한다.</li>
<li>캐시의 ttl 설정은 주의를 기울여야 한다. ttl이 짧으면 hit가 줄어들고, 만료정책이 없으면 메모리에 데이터가 계속 남게된다.</li>
<li>캐시에 저장할 데이터의 원본을 갱신하는 경우 캐시 데이터를 단일 트랜잭션으로 처리하지 않으면 일관성 문제가 발생한다.</li>
<li>캐시 서버도 분산시켜 주어야 한다. (고가용성)</li>
<li>캐시 메모리는 과할당하는 게 좋다.</li>
</ul>
<h3 id="캐시-메모리-과할당">캐시 메모리 과할당</h3>
<p>장점</p>
<ul>
<li>캐시 미스 감소</li>
<li>성능 향상</li>
</ul>
<p>과할당 기준</p>
<ul>
<li>명확한 과할당의 기준은 없지만, 일반적으로 ‘실제 메모리 용량 기준 전체 메모리의 20~30% 이상의 캐시 설정’을 과할당이라고 보는 경우가 있다.</li>
</ul>
<p>주의점</p>
<ul>
<li>지나친 캐시 과할당은 캐시 자체 접근 시간이 증가하고, 메모리 부족으로 인해 다른 프로세스가 느려지거나 스왑이 발생할 수 있다.</li>
</ul>
<h1 id="콘텐츠-전송-네트워크cdn">콘텐츠 전송 네트워크(CDN)</h1>
<p>CDN은 정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크이다. 이미지, 비디오, CSS, JavaScript 파일 등을 캐시할 수 있다.</p>
<p>CDN을 사용하면 서버로 부터 멀리 떨어진 해외에서도 정적 컨텐츠에 대한 접근 속도가 향상 된다.</p>
<h2 id="aws의-cdn-서비스">AWS의 CDN 서비스</h2>
<p>CloudFront는 AWS의 CDN 서비스이다. 사용자가 CloudFront에서 전송하는 콘텐츠를 요청할 경우 요청이 가까운 엣지 로케이션으로 라우팅된다.</p>
<p>CloudFront에는 EC2의 DNS주소를 입력하여 EC2에서 운영 중인 웹서버의 정적 컨텐츠를 캐싱할 수 있다.</p>
<p>CloudFront를 사용함으로서 비용을 절감할 수 있다. EC2는 아웃바운드 트래픽에 대해 과금당하고, S3는 데이터 업로드와 다운로드 모두 과금당한다. CloudFront를 사용하면 서버에 요청이 오기전에 캐싱하므로 비용 절감을 할 수 있다.<br><img src="https://velog.velcdn.com/images/62hoon99/post/69850951-c6e0-48b7-8082-63e0f188ff50/image.png" alt="aws cdn"></p>
<h2 id="cdn-사용-시-고려해야-할-사항">CDN 사용 시 고려해야 할 사항</h2>
<ul>
<li>비용: 자주 사용되지 않는 컨텐츠를 캐싱하는 것은 이득이 크게 되지 않으므로, 과금을 피하기 위해서는 CDN에서 빼는 게 좋다.</li>
<li>적절한 만료 설정: 시의성이 중요한 콘텐츠의 경우 만료 시점을 잘 정해야 한다.</li>
<li>CDN 장애에 대한 대처 방안: CDN 자체가 죽었을 경우 해당 문제를 감지하여 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트를 구성하는 것이 필요하다.</li>
</ul>
<h1 id="무상태-웹-계층">무상태 웹 계층</h1>
<p>웹 계층을 수평적으로 확장하기 위해서는 상태 정보(ex. 사용자 세션 데이터)를 웹 계층에서 제거해야 한다.</p>
<h2 id="상태-정보-의존적인-아키텍쳐">상태 정보 의존적인 아키텍쳐</h2>
<ul>
<li>웹 계층에서 상태 정보를 저장한다면, 클라이언트는 정해진 웹 서버로만 통신해야 한다.</li>
<li>상태 정보 의존적인 웹 서비스를 위해서 대부분의 로드밸런서는 고정 세션이라는 기능을 제공한다. 하지만 이는 로드밸런서에 부담을 준다.</li>
</ul>
<h2 id="무상태-아키텍쳐">무상태 아키텍쳐</h2>
<ul>
<li>웹 서버는 상태 정보가 필요한 경우 공유 저장소로부터 데이터를 가져오도록 해야 한다.<ul>
<li>로그인으로만 놓고 보자면 토큰을 사용하는 방법으로 개선할 수도 있다.</li>
</ul>
</li>
<li>세션 데이터는 NoSQL을 사용하면 이점이 있다. 트래픽 양에 다라서 자동 규모 확장이 자유롭기 때문이다.</li>
</ul>
<h1 id="데이터-센터">데이터 센터</h1>
<ul>
<li>데이터 센터는 여러 데이터 센터를 이용하는 것이 좋다. 천재지변으로 인하여 데이터센터A가 마비되면 데이터센터B를 사용할 수 있도록 해야 한다.</li>
<li>AWS는 한 리전(Region) 내 여러 가용 영역(AZ, Availability Zone)이라는 독립적인 데이터센터를 제공한다. 각 AZ는 별도의 전원, 네트워킹, 보안 시스템을 갖추고 물리적으로 분리되어 있다.</li>
<li>EC2, RDS 등 주요 서비스 배포 시, 하나의 AZ 또는 여러 AZ에 분산 배치할지 사용자가 직접 선택한다. AZ에 리소스를 분산하라고 “권장”하지만, 반드시 여러 AZ에 분산해야 서비스를 쓸 수 있는 식의 “강제”는 하지 않는다.</li>
</ul>
<h1 id="메시지-큐">메시지 큐</h1>
<p>발행자가 메시지를 만들어 메시지 큐에 발행한다. 큐에는 보통 소비자 혹은 구독자라 불리는 서비스 혹은 서버가 연결되어 있는데, 메시지를 받아 그에 맞는 동작을 수행하는 역할을 한다.
메시지 큐의 장점</p>
<ul>
<li>API를 쓰면 장애가 전파된다. 근데 메시지 브로커는 그렇지 않다. 이벤트만 전송하면 된다.</li>
<li>시스템 결합도가 낮아진다.</li>
</ul>
<h1 id="로그-메트릭-자동화">로그, 메트릭, 자동화</h1>
<ul>
<li>로그: 에러 로그를 모니터링 해야 한다. 대규모 시스템에서 로그 수집이 필수다.<ul>
<li>직접 구축: 엘라스틱서치, 키바나 사용</li>
<li>제품: DataDog</li>
</ul>
</li>
<li>메트릭: 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수도 있다.<ul>
<li><a href="https://medium.com/29cm/29cm-%EC%9D%98-%EC%9D%B4%EA%B5%BF%EC%9C%84%ED%81%AC-%EC%9E%A5%EC%95%A0%EB%8C%80%EC%9D%91-%EA%B8%B0%EB%A1%9D-177b6b2f07a0">https://medium.com/29cm/29cm-의-이굿위크-장애대응-기록-177b6b2f07a0</a></li>
</ul>
</li>
<li>자동화: 빌드, 테스트, 배포 등의 절차를 자동화하여 개발 생산성을 크게 향상시킬 수 있다.</li>
</ul>
<h1 id="데이터베이스의-규모-확장">데이터베이스의 규모 확장</h1>
<p>저장할 데이터가 많아지면 데이터베이스를 증설할 방법을 찾아야 한다.
데이터베이스 확장도 웹 서버와 마찬가지로 수직적 확장과 수평적 확장이 있다.</p>
<h2 id="수평적-확장">수평적 확장</h2>
<p>데이터베이스의 수평적 확장은 샤딩이라고도 부른다. 샤딩은 대규모 데이터베이스를 샤드라고 부르는 작은 단위로 분할하는 기술을 일컫는다. 모든 사드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다.</p>
<p>샤딩을 도입하면 시스템이 복잡해지고 풀어야 할 새로운 문제도 생긴다.</p>
<ul>
<li>데이터의 재 샤딩: 데이터가 너무 많아져서 하나의 샤드로는 더이상 감당하기 어려울 때. 샤드 소진이라고 부르는 이러한 현상이 발생하면 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치하여야 한다.</li>
<li>유명인사 문제: 커뮤니티를 예로 들면 인기글이 하나의 샤드에 쏠리는 경우 발생할 수 있는 문제다. 해결하기 위해서는 유명인사 각각에 샤드를 할당해야 할 수도 있다.</li>
<li>조인과 비정규화: 샤드를 물리적으로 나누게 되면 데이터를 조인하기가 어렵다.</li>
</ul>
<h3 id="샤딩">샤딩</h3>
<ul>
<li>수직 샤딩, 수평 샤딩 기법이 있고, 물리적 샤딩과 논리적 샤딩 기법이 있다.</li>
<li>샤딩은 데이터를 분산하기 위한 방법이다. 가용성을 위한 master-slave 와는 다르다.</li>
<li>샤딩은 해싱 키를 뭐로 쓸지 정하는 게 중요하다. <ul>
<li>게시판 DB 설계할 때 해싱 키는 게시판 id를 해싱 키로 잡는 게 좋다. dc inside를 예로들면 ‘야구 갤러리’에 글이 있고 글 안에 댓글이 있다. 따라서 게시판(갤러리) 기준으로 해싱키를 잡으면 좋음. 만약 글의 id를 해싱키로 잡았다면 게시판을 기준으로 게시글을 조회할 때 모든 db를 다 조회해야 할 수도 있다.</li>
</ul>
</li>
<li>샤딩을 할 때 PK로는 Snowflake 알고리즘을 사용한 키를 사용하는게 좋다. Snowflake 알고리즘은 오름차순 &amp; 유니크 숫자를 만들기 위한 알고리즘이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책 내용 정리] 개발자를 위한 글쓰기 가이드(2)]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B0%80%EC%9D%B4%EB%93%9C2</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B0%80%EC%9D%B4%EB%93%9C2</guid>
            <pubDate>Sat, 23 Aug 2025 06:26:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개발자를 위한 글쓰기 가이드 - 유영경</p>
</blockquote>
<h1 id="메일-작성">메일 작성</h1>
<h2 id="받는-사람을-명확하게-지정한다">받는 사람을 명확하게 지정한다</h2>
<p><strong>받는 사람</strong>
메일 내용을 반드시 알아야 할 사람, 메일을 받고 업무를 진행해야 하는 사람</p>
<p><strong>참조</strong>
메일 내용을 알아두면 좋은 사람, 지금 바로 일을 해야 하는 것은 아니지만 관련 있는 조직 담당자 등을 지정한다.</p>
<p><strong>숨은 참조</strong>
메일 내용을 알아 두면 좋지만, &#39;받는 사람&#39;이나 &#39;참조&#39;에 있는 사람에게 굳이 존재를 알리거나 메일 주소를 알릴 필요가 없을 때 사용한다.</p>
<h2 id="회의록-작성-원칙을-기억한다">회의록 작성 원칙을 기억한다</h2>
<p><strong>미리 회의 안건을 공유한다.</strong>
특히 의사 결정을 해야 하는 안건이 있다면 참석자 각작의 의견을 정리해 올 수 있게 필요한 데이터를 전달한다.</p>
<p><strong>안건별로 담당자와 일정을 정한다.</strong>
후속 회의를 줄이려면 해야 할 일을 명확하게 정해야 한다.</p>
<p><strong>회의록에는 요점만 정리한다.</strong>
각 안건을 어떻게 결정했는지와 토론 사항을 요약해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/62hoon99/post/635cb50c-dd53-42f0-9e85-283e83ed49e8/image.JPG" alt=""></p>
<p><img src="https://velog.velcdn.com/images/62hoon99/post/40912f6c-f6d4-49c9-83d7-2670a8b756ba/image.JPG" alt=""></p>
<h1 id="오류와-확인-메시지-작성">오류와 확인 메시지 작성</h1>
<h2 id="오류-메시지에-중요한-것을-해결-방법">오류 메시지에 중요한 것을 해결 방법</h2>
<p>오류 메시지
확인 메시지와 달리 오류 메시지는 작업을 완료할 수 없는 문제를 설명하기 위해 표시하는 텍스트이다.</p>
<h3 id="좋은-오류-메시지-작성하기">좋은 오류 메시지 작성하기</h3>
<p>오류 메시지 3요소: 상태, 원인, 해결 방법</p>
<ul>
<li>상태: 사용자 관점에서 문제가 발생한 상태를 설명하는 것</li>
<li>원인: 문제가 발생한 이유</li>
<li>해결 방법: 문제를 어떻게 해결해야 하는지 알기 쉽게 설명한다.</li>
</ul>
<p>좋은 오류 메시지 예시(1)</p>
<blockquote>
<p>저장 공간이 부족하여 &lt;- 원인
파일을 업로드하지 못했습니다. &lt;- 상태
저장 공간을 확보한 후 다시 시도해 주세요. &lt;- 해결 방법</p>
</blockquote>
<p>좋은 오류 메시지 예시(2)</p>
<blockquote>
<p>저장 공간이 부족하여 &lt;- 원인
파일을 업로드하지 못했습니다. &lt;- 상태
사용하지 않는 파일을 삭제하고 &lt;- 해결 방법
다시 시도해 주세요.</p>
</blockquote>
<h2 id="직관적인-버튼-텍스트를-만든다">직관적인 버튼 텍스트를 만든다.</h2>
<blockquote>
<p>휴지통 비우기
휴지통에 있는 모든 항목이 완전히 삭제됩니다.
                        [취소] [확인]</p>
</blockquote>
<p>위와 같이 작성해도 되지만 조금 더 직관적인 방법을 생각해 본다면, 버튼을 보고 다음 동작을 바로 알 수 있게 바꾸면 좋다.</p>
<blockquote>
<p>휴지통 비우기
휴지통에 있는 모든 항목이 완전히 삭제됩니다.
                        [취소] [휴지통 비우기]</p>
</blockquote>
<h2 id="장애-공지문의-기본-요소">장애 공지문의 기본 요소</h2>
<ul>
<li>장애 발생 시각과 지속 시간: 장애가 언제 발생했고 얼마나 지속됐는지를 알린다.</li>
<li>장애 발생 원인: 장애가 발생한 주요 원인을 적는다.</li>
<li>사용자 불편에 공감: 장애로 사용자가 입은 손해와 불편에 진심으로 공감한다.</li>
</ul>
<h1 id="사용자-가이드-작성">사용자 가이드 작성</h1>
<h2 id="사용자에게-맞는-가이드-종류를-선택한다">사용자에게 맞는 가이드 종류를 선택한다</h2>
<p>사용자 가이드에 있어야 할 항목</p>
<ul>
<li>사용자가 제품을 사용해 할 수 있는 일</li>
<li>제품을 사용하기 위해 알아야 할 사전 지식이나 참고 사항</li>
<li>업무별 사용 방법</li>
<li>실제 사용 예와 샘플 코드</li>
<li>추가 내용을 학습할 수 있는 참고 사이트</li>
<li>내용을 효율적으로 전달할 수 있는 스크린숏, 다이어그램, 차트 등</li>
</ul>
<h2 id="개념과-목적을-설명하는-개요를-추가한다">개념과 목적을 설명하는 개요를 추가한다</h2>
<p>문서 개요에 있어야 할 항목</p>
<ul>
<li>문서 정의</li>
<li>문서 목표</li>
<li>문서 독자</li>
<li>문서 변경 이력</li>
<li>문서에서 사용한 특정 스타일 소개</li>
<li>문서 내용 관련 문의처</li>
<li>문서 저작권</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책 내용 정리] 개발자를 위한 글쓰기 가이드 (1)]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Mon, 11 Aug 2025 11:26:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개발자를 위한 글쓰기 가이드 - 유영경</p>
</blockquote>
<h1 id="테크니컬-라이팅-5단계">테크니컬 라이팅 5단계</h1>
<ol>
<li>계획 세우기: 독자를 명확히 하기</li>
<li>구조 잡기: 계획 단계에서 수집한 정보를 작업 순서에 따라 차례대로 배열</li>
<li>초안 작성: 전달할 정보를 모두 넣는 것에 초점. 내용에만 집중</li>
<li>검토와 재작성: 초안 작성 후 다시 읽어 보며 고치는 단계. 이 단계에 집중 필요</li>
<li>배포: 문서를 배포하는 단계</li>
</ol>
<h1 id="1단계-계획-세우기">1단계 계획 세우기</h1>
<h2 id="대상-독자-정하기">대상 독자 정하기</h2>
<ul>
<li>누구를 위해 글을 쓰는지 명확하게 정해야 내용의 깊이 조절 가능</li>
<li><strong>대상 독자 정하는 쉬운 방법: 대상의 직무를 확인</strong><h2 id="설명할-기술의-깊이를-조절하라">설명할 기술의 깊이를 조절하라</h2>
</li>
<li>대상 독자의 직무에 따라 전문 용어에 대한 설명은 달라져야 한다.</li>
<li>개발자와 비개발자 모두가 대상인 글을 쓸 때는 각 독자 수준에 맞춰 일단 작성하고 각 직무마다 필요한 추가 설명은 따로 작성한다.<h2 id="주제를-구체적으로-정하라">주제를 구체적으로 정하라</h2>
</li>
<li><strong>주제를 구체적으로 좁혀 나가야 한다.</strong> 예를 들어, &#39;GitHub 사용법&#39;이라고 주제를 정하면 GitHub 백과사전을 만들어야 하지만 &#39;GitHub를 사용한 효율적인 문서 검토 방법&#39;으로 <strong>주제를 정하면 글의 범위가 명확해진다.</strong></li>
</ul>
<h1 id="2단계-초안-작성">2단계 초안 작성</h1>
<h2 id="일단-쓴다">일단 쓴다</h2>
<ul>
<li>흐름도 이상한 것 같고 정확한 내용인지 몰라도 그냥 쓴다.</li>
<li>맞춤법 확인도 하지 않는다.<h2 id="명확성-간결성-일관성-3원칙">명확성, 간결성, 일관성 3원칙</h2>
</li>
<li><strong>명확성</strong>: 개발 업무 관련된 글은 단호하고 명확한 설명과 표현을 사용해야 한다.</li>
<li><strong>간결성</strong>: 원하는 정보를 빠르게 알리려면 문장을 간결하게 써야 한다.</li>
<li><strong>일관성</strong>: 문서 전체에서 설명하는 내용이 일관돼야 한다. 같은 의미의 용어나 설명 방법도 일관되게 유지해야 한다.<h2 id="핵심부터-쓴다">핵심부터 쓴다</h2>
</li>
<li>제품이나 서비스 가이드와 같이 정보를 전달해야 하는 문서에서는 <strong>핵심 내용을 제일 앞에</strong> 써야 한다.<h3 id="역피라미드-방식">역피라미드 방식</h3>
</li>
<li>역피라미드 글쓰기 방식은 결론, 핵심, 주제부터 제시하고 나서 근거나 데이터를 설명하는 방식을 말한다.</li>
<li><strong>중요한 내용을 문서 앞부분에서 설명하고 덜 중요한 내용을 차례로 배치하는 것</strong></li>
</ul>
<h2 id="제목에-요점을-담는다">제목에 요점을 담는다</h2>
<ul>
<li>제목 아래 단락의 요점을 압축해 제목으로 쓰면된다.</li>
<li>명사로만 제목을 짓는 것은 좋지 않다. (예: 소셜 네트워크 게임 플랫폼 동향 -&gt; 소셜 네트워크 게임 플랫폼을 활용한 게임 제작 동향)<h2 id="객관적인-근거를-댄다">객관적인 근거를 댄다</h2>
</li>
<li><strong>수치 데이터를 제시하면 효과적이다.</strong> (예: A 서버보다 B 서버에서 파일을 로딩하는 속도가 훨씬 빨랐다. -&gt; 파일을 로딩할 때 A 서버를 사용하면 1.5초, B 서버를 사용하면 0.9초가 결렸다.)</li>
<li>기술 문서에는 추측성 주장이나 입증되지 않은 사실을 적지 않아야 한다.</li>
<li><strong>객관적인 수치나 근거를 제시해야 글의 신뢰도가 높아</strong>진다.<h2 id="용어는-일관되게-사용한다">용어는 일관되게 사용한다</h2>
</li>
<li>같은 어휘를 반복하게 사용하는 것이 지루하고 식상하게 느껴지지 않을까 걱정하지 않아도된다. 테크니컬 라이팅은 정보를 빠른 시간 안에 전달하는 것이 목적이다.</li>
<li>(보안그룹, 시큐리티 그룹), (인스턴스, instance), (로드밸런서, Load Balancer) 모두 같은 용어이다. 하나로만 통일해서 사용하라</li>
<li><strong>설명 방법도 일관성을 유지하라.</strong> &#39;화면에서 앱을 선택하라&#39;, &#39;화면에서 앱을 터치하라&#39;, &#39;화면에서 앱을 탭하라&#39; 모두 같은 의미이다. 정답은 없으니 일관되게만 작성하라<h2 id="쉽게-쓴다">쉽게 쓴다</h2>
</li>
<li>문서라고 해서, 형식적으로 써야 한다고 해서 평소에 잘 쓰지 않는 단어를 쓰거나 문장을 길게 해야 하는 것이 아니다.</li>
<li><strong>옆 사람에게 말하듯이 써봐라</strong> (예: &#39;화면 상단 우측 위에 위치한 X 버튼을 클릭하면 창이 닫히는 것을 확인할 수 있다.&#39; -&gt; &#39;화면 오륵쪽 위에 있는 X 버튼을 클릭하면 창이 닫힌다.&#39;)</li>
</ul>
<h1 id="3단계-시각화-요소로-가독성-높이기">3단계 시각화 요소로 가독성 높이기</h1>
<h2 id="목록을-사용해-정리한다">목록을 사용해 정리한다</h2>
<ul>
<li>점 목록: 문장 안의 여러 항목을 순서 상관없이 나열할 때 점 목록을 사용하면 가독성이 좋아진다.</li>
<li>점 목록을 사용할 때는 각 항목이 문법적으로 일관성을 가져야 한다.</li>
<li>점 목록을 남발하지 않는다: 각 점 목록이 대등한 관계일 때만 사용하라</li>
<li>번호 목록: 번호 목록은 순서가 중요할 때 사용한다.</li>
</ul>
<h2 id="스크린샷으로-이해도-높이기">스크린샷으로 이해도 높이기</h2>
<ul>
<li>필요한 부분만 잘라서 넣는다: 필요한 부분만 캡처한다. 특히 화면의 텍스트를 참고해야 할 때는 텍스트가 잘 보이게 해야 한다.</li>
<li>그림 크기를 일관되게 지정한다</li>
<li>입력값을 채우고 캡처한다</li>
<li>스크린샷 위에 텍스트를 추가하지 않는다: 텍스트를 추가할 거면 그림 바깥쪽에 따로 빼서 작성한다.</li>
</ul>
<h2 id="정보를-비교할-때는-표를-활용한다">정보를 비교할 때는 표를 활용한다</h2>
<ul>
<li>여러 제품의 장단점을 항목별로 비교할 때, 옵션별로 간단한 설명을 작성할 때 표를 사용하면 가독성을 높일 수 있다.</li>
</ul>
<h3 id="표가-적합하지-않은-경우">표가 적합하지 않은 경우</h3>
<ul>
<li><strong>표에 행이 1개일 때는 표보다 다른 형식을 고려하는 편이 좋다.</strong></li>
<li><strong>문장 중간에는 표를 넣지않는다.</strong></li>
<li><strong>표 열이 1개일 때는 표보다 목록을 사용한다.</strong></li>
</ul>
<h2 id="데이터-성격에-맞는-차트를-사용한다">데이터 성격에 맞는 차트를 사용한다</h2>
<ul>
<li>선형 차트: 데이터 간 상관관계를 나타낼 때 많이 사용한다.</li>
<li>막대형 차트: 데이터 여러 개의 관계를 나타내는 데 주로 사용한다.</li>
<li>파이형 차트: 비교하는 항목이 전체 중 어느 정도, 몇 %를 차지하는지 한눈에 파악하고 싶을 때 사용한다.</li>
</ul>
<h3 id="시각-자료를-쓰기-전에-소개부터-한다">시각 자료를 쓰기 전에 소개부터 한다</h3>
<ul>
<li>이미지와 목록뿐 아니라 표, 차트, 샘플 코드가 나올 때도 어떤 의도로 사용했는지 소개하는 것을 잊지 않아야 한다.</li>
<li>시각 자료를 설명하는 캡션을 활용한다. 그림 캡션에는 &#39;그림 + 숫자 번호 + 그림 내용&#39; 순서를 입력하는 것이 좋다. (예: &#39;그림 1 메일 환경 설정&#39;)</li>
</ul>
<h1 id="4단계-검토와-재작성">4단계 검토와 재작성</h1>
<h2 id="객관적으로-문서를-검토한다">객관적으로 문서를 검토한다</h2>
<p>간단하면서 효과적인 방법은 다음과 같다.</p>
<ul>
<li>소리 내어 읽기</li>
<li>시간을 두고 읽기</li>
<li>온라인 문서라면 인쇄해서 읽기</li>
</ul>
<h2 id="은어는-형식적인-표현으로-바꾼다">은어는 형식적인 표현으로 바꾼다</h2>
<ul>
<li>&#39;로그를 까다&#39; -&gt; &#39;로그를 확인하다&#39;</li>
<li>&#39;무거운 프로그램&#39; -&gt; 실행 속도가 느린 프로그램&#39;</li>
<li>&#39;로직을 태우다&#39; -&gt; &#39;로직을 적용하다&#39;</li>
<li>&#39;에러를 잡다&#39; -&gt; &#39;오류를 수정하다&#39;</li>
<li>&#39;창이 뜨면&#39; -&gt; &#39;창이 나타나면&#39;</li>
<li>&#39;한글이 깨지다&#39; -&gt; &#39;한글이 제대로 나타나지 않다&#39;</li>
</ul>
<h2 id="대명사는-일반-명사로-바꾼다">대명사는 일반 명사로 바꾼다</h2>
<p>&#39;이를 통해&#39;를 쓰지 않는다.
예: <code>업무 캘린더를 사용하면 ... 또한 이를 통해 프로젝트 진척도도 관리할 수 있습니다.</code> -&gt; <code>업무 캘린더를 사용하면 ... 업무 캘린더에서 프로젝트 진척도도 관리할 수 있습니다.</code></p>
<h2 id="고유한-이름은-정확히-쓴다">고유한 이름은 정확히 쓴다</h2>
<p>문서를 쓰다 보면 고유한 이름이 나오는데 이는 정확히 고유한 이름을 쓴다.</p>
<ul>
<li>&#39;MS&#39; -&gt; &#39;Microsoft&#39;</li>
<li>&#39;Win10&#39; -&gt; &#39;Windows 10&#39;</li>
<li>&#39;구글&#39; -&gt; &#39;Google&#39;</li>
<li>&#39;크롬&#39; -&gt; &#39;Chrome&#39;</li>
<li>&#39;리눅스&#39; -&gt; &#39;Linux&#39;</li>
</ul>
<h2 id="단정적인-어조로-확신-있게-쓴다">단정적인 어조로 확신 있게 쓴다</h2>
<p><strong>기술 문서는 독자에게 정확한 사실을 전달한다는 믿음을 주어</strong>야 한다. 이럴 수도 있고 저럴 수도 있는 내용을 담으면 안 되며, 단정적인 어조를 유지해야 한다.</p>
<ul>
<li><code>열기를 클릭하면 새 창이 열리게 됩니다.</code> -&gt; <code>열기를 클릭하면 새 창이 열립니다.</code></li>
<li><code>~~ 경우에 &#39;결제 복구 기능&#39;을 사용하면 좋을 듯합니다.</code> -&gt; <code>~~~ 경우에 &#39;결제 복구 기능&#39;을 사용합니다.</code></li>
</ul>
<h2 id="글꼬리를-뚜렷하게-쓴다">글꼬리를 뚜렷하게 쓴다</h2>
<ul>
<li>&#39;<del>~</del>유일한 방법이라고는 말할 수는 없지 않을까 싶다.&#39; -&gt; &#39;유일한 것은 아니다&#39;</li>
<li>&#39;<del>~</del>해결될 것이라 판단되는 바이다.&#39; -&gt; &#39;해결될 것이다.&#39;</li>
<li>&#39;<del>크게 세 가지로 갈라 볼 수 있다.&#39; -&gt; &#39;</del>세 가지다.&#39;</li>
</ul>
<h2 id="주어와-서술어를-일치시킨다">주어와 서술어를 일치시킨다</h2>
<ul>
<li>&#39;이 서비스가 가진 장점은 사용자에게 편리함을 줄 수 있다.&#39; X</li>
<li>&#39;이 서비스의 장점은 사용자에게 편리함을 주는 것이다.&#39; O</li>
</ul>
<h2 id="문장을-짧게-줄인다">문장을 짧게 줄인다</h2>
<ul>
<li>중복되는 단어가 있으면 없앤다.</li>
<li><strong>괜히 덧붙인 말은 없앤다.</strong> (예: &#39;활성화 작업 과정을 거치지&#39;에서 &#39;작업&#39;이나 &#39;과정&#39;을 빼도 의미가 통한다. -&gt; &#39;활성하 하지 않아도&#39;)</li>
<li><strong>필요 없는 조사를 없앤다.</strong> (예: &#39;제공이 되며&#39; -&gt; &#39;제공되며&#39;)</li>
</ul>
<h2 id="군더더기-표현을-없앤다">군더더기 표현을 없앤다</h2>
<h3 id="발생">발생</h3>
<p><code>인증서 도메인당 월 8만 원의 비용이 발생한다.</code> 
<code>인증서 비용은 도메인당 월 8만원 입니다.</code></p>
<h3 id="필요">필요</h3>
<p><code>서비스 활성화를 진행하기 위해서는 먼저 콘솔 로그인이 필요합니다.</code> 
<code>서비스를 활성화하려면 먼저 콘솔에 로그인해야 합니다.</code></p>
<h3 id="진행">진행</h3>
<p><code>원하는 파일 유형을 선택해 다운로드 진행해 주세요.</code> 
<code>원하는 파일 유형을 선택해 다운로드해 주세요.</code></p>
<h2 id="피동태보다-능동태로-쓴다">피동태보다 능동태로 쓴다</h2>
<ul>
<li>피동태: 행동의 주체를 문장의 주어로 두고 이에 맞는 서술어를 쓰는 것이 아니라, 사물이나 관념을 주어로 두고 이에 맞게 서술어를 변형시켜 쓰는 것을 말한다.<h3 id="예시">예시</h3>
<code>Python은 귀도 반 로섬에 의해 개발되었습니다.</code>
<code>Pyhon은 귀도 반 로섬이 개발했습니다.</code></li>
</ul>
<h2 id="복잡한-번역체를-다듬는다">복잡한 번역체를 다듬는다</h2>
<ul>
<li>&#39;<del>에 대해&#39;: &#39;</del>에 대해&#39;는 영어에서 &#39;<del>about&#39;를 번역한 표현이다. &#39;</del>을(를)로 바꿔 간결하게 쓸 수 있다.</li>
<li>&#39;<del>에 의해&#39;: &#39;</del>에 의해&#39;라는 표현 역시 &#39;by&#39;를 사용한 번역체다.</li>
<li>그 외 번역체: (&#39;<del>을 통해&#39; -&gt; 
&#39;으로&#39;), (&#39;가능, 불가능하다&#39; -&gt; &#39;</del>을 할 수 있다&#39;)</li>
</ul>
<h2 id="통해는-명확한-표현으로-바꾼다">&#39;통해&#39;는 명확한 표현으로 바꾼다</h2>
<p><strong>&#39;통해&#39;하나로 여러 가지 의미를 대체하면 분명한 문장을 만들기 어렵다.</strong></p>
<p><code>이번 테스트에서 나타난 문제점 파악을 통해 부족한 기능을 보완하면...</code> -&gt; <code>이번 테스트에서 나타난 문제점을 파악해 부족한 기능을 보완하면...</code></p>
<p><code>네트워크를 통해 전송합니다.</code> -&gt; <code>네트워크로 전송합니다.</code></p>
<h2 id="자주-틀린는-문장-부호">자주 틀린는 문장 부호</h2>
<h3 id="큰따옴표">큰따옴표(&quot;&quot;)</h3>
<ul>
<li>큰타옴표는 낱말이나 문장을 직접 인용할 때 사용한다.</li>
<li>책 제목, 신문 이름을 나타낼 때도 사용한다.<h3 id="작은따옴표">작은따옴표(&#39;&#39;)</h3>
</li>
<li>문장의 중요한 부분을 강조할 때 사용한다.</li>
<li>인용한 말 안에 있는 인용한 말을 나타낼 때 사용한다.<h3 id="소괄호">소괄호</h3>
</li>
<li>소괄호는 보충할 내용을 덧붙일 때, 우리말 표기와 원어 표기를 같이 쓸 때 사용한다.</li>
<li>주석이나 보충 내용을 덧붙일 때 사용한다.</li>
<li>우리말 표기와 원어 표기를 같이 쓸 때 사용한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드 개발 실무 지식 (5)]]></title>
            <link>https://velog.io/@dev-gromit/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-%EC%8B%A4%EB%AC%B4-%EC%A7%80%EC%8B%9D-5</link>
            <guid>https://velog.io/@dev-gromit/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-%EC%8B%A4%EB%AC%B4-%EC%A7%80%EC%8B%9D-5</guid>
            <pubDate>Sun, 10 Aug 2025 04:42:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>주니어 백엔드 개발자가 반드시 알아야 할 실무 지식</p>
</blockquote>
<h1 id="9장-최소한-알고-있어야-할-서버-지식">9장 최소한 알고 있어야 할 서버 지식</h1>
<h2 id="개발자와-서버">개발자와 서버</h2>
<ul>
<li>서버라는 단어는 다양한 대상을 의미한다. 서버 프로그램을 구동하는 OS를 &#39;서버&#39;, 아파치나 톰캣 같은 프로그램은 &#39;서버 프로그램&#39;이라고 부르자.</li>
</ul>
<h2 id="os-계정과-권한-linux">OS 계정과 권한 (Linux)</h2>
<ul>
<li>root 계정은 OS를 설치하면 기본 생성되는 계정으로 모든 권한을 가진 관리자 계정이다.</li>
<li>모든 것을 다 할 수 있기 때문에 root 계정에 접근할 수 있는 인원에 제한을 둔다.</li>
<li>파일을 실행할 때 접근 거부가 발생하는 이유는 보통 읽기 권한이나 쓰기 실행 권한이 없기 때문이다.</li>
<li>ls -l 명령어를 사용하면 권한을 포함한 여러 정보를 확인할 수 있다.</li>
</ul>
<h3 id="sudo로-권한-주기">sudo로 권한 주기</h3>
<ul>
<li>일반적으로 운영체제의 root 권한은 일부 인프라 담당자만 갖고, 개발자는 일반 계정에 대한 권한만 갖는다.</li>
<li><strong>하지만 개발자도 root 권한이 필요한 경우가 있는데 이럴 때 매번 인프라 담당자한테 작업 요청을 할 수도 없으니 사용하는 명령어가 sudo 이다.</strong></li>
<li><strong>sudo 명령어를 사용하면 다른 사용자의 권한으로 프로그램을 실행할 수 있다.</strong></li>
<li>user1이라는 계정에 sudo로 실행할 수 있는 명령어를 지정해주면 user1이 sudo로 해당 명령어를 쓸 수 있다.</li>
</ul>
<h2 id="네트워크-정보-확인">네트워크 정보 확인</h2>
<h3 id="nc-명령어로-연결-확인하기">nc 명령어로 연결 확인하기</h3>
<ul>
<li>특정 포트로 연결이 잘 되는지 확인할 때 사용할 수 있는 명령어로 nc가 있다.</li>
<li><code>nc -z -v www.daum.net 443</code> 명령어는 443 포트로 연결이 잘 되는지 확인하는 명령어의 예시이다.</li>
<li>UDP 포트가 열려있는지 확인해보려면 -u 옵션을 사용하면 된다.</li>
</ul>
<h3 id="netstat-명령어로-포트-사용-확인">netstat 명령어로 포트 사용 확인</h3>
<ul>
<li><code>netstat -lputn</code> 명령어를 사용하면 현재 서버에서 열려 있는 서버 포트를 확인할 수 있다.</li>
<li><code>netstat -anp | grep 12931</code> 현재 사용 중인 전체 포트를 확인하고 싶다면 -a 옵션을 확인하면 되고, 다음처럼 -anp 명령어와 grep을 함께 사용하면 현재 사용 중인 포트를 확인할 수 있다.</li>
</ul>
<h1 id="10장-모르면-답답해지는-네트워크-기초">10장 모르면 답답해지는 네트워크 기초</h1>
<h2 id="ip-주소와-도메인">IP 주소와 도메인</h2>
<h3 id="고정-ip와-동적-ip">고정 IP와 동적 IP</h3>
<ul>
<li>고정 IP는 말 그대로 노드가 고정된 IP를 갖는다. 고정 IP를 사용하는 노드는 IP 주소를 직접 지정한다.</li>
<li>동적 IP는 노드가 네트워크에 연결할 때마다 IP를 할당한다. 동적 IP는 DHCP 서버를 통해 제공받는다. 가정에서 사용하는 공유기가 주로 동적 IP 방식을 사용한다.</li>
</ul>
<h2 id="nat">NAT</h2>
<ul>
<li>SNAT: 내부 네트워크에서 나가는 패킷의 사설 IP를 공인 IP로 변환한다.</li>
<li>DNAT: 공인 IP로 들어온 패킷의 목적지를 사설 IP로 변환한다.</li>
</ul>
<h1 id="부록a-처음-해보는-성능-테스트를-위한-기본-정리">부록A 처음 해보는 성능 테스트를 위한 기본 정리</h1>
<h2 id="성능-테스트-종류">성능 테스트 종류</h2>
<ul>
<li>부하 테스트: 특정한 예상 부하에서 시스템이 어떻게 동작하는지 확인한다.</li>
<li>스트레스 테스트: 시스템의 최대 성능을 확인하기 위한 테스트. 예상을 뛰어넘는 부하 발생</li>
<li>지속 부하 테스트: 시스템이 지속적인 부하를 견딜 수 있는 지를 검증한다.</li>
<li>스파이크 테스트: 급격하게 트래픽이 변화할 때 시스템의 반응성과 안정성을 검증하는 테스트</li>
</ul>
<h2 id="포화점과-버클존">포화점과 버클존</h2>
<ul>
<li>포화점: 성능이 저하되기 전의 최대 처리량</li>
<li>버클존: 포화점을 지나 성능이 걲이기 시작하는 구간</li>
</ul>
<h2 id="주요-측정-지표">주요 측정 지표</h2>
<h3 id="응답-시간">응답 시간</h3>
<ul>
<li>평균</li>
<li>최대</li>
<li>최소</li>
<li>중앙</li>
<li>99%나 95% 백분위</li>
</ul>
<h3 id="처리량">처리량</h3>
<ul>
<li>TPS(초당 트랜잭션 건수)처럼 초 단위로 얼마나 많은 요청을 처리했는지를 나타낸다. 테스트를 진행하는 동안 처리량은 변화하므로 최대, 평균, 최소 값을 함께 구한다.</li>
</ul>
<h2 id="성능-테스트-도구">성능 테스트 도구</h2>
<ul>
<li>nGrinder 추천</li>
</ul>
<h1 id="부록b-nosql-이해하기">부록B NOSQL 이해하기</h1>
<h2 id="nosql-사용하는-주된-이유">NoSQL 사용하는 주된 이유</h2>
<ul>
<li>대용량 데이터나 분산 처리</li>
<li>고속의 읽기와 쓰기 성능</li>
<li>특정한 요구사항에 맞는 데이터 설계</li>
<li>비정형 데이터 처리 또는 유연한 스키마</li>
</ul>
<h2 id="nosql-종류">NoSQL 종류</h2>
<h3 id="키-값-db">키-값 DB</h3>
<ul>
<li>대표적으로 Redis</li>
<li>주된 용도: 세션 관리, 캐시, 설정 관리</li>
<li>레디스는 큐 기능을 제공하고 있어 메시징 시스템으로도 활용이 가능하다.</li>
</ul>
<h3 id="문서-db">문서 DB</h3>
<ul>
<li>문서 DB는 데이터를 (주로) JSON과 유사한 문서에 저장한다.</li>
<li>새로운 속성이 필요하면 추가하면 되고 중첩된 구조나 배열을 사용할 수 있다.</li>
</ul>
<h2 id="nosql-도입-시-고려-사항">NoSQL 도입 시 고려 사항</h2>
<ol>
<li>트랜잭션 지원 여부를 고려한다.: 다수의 NoSQL은 RDBMS가 지원하는 수준의 트랜잭션을 지원하지 않는다.</li>
<li>데이터 모델이 요구사항에 적합하지 확인해야 한다.: NoSQL마다 지원하는 데이터 모델이 있다.</li>
<li>확장성과 성능 요구도 주요 고려 사항이다.: 성능보다 일관성이 중요한 서비스는 NoSQL의 일관성 특징이 요구를 충족시키는 지 확인해야 한다.</li>
<li>운영과 개발 역량을 확보해야 한다.: NoSQL을 도입할 때에는 팀이 가진 경험을 고려해야 하며 필요하다면 미리 학습해야 한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책 내용 정리] 육각형 개발자 (3)]]></title>
            <link>https://velog.io/@dev-gromit/%EC%B1%85-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-%EC%9C%A1%EA%B0%81%ED%98%95-%EA%B0%9C%EB%B0%9C%EC%9E%90-3</link>
            <guid>https://velog.io/@dev-gromit/%EC%B1%85-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-%EC%9C%A1%EA%B0%81%ED%98%95-%EA%B0%9C%EB%B0%9C%EC%9E%90-3</guid>
            <pubDate>Sun, 10 Aug 2025 03:34:46 GMT</pubDate>
            <description><![CDATA[<h1 id="정리하고-공유하기">정리하고 공유하기</h1>
<h2 id="글로-정리해서-공유하기">글로 정리해서 공유하기</h2>
<blockquote>
<p>먼저 글을 읽기: 좋은 글을 쓰기 위해서는 우선 글을 읽는 노력이 필요하다.</p>
</blockquote>
<h3 id="주제와-내용-흐름-잡기">주제와 내용 흐름 잡기</h3>
<ul>
<li>짧지 않은 글을 쓸 때는 일단 글에 담을 내용부터 정리해야 한다.</li>
<li>글의 주제, 개요, 목적, 대상을 결정했다면 내용을 어떤 순서로 풀어갈고 고민하자.</li>
<li>목차와 내용 흐름 초안이 나오면 그때부터 글을 쓰기 시작하자.</li>
</ul>
<blockquote>
<p>배경, 정보 제공하기: 배경 설명이나 정보 제공을 함께 작성하여 요청하면, 그 목적에 더 적합한 응답을 받을 수 있다.</p>
</blockquote>
<h3 id="글-쓰기-팁">글 쓰기 팁</h3>
<ul>
<li>문장이 길어진다 싶으면 문장을 나누어라</li>
<li>모든 내용을 문장으로만 쓸 필요는 없다. 글머리 기호 목록이나 번호 목록을 써라</li>
<li>표, 그래프, 그림을 적절하게 사용하면 글 보다 이해하기 쉽다. (글로 이해시키기 어려운 내용은 표, 그래프, 그림을 고려해보자)</li>
</ul>
<h3 id="시간을-내서-글쓰기-연습하기">시간을 내서 글쓰기 연습하기</h3>
<ul>
<li>글쓰기는 도움이 되는 책이나 글을 읽는다 해서 느는게 아니다. 직접 써야 한다.</li>
<li><strong>아무 주제나 잡고 쓰자. 일기, 문제 해결 방안, 리뷰 등 아무 글이나 쓰자</strong></li>
</ul>
<h2 id="발표하기">발표하기</h2>
<h3 id="겉치레는-나중에-신경-쓰기">겉치레는 나중에 신경 쓰기</h3>
<ul>
<li>발표는 말로한다. 보조 수단(파워포인트)이 있지만 발표 자체는 말 중심으로 이뤄진다. 글쓰기와 마찬가지로 발표할 때는 말을 잘하는 게 중요하다. </li>
<li><strong>발표의 핵심은 내용 전달이다. 화려한 장표가 아니다.</strong></li>
<li><strong>발표 자료를 만들 때는 먼저 내용에 집중하자.</strong></li>
<li>유머같은 거 능력있는 거 아니면 발표에서 유머를 챙기려고 하지마라.</li>
</ul>
<h3 id="외래어-남용하지-않기">외래어 남용하지 않기</h3>
<ul>
<li>발표는 내가 아니라 듣는 사람을 위해서 하는 것이다. <strong>영어 문장을 남발하는 것은 듣는 사람을 힘들게 한다.</strong></li>
<li><strong>발표할 때는 청자 입장에서 자료를 만들기 위해 노력하자</strong></li>
</ul>
<blockquote>
<p>말재주가 부족한 개발자는 꾸준히 글을 쓰고 발표에 참여해보자. 말로 소통 역량을 높이는 데 많은 도움이 될 것이다.</p>
</blockquote>
<h1 id="리더와-팔로워">리더와 팔로워</h1>
<p>팀장 같은 직급이 있어야 리더가 되는 것이 아니다. 우리 모두가 리더이면서 동시에 팔로워이기에 두 역할을 이해하고 연습해야 한다.</p>
<h2 id="리더-연습하기">리더 연습하기</h2>
<ul>
<li>리더십도 연습해야 한다. 규모가 작은 업무가 있다면 리더를 연습할 수 있는 좋은 기회다. 나보다 경험이 부족한 직원과 팀을 이뤄 업무를 이끌어보자.</li>
<li>리더십 향상에는 연습뿐 아니라 강의, 책도 중요하다. 간접 경험을 할 수 있는 수단들을 적극 활용하자</li>
</ul>
<h3 id="사람이-아닌-프로세스-시스템-변화-시키기">사람이 아닌 프로세스-시스템 변화 시키기</h3>
<ul>
<li>변화가 필요하다면 사람이 아닌 프로세스와 시스템에 집중하자.</li>
<li><strong>기존 프로세스를 변경하는 것은 매우 힘든 일이다. 본인 스스로 모범 사례가 될 수 있도록 노력하고, 장점을 느낄 수 있도록 옆에서 도와주어야 한다.</strong></li>
</ul>
<h3 id="대신하지-않기">대신하지 않기</h3>
<ul>
<li>리더가 되면 어려움을 겪는 팀원을 보고 내가 하면 더 빨리 할 수 있겠다는 생각에 대신하려고 할 수 있다. 그러기 보다는 최대한 믿고 기다려라.</li>
</ul>
<h3 id="도움-요청하기">도움 요청하기</h3>
<ul>
<li>리더라고 해서 힘든 일을 혼자서 떠맏지 않아도 된다.</li>
<li>리더가 가져야 할 책임은 일을 제대로 끝내는 것이다.</li>
<li>힘든 일이 있거나 도움이 필요하면 상위 직급자한테 지원 요청을 하거나 함께하는 직원에게 도움을 구하자</li>
</ul>
<h3 id="규모의-비경제-이해하기">규모의 비경제 이해하기</h3>
<ul>
<li>일정이 조금 지연되면 개발 참여 인력을 늘리려고 할 수 있지만, 이 때 규모의 비경제에 빠지지 않도록 주의해야 한다.</li>
<li>규모의 비경제란 프로젝트에 인력을 추가하는 등 프로젝트가 커지면 소통 비용과 부하가 늘어나면서 개발 시간이 줄기는 커녕 오히려 증가하는 것을 뜻한다.</li>
<li>이런 경우에는 프로젝트를 나누어서 최대한 독립적으로 따로 진행해야 비경제성이 줄어든다.</li>
</ul>
<h2 id="팔로워">팔로워</h2>
<p>팔로워십은 리더와 조화를 이루고 능동적으로 일을 수행하면서 리더가 성공할 수 있도록 지원하는 것을 뜻한다.</p>
<h3 id="팔로워십과-영향력">팔로워십과 영향력</h3>
<ul>
<li>좋은 팔로워는 리더가 제시하는 방향을 잘 지원하고 따르는 것 뿐만 아니라 <strong>리더가 잘못된 의사 결정을 내렸을 때 리더가 올바른 방향으로 이끌어가 수 있도록 노력한다.</strong></li>
</ul>
<h3 id="이끌거나-따르거나-비켜라">이끌거나 따르거나 비켜라</h3>
<ul>
<li>여러 사람과 함께 일한다면 둘 중 하나는 해야 한다. 리더가 되어 누군가를 이끌거나, 팔로워가 되어 누군가를 따라야 한다.</li>
<li><strong>좋은 팔로워는 리더가 의사 결정하는 과정에 참여하고 좋은 결정을 내릴 수 있게 함께 고민해야 한다.</strong></li>
</ul>
<h2 id="겸손-존중-신뢰">겸손-존중-신뢰</h2>
<ul>
<li>겸손: 나는 다 알지 못하며 완벽하지 않다. 스스로 발전하는 데 열려있다.</li>
<li>존중: 진심으로 상대를 배려한다. 동료에게 친절히 대하고 동료의 능력과 성취를 인정한다.</li>
<li>신뢰: 동료가 능숙하게 올바른 일을 하리라 믿는다.</li>
</ul>
<p><strong>동료의 부족함을 지적할 때는 개인을 비난하지 말고 최대한 정중해야 한다.</strong></p>
<p>동료의 <strong>신뢰</strong>를 얻는 것이 제일 중요하다.</p>
<ul>
<li>신뢰는 역량과 성품을 기반으로 이루어진다. 역량과 성품 모두 좋아야 신뢰를 얻을 수 있다.</li>
<li><strong>좋은 관계는 어려울 대 서로 큰 힘이 되어준다. 그러니 관계의 힘을 무시하지 말자.</strong></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>