<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>do-something</title>
        <link>https://velog.io/</link>
        <description>되면 한다</description>
        <lastBuildDate>Sun, 16 Mar 2025 13:42:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>do-something</title>
            <url>https://velog.velcdn.com/images/eunz_juu/profile/e3a54975-cc98-4f15-a09a-b0d1858f027c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. do-something. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/eunz_juu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초 1] 6장. 키-값 저장소 설계]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-6%EC%9E%A5.-%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/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-6%EC%9E%A5.-%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 Mar 2025 13:42:37 GMT</pubDate>
            <description><![CDATA[<ul>
<li>성능상의 이유로, 키는 짧을수록 좋다</li>
<li>키-값 저장소는 보통 값은 무엇이 오든 크게 상관 없다.</li>
<li>키-값 저장소로 널리 알려진 것으로는 아마존 다이나모, memcached, 레디스 같은 것들이 있다</li>
</ul>
<h2 id="문제-이해-및-설계-범위-확정">문제 이해 및 설계 범위 확정</h2>
<ul>
<li>키-값 쌍의 크기는 10KB 이하이다.<blockquote>
<p>10KB = 10000 Byte <br>
UTF-8 인코딩: 1<del>4바이트/문자 → 2,500</del>10,000자</p>
</blockquote>
</li>
<li>큰 데이터를 저장할 수 있어야 한다.</li>
<li>높은 가용성을 제공해야 한다. 따라서 시스템은 설사 장애가 있더라도 빨리 응답해야 한다.</li>
<li>높은 규모 확장성을 제공해야 한다. 따라서 트래픽 양에 따라 자동적으로 서버 증설/삭제가 이루어져야 한다.</li>
<li>데이터 일관성 수준은 조정이 가능해야 한다.</li>
<li>응답 지연시간이 짧아야 한다.</li>
</ul>
<h2 id="단일-서버-키-값-저장소">단일 서버 키-값 저장소</h2>
<ul>
<li>한 대 서버만 사용하는 키-값 저장소 설계로 가장 직관적인 방법은 키-값 쌍 전부를 <strong>메모리에 해시 테이블로 저장하는 것</strong>이다.
  <img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-4.png" alt="alt text"><ul>
<li>이는 빠른 속도를 보장하기는 하지만 모든 데이터를 메모리 안에 두는 것이 불가능할 수도 있다</li>
</ul>
</li>
<li>해결책으로는 1) 데이터 압축 / 2) 자주 쓰이는 데이터만 메모리에 두고 나머지는 디스크에 저장<blockquote>
<p>✅ 데이터 압축</p>
<ul>
<li>압축 알고리즘 적용: <strong>LZF, LZ4, Zstandard 등 고속 압축 알고리즘</strong>을 사용하여 데이터 크기 감소</li>
</ul>
</blockquote>
</li>
</ul>
<h2 id="분산-키-값-저장소">분산 키-값 저장소</h2>
<h3 id="cap정리">CAP정리</h3>
<ul>
<li>데이터 일관성 (Consistency), 가용성 (Availability), 파티션 감내 (Partition Tolerance) 라는 <strong>세 가지 요구사항을 동시에 만족하는 분산 시스템을 설계하는 것은 불가능하다</strong>는 정리
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image.png" alt="alt text"></li>
<li>데이터 일관성<ul>
<li>분산 시스템에 접속하는 모든 클라이언트는 어떤 노드에 접속했느냐와 관계 없이 <strong>언제나 같은 데이터</strong>를 보게 되어야 한다.</li>
</ul>
</li>
<li>가용성<ul>
<li>분산 시스템에 접속하는 클라이언트는 <strong>일부 노드에 장애가 발생하더라도 항상 응답을 받을 수 있어야</strong> 한다.</li>
</ul>
</li>
<li>파티션 감내<ul>
<li>파티션은 두 노드 사이에 통신 장애가 발생하였음을 의미한다. </li>
<li>즉, 파티션 감내는 <strong>네트워크에 파티션이 생기더라도 시스템이 계속 동작해야 한다</strong>는 것을 뜻한다.</li>
</ul>
</li>
<li>3가지 요구사항 중 어떤 두 가지를 만족하냐에 따라 다음과 같이 분류할 수 있다.<ul>
<li>CP시스템</li>
<li>AP시스템</li>
<li>CA시스템</li>
</ul>
</li>
<li>다만 통상 네트워크 장애는 피할 수 없는 것으로 여겨지기 때문에 <strong>분산시스템은 반드시 파티션 문제를 감내할 수 있도록 설계되어야 한다.</strong> 따라서 CA시스템은 실질 존재하지 않는다.<blockquote>
<p>✅ <strong>CP, AP 시스템 추가 예시</strong></p>
<ul>
<li>MongoDB (CP 시스템)
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-5.png" alt="alt text"><ul>
<li>비동기 복제를 사용하여 데이터의 여러 복사본을 분산시킨다</li>
<li>주요 구성 요소<ul>
<li><strong>프라이머리: 모든 쓰기 작업을 처리하는 마스터 노드</strong></li>
<li>세컨더리: 프라이머리로부터 데이터를 복제받아 동일한 데이터 세트를 유지하는 노드</li>
<li>기본적으로 프라이머리가 모든 읽기와 쓰기를 처리하는데, 프라이머리 실패 시 새로운 프라이머리 선출에 최대 12초가 소요됨</li>
<li><strong>선출 과정 동안 모든 쓰기 작업이 중단됨</strong></li>
</ul>
</li>
</ul>
</li>
<li>Cassandra (AP 시스템)
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-6.png" alt="alt text"><ul>
<li>카산드라는 Peer-to-peer 시스템<ul>
<li>즉, 카산드라는 프라이머리 노드 없이 <strong>모든 노드가 읽기 작업과 쓰기 작업을 수행할 수 있고 복제본을 분리된 다른 노드에 저장</strong></li>
</ul>
</li>
<li>프라이머리 노드 없이 모든 노드가 같은 작업을 수행하므로 SPOF가 없다는 장점이 있음</li>
<li>데이터는 복제 팩터 (데이터 복제본의 수) 에 따라 복제되며, 안정해시 기반 데이터 파티셔닝 제공</li>
<li>파티션이 발생해서 복제본이 업데이트된 데이터 사본을 받지 못하는 상황이 발생할 수 있으나, 다른 노드들은 여전히 사용자가 사용할 수 있으므로 데이터 일관성이 깨짐</li>
<li>그러나 Cassandra는 <strong>최종 일관성(eventual consistency)</strong> 제공<ul>
<li>즉, 모든 노드간 데이터가 동기화되기 전까지 각 노드는 서로 다른 버전의 데이터를 가지고 있을 수 있음</li>
<li>카산드라가 최종 일관성을 보장하는 방법<ul>
<li>복제 팩터: 데이터의 복제본 수를 결정하여 여러 노드에 데이터를 분산 저장</li>
<li><a href="https://devlog-wjdrbs96.tistory.com/444">일관성 수준 설정</a>: 읽기/쓰기 작업에 대한 일관성 수준 조절 가능</li>
<li>읽기 복구: 일관성이 맞지 않는 데이터를 발견하면 자동으로 최신 데이터로 업데이트</li>
<li>가십 프로토콜: 노드 간 주기적인 데이터 동기화를 통해 일관성 유지 (Cassandra의 모든 노드는 데이터 및 노드 상태에 대한 정보를 브로드캐스트하는 가십 프로토콜이라는 피어 투 피어 통신 프로토콜을 통해 서로 통신)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Reference<ul>
<li><a href="https://www.instaclustr.com/blog/cassandra-vs-mongodb/">https://www.instaclustr.com/blog/cassandra-vs-mongodb/</a></li>
<li><a href="https://www.analyticsvidhya.com/blog/2020/08/a-beginners-guide-to-cap-theorem-for-data-engineering/#h-understanding-cp-with-mongodb">https://www.analyticsvidhya.com/blog/2020/08/a-beginners-guide-to-cap-theorem-for-data-engineering/#h-understanding-cp-with-mongodb</a></li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
<h4 id="이상적-상태">이상적 상태</h4>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-1.png" alt="alt text"></p>
<ul>
<li>이상적 환경이라면 네트워크가 파티션되는 상황은 절대로 일어나지 않을 것이다.</li>
</ul>
<h4 id="실세계의-분산-시스템">실세계의 분산 시스템</h4>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-2.png" alt="alt text"></p>
<ul>
<li>분산 시스템은 파티션 문제를 피할 수 없다.</li>
<li>n3에 장애가 발생하여 n1및 n2와 통신할 수 없으면 클라이언트가 n1, n2에 기록한 데이터가 n3에 전달되지 않는다. 또한 n3에 기록되었으나 아직 n1, n2로 전달되지 않은 데이터가 있다면 n1와 n2는 오래된 사본을 갖고 있을 것이다.</li>
<li>가용성 대신 일관성을 선택했다면(CP시스템) 세 서버 사이에 생길 수 있는 <strong>데이터 불일치 문제를 피하기 위해 n1과 n2에 대해 쓰기 연산을 중단시켜야 하는데</strong>, 그렇게 하면 가용성이 깨진다.<ul>
<li><strong>은행권의 경우 데이터 일관성을 양보하지 않는다</strong> ex. 온라인 뱅킹 시스템이 계좌 최신 정보를 출력하지 못한다면 큰 문제일 것이다.</li>
<li>이와 같이 만약에 일관성이 깨질 수 있는 상황이 발생하면 이런 시스템은 <strong>상황이 해결될 때 까지는 오류를 반환해야 한다.</strong></li>
</ul>
</li>
<li>반대로 일관성 대신 가용성을 선택한다면(AP시스템) 낡은 데이터를 반환할 위험이 있다고 해도 계속 읽기 연산을 허용해야 한다. 그리고 n1과 n2에서는 계속 쓰기 연산을 허용할 것이고, 파티션 문제가 해결된 후에 새 데이터를 n3에 전송할 것이다.</li>
</ul>
<h3 id="시스템-컴포넌트">시스템 컴포넌트</h3>
<ul>
<li>키-값 저장소 구현에 사용될 핵심 컴포넌트들 및 기술들을 살펴본다.<ul>
<li>데이터 파티션</li>
<li>데이터 다중화</li>
<li>일관성</li>
<li>일관성 불일치 해소</li>
<li>장애 처리</li>
<li>시스템 아케텍처 다이어그램</li>
<li>쓰기 경로</li>
<li>읽기 경로</li>
<li>데이터 파티션</li>
</ul>
</li>
</ul>
<h4 id="데이터-파티션">데이터 파티션</h4>
<ul>
<li>대규모 어플리케이션에서 전체 데이터를 한 대 서버에 욱여넣는 것은 불가능하므로, 가장 단순한 해결책은 <strong>데이터를 작은 파티션들로 분할한 다음 여러 대의 서버에 저장하는 것</strong>이다.</li>
<li>데이터를 파티션 단위로 나눌 때는 다음 두 가지 문제를 중요하게 살펴보아야 한다.<ul>
<li>데이터를 여러 서버에 고르게 분산할 수 있는가</li>
<li>노드가 추가되거나 삭제될 때 데이터의 이동을 최소화 할 수 있는가</li>
</ul>
</li>
<li>이전 장에 배운 안정 해시가 이런 문제를 푸는 데 적합한 기술이다.</li>
<li>안정해시를 사용하여 데이터를 파티셔닝하면 좋은 점<ul>
<li><strong>규모 확장 자동화</strong><ul>
<li>시스템 부하에 따라 서버가 자동으로 추가되거나 삭제 가능 -&gt; ➕ 하지만 안정해시만으로는 서버 자동 추가/삭제가 가능하지 않음. 왜냐하면 안정해시는 서버 추가/삭제 시 데이터 재배치를 최소화하기 위한 기술일 뿐이므로 자동화된 서버 관리를 위해선 추가구현 필요.</li>
</ul>
</li>
<li><strong>다양성</strong><ul>
<li>각 서버의 용량에 맞게 가상 노드의 수를 조정할 수 있다.</li>
<li>즉, 고성능 서버는 더 많은 가상 노드를 갖도록 설정할 수 있다. -&gt; ➕ s1,s2,s3 서버가 있을 때 s1 이 고성능 서버라면 s2,s3 와 다른 개수의 가상 노드 설정 가능.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="데이터-다중화">데이터 다중화</h4>
<ul>
<li>높은 가용성과 안정성을 확보하기 위해서는 <strong>데이터를 N개 서버에 비동기적으로 다중화</strong>할 필요가 있다.<ul>
<li>여기서 N은 튜닝 가능한 값</li>
</ul>
</li>
<li>N개 서버를 선정하는 방법은 어떤 키를 해시 링 위에 배치한 후, 그 지점으로부터 시계 방향으로 <strong>링을 순회하면서 만나는 첫 N개 서버에 데이터 사본을 보관하는 것</strong>이다</li>
<li>가상 노드를 사용한다면 위와 같이 선택한 N개의 노드가 대응될 실제 물리 서버의 개수가 N보다 작아질 수 있다.
이를 해결하기 위해서는 <strong>같은 물리 서버를 중복 선택하지 않도록 해야 한다.</strong><ul>
<li>➕ N=3 인데 해시 링을 순회하며 3개의 가상 노드 선택 시 s1_2, s1_3, s2_1 일 경우에는 3개의 가상 노드를 선택했지만 실제 물리서버 대수가 2인 상황이 됨.</li>
</ul>
</li>
<li>같은 데이터 센터에 속한 노드는 정전, 네트워크 이슈 등의 문제를 동시에 겪을 가능성이 있다. 따라서 안정성을 담보하기 위해 <strong>데이터의 사본은 다른 센터의 서버에 보관하고, 센터들은 고속 네트워크로 연결한다.</strong><blockquote>
<p>✅ 안정 해시 구현 시 사용할 수 있는 방법</p>
<ul>
<li>데이터 센터 인식 파티셔닝: 해시 링에 노드를 배치할 때 각 데이터 센터의 노드를 번갈아가며 배치<ul>
<li>가상 노드 식별자 생성: 각 물리적 서버에 대해 여러 개의 가상 노드를 생성할 때, 데이터 센터 ID를 포함시킴. ex) &quot;DC1-Server1-VNode1&quot;, &quot;DC2-Server1-VNode1&quot; 등의 형식으로 식별자 생성</li>
<li>해시 함수 수정: 가상 노드의 해시 값을 계산할 때, 데이터 센터 ID를 고려하는 해시 함수 사용. 이를 통해 같은 데이터 센터의 노드들이 해시 링에서 연속적으로 배치되는 것을 방지 가능</li>
</ul>
</li>
<li>복제 전략 수립: 데이터의 복제본을 다른 데이터 센터의 노드에 저장하도록 설정</li>
<li>고속 네트워크 연결: 데이터 센터 간 고속 네트워크를 구축하여 효율적인 데이터 복제와 동기화를 가능하게 함</li>
</ul>
</blockquote>
</li>
</ul>
<h4 id="데이터-일관성">데이터 일관성</h4>
<ul>
<li><p><strong>여러 노드에 다중화된 데이터는 적절히 동기화가 되어야 한다.</strong></p>
</li>
<li><p>정족수 합의(Quorum Consensus) 프로토콜을 사용하면 읽기/쓰기 연산 모두에 일관성을 보장할 수 있다.</p>
<ul>
<li>N=사본 개수</li>
<li>W=쓰기 연산에 대한 정족수. 쓰기 연산이 성공한 것으로 간주되려면 <strong>적어도 W개의 서버로부터 쓰기 연산이 성공했다</strong>는 응답을 받아야 한다.</li>
<li>R=읽기 연산에 대한 정족수. 읽기 연산이 성공한 것으로 간주되려면 <strong>적어도 R개의 서버로부터 응답</strong>을 받아야 한다.
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-3.png" alt="alt text"></li>
</ul>
</li>
<li><p>중재자는 클라이언트와 노드 사이에서 프록시 역할을 한다</p>
<blockquote>
<p>❓ 중재자는 보통 어떤 컴포넌트로 구현되는지?
= 어떠한 컴포넌트들이 클라이언트 &lt;-&gt; 노드 사이의 프록시 역할을 하면서, 정족수 합의 프로토콜을 구현할 수 있는지?</p>
<ul>
<li>Kubernetes 의 etcd (모든 클러스터 데이터를 백업하는 Key-value 저장소)<ul>
<li>Etcd는 Replicated state machine(이하 RSM) 으로, RSM 은 분산 컴퓨팅 환경에서 서버가 몇 개 다운되어도 잘 동작하는 시스템을 만들고자 할 때 선택하는 방법의 하나이다.</li>
<li>etcd는 하나의 write 요청을 받았을 때, 쿼럼 숫자만큼의 서버에 데이터 복제가 일어나면 작업이 완료된 것으로 간주하고 다음 작업을 받아들일 수 있는 상태가 됨</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>ex. RSM을 구성하는 서버의 숫자가 3대인 경우 쿼럼 값은 2(3/2+1) <ul>
<li>여기서 쿼럼 수식이 N(서버 수)/2+1 인 이유는 다수의 합의 요청이 가능하기 위함이다</li>
<li>보통 클러스터 수는 홀수 개의 노드로 구성한다고 함. ref; <a href="https://judo0179.tistory.com/entry/Apache-Kafka-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EA%B5%AC%EC%84%B1%EC%9D%84-%ED%99%80%EC%88%98%EB%A1%9C-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0#:~:text=%EB%B0%98%EB%93%9C%EC%8B%9C%20%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%20%EB%85%B8%EB%93%9C%EC%9D%98%20%EC%88%98%EA%B0%80%20%EC%A7%9D%EC%88%98%EA%B0%80%20%EB%90%98%EB%A9%B4%20%EC%95%88%20%EB%90%98%EB%8A%94%20%EA%B1%B4%EA%B0%80%3F">반드시 클러스터 노드의 수가 짝수가 되면 안 되는 건가?</a></li>
</ul>
</li>
</ul>
</blockquote>
</li>
<li><p>W, R, N의 값을 정하는 것은 <code>응답 지연</code>과 <code>데이터 일관성</code> 사이의 타협점을 찾는 전형적인 과정이다.</p>
<ul>
<li>W=1 또는 R=1의 구성인 경우, 중재자는 한 대의 서버로부터의 응답만을 받으면 되기 때문에 응답속도는 빠를 것이다.</li>
<li>만약 W, R 의 값이 이보다 큰 경우 데이터 일관성의 수준은 향상되지만 응답 속도는 느려질 것이다.</li>
<li>W+N &gt; N의 경우에는 강한 일관성이 보장된다.<ul>
<li>일관성을 보증할 최신 데이터를 가진 노드가 최소 하나는 겹칠 것이기 때문이다.</li>
</ul>
</li>
</ul>
</li>
<li><p>가능한 W, R, N 의 구성</p>
<ul>
<li>R=1, W=N : <strong>빠른 읽기 연산</strong>에 최적화된 시스템</li>
<li>W=1, R=N : <strong>빠른 쓰기 연산</strong>에 최적화된 시스템</li>
<li>W+R &gt; N : <strong>강한 일관성 보장</strong> (보통 N=3, W=R=2)</li>
<li>W+R &lt;= N : 강한 일관성이 보장되지 않는다.<blockquote>
<p>❓ 일관성을 보장하게 되는 최종 시점도 중요하지 않은지? 왜 시간에 대한 언급이 없나.. 
<br> ex. 1분에 1회씩은 W+R&gt;N 을 보장한다거나.. 무작정 W+R&gt;N 이 될때까지 기다리진 않을테니까.. </p>
<ul>
<li>cassandra 예시 <a href="https://cassandra.apache.org/doc/3.11/cassandra/configuration/cass_yaml_file.html">https://cassandra.apache.org/doc/3.11/cassandra/configuration/cass_yaml_file.html</a><ul>
<li>write_request_timeout_in_ms</li>
<li>read_request_timeout_in_ms</li>
</ul>
</li>
<li>타임아웃 설정을 통해서 보장 가능. 실제 타임아웃에 걸리면 <code>(ReadTimeoutException): Cassandra timeout during read query at consistency QUORUM (2 responses were required but only 1 replica responded) after nodetool cleanup</code> 이런식으로 에러가 발생함</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4 id="일관성-모델">일관성 모델</h4>
<ul>
<li>일관성 모델은 데이터 일관성의 수준을 결정하는데, 종류가 다양하다.</li>
<li><strong>강한 일관성</strong><ul>
<li>모든 읽기 연산은 가장 최근에 갱신된 결과를 반환한다.</li>
<li>다시 말해서 클라이언트는 <strong>절대로 낡은 데이터를 보지 못한다.</strong></li>
</ul>
</li>
<li><strong>약한 일관성</strong><ul>
<li>읽기 연산은 가장 <strong>최근에 갱신된 결과를 반환하지 못할 수 있다.</strong></li>
</ul>
</li>
<li><strong>결과적 일관성</strong><ul>
<li>약한 일관성의 한 형태로, <strong>갱신 결과가 결국에는 모든 사본에 반영(즉, 동기화) 되는 모델</strong>이다.</li>
</ul>
</li>
<li>강한 일관성을 달성하는 일반적인 방법은, <strong>모든 사본에 현재 쓰기 연산의 결과가 반영될 때까지 해당 데이터에 대한 읽기/쓰기를 금지하는 것</strong>이다.<ul>
<li>이 방법은 고가용성 시스템에는 적합하지 않다</li>
</ul>
</li>
<li>결과적 일관성 모델을 따를 경우 쓰기 연산이 병렬적으로 발생하면 시스템에 저장된 값의 일관성이 깨어질 수 있는데, 이 문제는 클라이언트가 해결해야 한다.</li>
</ul>
<h4 id="비-일관성-해소-기법--데이터-버저닝">비 일관성 해소 기법 : 데이터 버저닝</h4>
<ul>
<li>데이터를 다중화하면 가용성은 높아지지만 사본 간 일관성이 깨질 가능성이 높이진다.<ul>
<li><code>버저닝</code> 과 <code>벡터 시계</code> 는 이 문제를 해소하기 위해 등장한 기술이다.</li>
</ul>
</li>
<li>버저닝 : <strong>데이터를 변경할 때마다 해당 데이터의 새로운 버전을 만드는 것</strong><ul>
<li>따라서 각 버전의 데이터는 변경 불가능</li>
</ul>
</li>
</ul>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-7.png" alt="alt text"></p>
<ul>
<li>이 변경이 이루어진 이후에 원래 값은 무시할 수 있는데, 변경이 끝난 옛날 값이어서이다.</li>
<li>두 버전 v1, v2사이의 충돌을 해결하려면, <strong>충돌을 발견하고 자동으로 해결해 낼 버저닝 시스템</strong>이 필요하다.<ul>
<li>벡터 시계는 이런 문제 해결에 보편적으로 사용되는 기술이다.</li>
</ul>
</li>
<li>벡터 시계는 [서버, 버전]의 순서쌍을 데이터에 매단 것이다.<ul>
<li>벡터 시계는 D([S1, v1], [S2, v2] ... [Sn, vn])와 같이 표현한다고 가정하자.</li>
<li>D는 데이터, vi는 카운터, si는 서버 번호이다.</li>
</ul>
</li>
<li>만일 데이터 D를 서버 Si에 기록하면, 시스템은 아래 작업 가운데 하나를 수행해야 한다.<ul>
<li>[Si, vi]가 있다면 vi를 증가시킨다.</li>
<li>그렇지 않으면 새 항목[Si, 1]를 만든다.</li>
</ul>
</li>
</ul>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-8.png" alt="alt text"></p>
<ol>
<li>클라이언트가 데이터 D1을 시스템에 기록한다.
이 쓰기 연산을 처리한 서버는 Sx이므로 벡터 시계는 <code>D1[(Sx, 1)]</code>으로 변한다.</li>
<li>다른 클라이언트가 데이터 D1을 읽고 D2로 업데이트 한 다음 기록한다. <br>D2는 D1에 대한 변경이므로 D1을 덮어쓴다. <br>이 때 쓰기 연산은 같은 서버 Sx가 처리한다고 가정하면 벡터 시계는 <code>D2([Sx, 2])</code>로 바뀔 것이다.</li>
<li>다른 클라이언트가 D2를 읽어 D3로 갱신한 다음 기록한다. <br>이 쓰기 연산은 Sy가 처리한다고 가정하면 벡터 시계 상태는 <code>D3([Sx, 2], [Sy, 1]])</code>로 바뀐다.</li>
<li>또 다른 클라이언트가 D2를 읽고 D4로 갱신한 다음 기록한다. <br>이 때 쓰기 연산은 서버 Sz가 처리한다고 가정하면 벡터 시계는 <code>D4([Sx, 2], [Sz, 1])</code>일 것이다.</li>
<li>어떤 클라이언트가 D3와 D4를 읽으면 데이터 간 충돌이 있다는 것을 알게 된다. (두 버전 모두 D2([Sx, 2])를 기반으로 함. 이는 D2에서 분기가 발생했음을 의미)<br>D2를 Sy와 Sz가 각기 다른 값으로 바꾸었기 때문이다. 이 충돌은 클라이언트가 해소한 후에 서버에 기록한다. <br>이 쓰기 연산을 처리한 서버는 Sx였다고 하면 벡터 시계는 <code>D5([Sx, 3], [Sy, 1], [Sz, 1])</code>로 바뀐다. </li>
</ol>
<ul>
<li><p>벡터 시계를 사용하면 어떤 버전 X가 버전 Y의 이전 버전인지(따라서 충돌이 없는지) 쉽게 판단할 수 있다.</p>
<ul>
<li><strong>버전 Y에 포함된 모든 구성요소의 값이 X에 포함된 모든 구성요소 값보다 같거나 큰지만</strong> 보면 된다.</li>
</ul>
</li>
<li><p>버전 X와 Y사이에 충돌이 있는지 보려면 <strong>Y의 벡터 시계 구성요소 가운데 X의 벡터 시계 동일 서버 구성요소보다 작은 값을 갖는 것이 있는지 확인해 보면 된다.</strong></p>
</li>
<li><p>벡터 시계를 사용해 충돌을 감지하고 해소하는 방법의 2가지 단점</p>
<ul>
<li><p>충돌 감지 및 해소 로직이 클라이언트에 들어가야 하므로, 클라이언트 구현이 복잡해진다.</p>
</li>
<li><p>[서버:버전] 의 순서쌍 개수가 굉장히 빨리 늘어난다</p>
<ul>
<li>따라서 길이에 임계치를 정하고, 임계치 이상으로 길이가 길어지면 오래된 순서쌍을 벡터 시계에서 제거하도록 해야 한다<blockquote>
<ul>
<li>위에서 말하는 클라이언트 == 저장소 시스템과 상호작용하는 소프트웨어 컴포넌트</li>
<li>애플리케이션 서버, 데이터베이스 클라이언트 라이브러리 일 수 있음.</li>
<li>애플리케이션 서버가 클라이언트라고 가정한다면, 아래와 같은 코드를 짤 수 있음</li>
</ul>
</blockquote>
<pre><code class="language-java">public class VectorClockClient {
// 벡터 시계를 저장하는 맵. 키는 서버 ID, 값은 해당 서버의 버전
private Map&lt;String, Integer&gt; vectorClock;
</code></pre>
</li>
</ul>
<p>// 생성자: 빈 벡터 시계로 초기화
public VectorClockClient() {</p>
<pre><code>this.vectorClock = new HashMap&lt;&gt;();</code></pre><p>}</p>
<p>// 특정 서버의 버전을 업데이트하는 메소드
public void update(String server, int version) {</p>
<pre><code>// 현재 저장된 버전과 새 버전 중 더 큰 값을 선택하여 저장
vectorClock.put(server, Math.max(vectorClock.getOrDefault(server, 0), version));</code></pre><p>}</p>
<p>// 다른 벡터 시계와 충돌을 감지하는 메소드
public boolean detectConflict(Map&lt;String, Integer&gt; otherClock) {</p>
<pre><code>// 현재 벡터 시계의 각 서버에 대해
for (String server : vectorClock.keySet()) {
    if (otherClock.containsKey(server)) {
        // 현재 벡터 시계의 버전이 더 크면 충돌
        if (vectorClock.get(server) &gt; otherClock.get(server)) {
            return true;
        }
    }
}
// 다른 벡터 시계의 각 서버에 대해
for (String server : otherClock.keySet()) {
    if (vectorClock.containsKey(server)) {
        // 다른 벡터 시계의 버전이 더 크면 충돌
        if (otherClock.get(server) &gt; vectorClock.get(server)) {
            return true;
        }
    }
}
// 충돌이 없으면 false 반환
return false;</code></pre><p>}</p>
<p>// 충돌을 해결하고 병합된 벡터 시계를 반환하는 메소드
public Map&lt;String, Integer&gt; resolveConflict(Map&lt;String, Integer&gt; otherClock) {</p>
<pre><code>// 현재 벡터 시계를 복사하여 새로운 맵 생성
Map&lt;String, Integer&gt; resolvedClock = new HashMap&lt;&gt;(vectorClock);
// 다른 벡터 시계의 각 항목에 대해
for (Map.Entry&lt;String, Integer&gt; entry : otherClock.entrySet()) {
    // 두 벡터 시계 중 더 큰 버전을 선택하여 병합된 시계에 저장
    resolvedClock.put(entry.getKey(), Math.max(resolvedClock.getOrDefault(entry.getKey(), 0), entry.getValue()));
}
// 병합된 벡터 시계 반환
return resolvedClock;</code></pre><p>}
}</p>
</li>
</ul>
</li>
</ul>
<p>// 그래서 데이터를 읽을 때 충돌 발생한 것을 클라이언트 (애플리케이션 서버) 가 감지하고 해소
public Data read(String key) {
    ServerResponse response = server.read(key);
    Data data = response.getData();
    Map&lt;String, Integer&gt; serverClock = response.getVectorClock();</p>
<pre><code>if (vectorClockClient.detectConflict(serverClock)) {
    // 충돌 발생, 해결 로직 실행
    resolveConflict(key, data, serverClock);
} else {
    // 충돌 없음, 로컬 벡터 시계 업데이트
    vectorClockClient.update(serverId, serverClock.get(serverId));
}

return data;</code></pre><p>}</p>
<p>```</p>
<h4 id="장애-감지">장애 감지</h4>
<ul>
<li><p>보통 <strong>두 대 이상의 서버가 똑같이 서버 A의 장애를 보고해야</strong> 실제로 해당 서버에 장애가 발생했다고 간주한다</p>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-9.png" alt="alt text"></p>
<ul>
<li>모든 노드 사이에 멀티캐스팅 채널을 구축하는 것이 서버 장애를 감지하는 가장 손쉬운 방법이지만 이 방식은 서버가 많을 때에는 비효율적이다</li>
</ul>
</li>
<li><p><code>가십 프로토콜</code> 같은 <strong>분산형 장애 감지 솔루션을 채택</strong>하는 편이 더 효율적이다.</p>
<ul>
<li>각 노드는 멤버십 목록 (각 멤버 ID 와 그 박동 카운터 쌍의 목록) 을 유지한다</li>
<li>각 노드는 주기적으로 자신의 박동 카운터를 증가시킨다.</li>
<li>각 노드는 무작위로 선정된 노드들에게 주기적으로 자기 박동 카운터 목록을 보낸다.</li>
<li>박동 카운터 목록을 받은 노드는 멤버십 목록을 최신 값으로 갱신한다.</li>
<li>어떤 멤버의 박동 카운터 값이 지정된 시간 동안 갱신되지 않으면, 해당 멤버는 장애 상태인 것으로 간주한다.</li>
</ul>
</li>
</ul>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-10.png" alt="alt text"></p>
<ul>
<li>노드 s0은 그림 좌측의 테이블과 같은 멤버십 목록을 가진 상태이다.</li>
<li>노드 s0은 노드 s2 (멤버ID=2)의 박동 카운터가 오랫동안 증가되지 않았다는 것을 발견한다.</li>
<li>노드 s0은 노드 s2를 포함하는 박동 카운터 목록을 무작위로 선택된 다른 노드에게 전달한다.</li>
<li>노드 s2의 박동 카운터가 오랫동안 증가되지 않았음을 발견한 모든 노드는 해당 노드를 장애 노드로 표시한다.</li>
</ul>
<h4 id="일시적-장애-처리">일시적 장애 처리</h4>
<ul>
<li>가십 프로토콜로 장애를 감지한 시스템은 가용성을 보장하기 위해 필요한 조치를 해야 한다<ul>
<li>엄격한 정족수 접근법 사용 : 읽기와 쓰기 연산 금지</li>
<li>느슨한 정족수 접근법 사용 : 이 조건을 완화하여 가용성을 높인다.<ul>
<li>장애 상태인 서버로 가는 요청은 다른 서버가 잠시 맡아 처리한다.<ul>
<li>장애 서버로 향하는 요청을 감지하면, 시스템은 해시 링에서 다음으로 가까운 정상 서버로 요청을 리다이렉션</li>
</ul>
</li>
<li>그동안의 변경사항은 해당 서버 복구 시 일괄로 반영하여 데이터 일관성을 보존한다.</li>
<li>이를 위해 임시로 쓰기 연산을 처리한 서버에는 그에 관한 단서 (hint) 를 남긴다.</li>
<li>이런 장애 처리 방안을 <strong>단서 후 임시 위탁 기법</strong> (hinted handoff) 라고 부른다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>✅ <strong>hinted handoff 실제 예시</strong></p>
<ul>
<li>카산드라가 노드 장애시에도 fault tolerance (결함 내성) 을 유지하는 주요 방법 중 하나는 hinted handoff 메커니즘이다</li>
<li>cassandra.yaml에서 hinted_handoff_enabled이 true로 설정되어 있으면(기본 설정) 복제 노드 중 하나에 도달 할 수 없을 때 카산드라는 coordinator 노드에 hint를 저장<ul>
<li><a href="https://nosqldb.tistory.com/entry/%EC%B9%B4%EC%82%B0%EB%93%9C%EB%9D%BC%EC%9D%98-%EB%85%B8%EB%93%9CNode%EC%97%90-%EC%A0%91%EA%B7%BC-%EC%BD%94%EB%94%94%EB%84%A4%EC%9D%B4%ED%84%B0">coordinator</a></li>
<li>해당 힌트는 클러스터에 속한 위치에 대한 정보를 포함한다. </li>
<li>힌트는 코디네이터 노드의 $CASSANDRA_HOME/data/hints 디렉터리에 있는 플랫 파일에 저장됨. 힌트에는 <strong>힌트 ID</strong>, 변경 사항을 저장할 대상 <strong>복제본 노드</strong>, 복제본 노드로 전달할 수 없는 직렬화된 <strong>변경 사항</strong>(blob으로 저장), <strong>변경 사항 타임스탬프</strong> 및 변경 사항을 직렬화하는 데 사용된 <strong>Cassandra 버전</strong>이 포함됨<ul>
<li>Cassandra의 이전 버전에서는 힌트를 힌트 테이블에 저장했는데, 최신 Cassandra 릴리스는 힌트를 저장하기 위해 디스크의 플랫 파일을 사용</li>
</ul>
</li>
</ul>
</li>
<li>coordinator가 복제 노드가 다시 정상화되는 것을 알아차리게 되면 hint 정보를 복제 노드에서 replay (코디네이터 노드가 저장해 둔 힌트(hint) 정보를 복구된 복제 노드에 적용하는 과정) 시킨다. </li>
<li>기본적으로 Cassandra는 hint 대기열이 너무 길어지는 것을 방지하기 위해 최대 3시간 동안 hint를 저장한다. <ul>
<li>cassandra.yaml의 max_hint_window_in_ms 속성(기본 3시간)이 해당 hint가 저장되는 시간을 의미한다. </li>
<li>max_hin_window_in_ms 시간이 지나면 일관성을 복원하기 위해서 노드가 다시 정상화되었을 때 수동으로 node repair를 실행해야 한다.</li>
</ul>
</li>
</ul>
<p>+) AWS DynamoDB 도 hinted handoff 사용한다고 함.</p>
<p>ref; <a href="https://cassandra.apache.org/doc/4.0/cassandra/operating/hints.html">https://cassandra.apache.org/doc/4.0/cassandra/operating/hints.html</a></p>
</blockquote>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-11.png" alt="alt text"></p>
<h4 id="영구-장애-처리">영구 장애 처리</h4>
<ul>
<li>단서 후 임시 위탁 기법은 일시적 장애를 처리하기 위한 것이므로 영구적 노드 장애 상태는 다른 처리 방안이 필요하다</li>
<li><code>반-엔트로피 프로토콜</code>을 구현해서 사본들을 동기화해야 한다.<ul>
<li>반-엔트로피 프로토콜은 <strong>사본들을 비교하여 최신 버전으로 갱신하는 과정</strong>을 포함한다.</li>
</ul>
</li>
<li>사본 간의 일관성이 망가진 상태를 탐지하고 전송 데이터의 양을 줄이기 위해서는 <code>머클 트리</code>를 사용할 것이다.</li>
<li>머클 트리 : 해시 트리라고도 불리며 각 노드에 그 자식 노드들에 보관된 값의 해시, 또는 자식 노드들의 레이블로부터 계산된 해시 값을 레이블로 붙여두는 트리</li>
</ul>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-12.png" alt="alt text"></p>
<ul>
<li>루트 노드의 해시 값이 일치하면 두 서버는 같은 데이터를 갖는 것이다</li>
<li>그 값이 다를 경우 왼쪽, 오른쪽 자식노드의 해시값을 비교하면서 아래쪽으로 탐색하다보면 다른 데이터를 갖는 버킷을 찾을 수 있으므로 그 버킷들만 동기화하면 된다</li>
</ul>
<h4 id="데이터-센터-장애-처리">데이터 센터 장애 처리</h4>
<ul>
<li>데이터 센터 장애는 정전, 네트워크 장애, 자연재해 등 다양한 이유로 발생할 수 있다.</li>
<li>데이터 센터 장애에 대응할 수 있는 시스템을 만들려면 데이터를 여러 데이터 센터에 다중화하는 것이 중요하다.</li>
</ul>
<h3 id="쓰기-경로">쓰기 경로</h3>
<ul>
<li>아래 구조는 카산드라의 사례를 참고한 것이다
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-14.png" alt="alt text"></li>
</ul>
<ol>
<li>쓰기 요청이 커밋 로그 파일에 기록된다.</li>
<li>데이터가 메모리 캐시에 기록된다.</li>
<li>메모리 캐시가 가득차거나 사전에 정의된 어떤 임계치에 도달하면 데이터는 디스크에 있는 SSTable에 기록된다. <ul>
<li>SSTable은 Sorted-String Table의 약어로, &lt;키, 값&gt;의 순서쌍을 정렬된 리스트 형태로 관리하는 테이블</li>
</ul>
</li>
</ol>
<h3 id="읽기-경로">읽기 경로</h3>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-15.png" alt="alt text"></p>
<ul>
<li>데이터가 메모리에 없는 경우에는 디스크에서 가져와야 하며, <strong>어느 SSTable에 찾는 키가 있는지 알아낼 효율적인 방법</strong>으로 <code>블룸 필터</code>가 흔히 사용된다.</li>
</ul>
<blockquote>
<p>➕ Cassandra</p>
<ul>
<li>카산드라는 일반적인 DB에서 쓰이는 B-Tree 대신 Log-Structured Merge Tree (LSM Tree) 사용</li>
<li>블룸 필터 : Bloom Filter는 어떤 원소 x가 어떤 집합 A 의 원소인지 확률적으로 판단하는 표시함수 
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-17.png" alt="alt text"></li>
<li>블룸 필러를 이용한 membership query (어떤 집합 A와 어떤 원소 x가 &#39;x가 A에 포함된&#39; 관계인지 묻는 것) 의 결과 값은 &#39;아마도 해당 집합의 원소인 것 같다&#39;, 또는 &#39;확실히 해당 집합에 포함된 원소가 아니다&#39;를 뜻한다. <br> 이런 특성 때문에 블룸 필터는 단독으로 사용하는 것보다는 확률적인 방법이 아닌 다른 방법을 보조하는 역할로 사용하는 것이 적합하다</li>
</ul>
</blockquote>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%206/%EC%9D%80%EC%A3%BC/image-16.png" alt="alt text"></p>
<ol>
<li>데이터가 메모리 있는지 검사한다. </li>
<li>데이터가 메모리에 없으므로 블룸 필터를 검사한다.</li>
<li>블룸 필터를 통해 어떤 SSTable에 키가 보관되어 있는지 알아낸다.</li>
<li>SSTable에서 데이터를 가져온다.</li>
<li>해당 데이터를 클라이언트에 반환한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초 1] 5. 안정 해시 설계]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-5.-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-5.-%EC%95%88%EC%A0%95-%ED%95%B4%EC%8B%9C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 02 Mar 2025 14:43:44 GMT</pubDate>
            <description><![CDATA[<h1 id="5장-안정-해시-설계">5장. 안정 해시 설계</h1>
<ul>
<li>수평적 규모 확장성을 달성하기 위해서는 <strong>요청 또는 데이터를 서버에 균등하게 나누는 것이 중요</strong>하다<blockquote>
<p>❓사용자의 요청을 고르게 분배하기 위해 사용되는 로드밸런서에서는 안정해시 기술을 사용안할까?</p>
<ul>
<li>목적<ul>
<li>로드 밸런서: 네트워크 <strong>트래픽을 여러 서버에 분산</strong>시켜 서버 과부하를 방지하고 가용성을 높이는 데 사용</li>
<li>안정 해시: 분산 시스템에서 <strong>데이터를 균등하게 분배</strong>하고 노드의 추가 및 제거 시 재분배를 최소화하는 데 사용</li>
<li>즉, 주된 분산 대상이 다름 ! 로드밸런서는 주로 네트워크 요청이나 트래픽을 분산하며, 안정 해시는 주로 데이터를 분산함</li>
</ul>
</li>
<li>알고리즘의 복잡성<ul>
<li>로드 밸런서: <strong>일반적으로 간단한 분산 알고리즘</strong>(예: 라운드 로빈, 최소 연결, IP 해시 등)을 사용하여 실시간으로 트래픽 분배</li>
<li>안정 해시: <strong>복잡한 해싱 기술을 사용</strong>하여 데이터를 분배하며, 노드의 추가/제거 시에도 최소한의 재분배를 보장하는 알고리즘을 사용</li>
</ul>
</li>
<li>하지만 로드밸런서에서도 안정해시 기술 도입할 수 있음! <ul>
<li>구글에서 개발한 Maglev 네트워크 로드 밸런서는 안정 해시 기술을 활용하여 대규모 분산 시스템에서 효율적인 트래픽 분산을 실현</li>
<li><a href="https://www.f5.com/go/faq/nginx-faq#:~:text=Consistent%20(ketama)%20Hash">NGINX</a> 도 안정해시 알고리즘 지원 </li>
</ul>
</li>
</ul>
</blockquote>
<h2 id="해시-키-재배치-rehash-문제">해시 키 재배치 (rehash) 문제</h2>
</li>
<li>N개의 캐시 서버가 있을 때 이 서버들에 요청을 균등하게 나누는 보편적인 방법은 아래 해시 함수를 사용하는 것이다. <ul>
<li>서버 인덱스 = hash(key) % N (서버의 개수)</li>
</ul>
</li>
<li>이 방법은 서버 풀의 크기가 고정되어있고 키의 분포가 균등할 때는 잘 동작한다<blockquote>
<p>❓ 서버 풀의 크기가 고정되어 있을 때는 무슨 상관이 있는거지?</p>
<ul>
<li>재분배할 필요가 없으니까 hash % N 의 결과가 항상 일정하여 캐시 일관성이 유지됨.</li>
</ul>
</blockquote>
</li>
<li>서버가 추가되거나 기존 서버가 삭제된다면 키에 대한 해시값은 변하지 않아도 나머지 연산을 적용한 서버 인덱스 값이 달라질거고, 그러면 <strong>캐시 클라이언트는 데이터가 없는 엉뚱한 서버에 접속하게 된다.</strong><ul>
<li>이것은 <code>대규모 캐시 미스</code>(cache miss)가 발생하게 되며 <strong>안정 해시는 이 문제를 효과적으로 해결하는 기술이다</strong></li>
</ul>
</li>
</ul>
<h2 id="안정-해시">안정 해시</h2>
<ul>
<li><code>안정해시</code>는 해시 테이블 크기가 조정될 때 평균적으로 오직 k/n 개의 키만 재배치하는 해시 기술<ul>
<li>k : 키의 개수, n : 슬롯 (서버)의 개수 </li>
<li>대부분의 <strong>전통적 해시 테이블은 슬롯의 수가 바뀌면 거의 대부분의 키를 재배치</strong>한다
해시 테이블의 크기가 조정되어도
평균적으로 오직 k/n 개의 키만 재배치 하는 해시 기술이다.</li>
</ul>
</li>
</ul>
<h3 id="해시-공간과-해시-링">해시 공간과 해시 링</h3>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/0de78d32-000b-4f0d-a353-8bf3e182c2a8/image.png" alt=""></p>
<ul>
<li>해시 공간의 양쪽을 구부려 붙여 만든 링이어서 해시 링이라고 한다</li>
</ul>
<h3 id="서버-조회">서버 조회</h3>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%205/%EC%9D%80%EC%A3%BC/image-2.png" alt=""></p>
<ul>
<li>어떤 키가 저장되는 서버는, 해당 키의 위치로부터 <strong>시계 방향으로 링을 탐색해나가면서 만나는 첫번째 서버</strong>다</li>
</ul>
<h3 id="서버-추가">서버 추가</h3>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%205/%EC%9D%80%EC%A3%BC/image-4.png" alt=""></p>
<ul>
<li>서버를 추가하더라도 키 가운데 일부만 추가해주면 된다. <ul>
<li>새로 추가된 노드 C로부터 시계방향으로 가장 가까운 노드 A가 관리하던 키들 중, 노드 C 보다 시계 반대 방향에 있는 키들을 모두 C로 재배치한다.</li>
</ul>
</li>
</ul>
<h3 id="서버-제거">서버 제거</h3>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%205/%EC%9D%80%EC%A3%BC/image-3.png" alt=""></p>
<ul>
<li>하나의 서버가 제거될 때도 키 가운데 일부만 재배치 된다.</li>
</ul>
<h3 id="기본-구현법의-문제들">기본 구현법의 문제들</h3>
<ul>
<li><p>안정 해시의 기본적인 절차는 다음과 같다</p>
<ul>
<li>서버와 키를 <strong>균등 분포 (uniform distribution) 해시 함수를 사용해 해시 링에 배치</strong>한다.<blockquote>
<p>✅ 균등 분포 해시 함수 예시</p>
<ul>
<li>SHA-1 (Secure Hash Algorithm 1) : 0부터 2^160-1까지의 범위를 갖고 입력값을 해시 공간에 고르게 분포 시킴</li>
<li>MD5 (Message Digest algorithm 5) : 128비트 해시값을 생성하며, 빠른 계산 속도를 제공</li>
</ul>
</blockquote>
</li>
<li>키의 위치에서 링을 <strong>시계 방향으로 탐색하다 만나는 최초의 서버가 키가 저장될 서버</strong>다.</li>
</ul>
</li>
<li><p>이 접근법에는 두가지 문제가 있다.</p>
<ul>
<li><p><strong>파티션의 크기를 균등하게 유지하는 게 불가능</strong>하다</p>
<ul>
<li>파티션 : 인접한 서버 사이의 해시 공간</li>
<li>C가 삭제되면 A의 파티션과 B의 파티션 크기가 균등하지 않게 된다.
<img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%205/%EC%9D%80%EC%A3%BC/image-5.png" alt=""></li>
</ul>
</li>
<li><p><strong>키의 균등 분포를 달성하기 어렵다</strong></p>
</li>
</ul>
</li>
<li><p>이 두 문제를 해결하기 위해 제안된 기법이 <code>가상노드/복제</code> 라 불리는 기법이다.</p>
</li>
</ul>
<h3 id="가상-노드">가상 노드</h3>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/main/%EC%A0%95%EB%A6%AC/Chapter%205/%EC%9D%80%EC%A3%BC/image-6.png" alt=""></p>
<ul>
<li><code>가상 노드</code> : 실제 노드 또는 서버를 가리키는 노드로서, <strong>하나의 서버는 링 위에 여러 개의 가상 노드를 가질 수 있다.</strong><ul>
<li>각 서버는 하나가 아닌 <strong>여러 개 파티션을 관리</strong>해야 한다<blockquote>
<p>❓실 사용 사례에서는 가상 노드의 개수를 어느정도로 가져가는가?</p>
</blockquote>
</li>
</ul>
</li>
<li>가상 노드의 개수가 늘어날 수록 <strong>표준 편차가 작아져 데이터가 고르게 분포</strong>된다.</li>
<li>하지만 <strong>가상 노드 데이터를 저장할 공간은 더 많이 필요하게 된다</strong><blockquote>
<p>❓가상 노드는 실제 노드/서버랑 동일 스펙으로 구성되나?</p>
<ul>
<li>가상 노드는 <strong>실제 물리적인 서버가 아니라</strong>, 해시 링 상에서 실제 서버를 여러 개로 나누어 표현하는 <strong>논리적 개념</strong>이다</li>
<li>따라서 실제 데이터를 저장하지 않고, 단지 <strong>실제 서버로 데이터를 라우팅하는 역할</strong>을 한다<ul>
<li>데이터 분산과 로드 밸런싱을 위한 메타데이터로 사용</li>
</ul>
</li>
<li>가상 노드에서 실제 노드로의 데이터 전달 과정<ul>
<li>키 해싱: 데이터의 키가 해시되어 해시 링 상의 위치가 결정됨</li>
<li>가상 노드 매핑: 해당 위치에서 시계 방향으로 가장 가까운 가상 노드를 찾음</li>
<li>실제 서버 식별: 각 <strong>가상 노드는 자신이 속한 실제 서버의 정보를 가지고 있으므로</strong> 이 정보를 사용하여 해당 데이터가 어느 실제 서버로 가야 하는지 결정</li>
<li>데이터 전송: 식별된 실제 서버로 데이터가 직접 전송</li>
<li>저장 및 처리: 실제 서버에서 데이터를 저장하고 처리</li>
</ul>
</li>
<li>메모리 내 데이터 구조<ul>
<li>가상 노드 정보는 주로 <strong>해시 맵이나 배열과 같은 메모리 내 데이터 구조</strong>에 저장됨</li>
<li>이 구조는 가상 노드의 ID와 해당 실제 노드의 정보(예: IP 주소, 포트 번호)를 매핑함</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
<h2 id="마치며">마치며</h2>
<ul>
<li>안정 해시의 이점<ul>
<li>서버가 추가되거나 삭제될 때 <strong>재배치되는 키의 수가 최소화</strong>된다.</li>
<li>데이터가 보다 <strong>균등하게 분포</strong>하게 되므로 수평적 규모 확장성을 달성하기 쉽다.</li>
<li><strong>핫스팟 키 문제를 줄인다</strong>. 특정한 샤드에 대한 접근이 지나치게 빈번하면 서버에 과부하 문제가 생길 수 있다.</li>
</ul>
</li>
<li>안정 해시의 유명한 사례<ul>
<li><a href="https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html">아마존 DynamoDB의 파티셔닝 관련 컴포넌트</a></li>
<li>아파치 카산드라 클러스터에서의 데이터 파티셔닝</li>
<li><a href="https://discord.com/blog/how-discord-scaled-elixir-to-5-000-000-concurrent-users">디스코드 채팅 어플리케이션</a></li>
<li>아카마이 CDN</li>
<li>매그레프 네트워크 로드 밸런서</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[return 2024;  새로운 도전과 성취의 2024년 (1)]]></title>
            <link>https://velog.io/@eunz_juu/return-2024-%EC%83%88%EB%A1%9C%EC%9A%B4-%EB%8F%84%EC%A0%84%EA%B3%BC-%EC%84%B1%EC%B7%A8%EC%9D%98-2024%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@eunz_juu/return-2024-%EC%83%88%EB%A1%9C%EC%9A%B4-%EB%8F%84%EC%A0%84%EA%B3%BC-%EC%84%B1%EC%B7%A8%EC%9D%98-2024%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 02 Feb 2025 14:08:53 GMT</pubDate>
            <description><![CDATA[<h2 id="intro">Intro</h2>
<p>어느덧 기나 긴 2024년도 끝이 났다 ~! 
2024년 12월 말부터 회고를 썼는데 미루고 미루다가 2025년 한달이 지난 이 시점에도 회고를 계속 쓰고 있다..ㅎㅎ (게으름이슈.......🤧
매년 회고를 할 때마다 느끼는건데 작년보다 이것저것 한 것도 많은 것 같고 후회 없이 보낸 것 같은데 연말이 되면 항상 뭘 했는지 모르겠는 느낌이 들곤 한다.
그래서 더더욱 회고를 쓰면서 한 해동안 있었던 나의 성취와 다양한 경험을 떠올려 보려 하는 것 같다.
돌이켜보면 매년 나는 지나온 한 해에 대한 후회가 없었고 내가 할 수 있는 최선을 다했다고 생각한다. 
누가 뭐라하든 스스로 내가 보낸 한 해에 만족했으면 되지 않나 싶고... 그런 점에서 2024년 한해도 참 수고했다 !!!!
올해도 투두메이트 열심히 채우면서 잘 살았다 😁
<img src="https://velog.velcdn.com/images/eunz_juu/post/057422af-04db-4281-a80f-45ff47960396/image.png" alt=""></p>
<p>2024년은 크게 4가지의 키워드로 나눠볼 수 있을 것 같아, 키워드 별로 여러가지 경험들을 정리해보고자 한다!
4번 업무에 대한 내용은 쓰다보니 길어지는 것 같아서 업무 회고로 따로 작성해보겠다 🙈</p>
<blockquote>
<h3 id="2024년-키워드">2024년 키워드</h3>
<ol>
<li>운동</li>
<li>자기계발</li>
<li>기부</li>
<li>업무</li>
</ol>
</blockquote>
<h2 id="운동">운동</h2>
<h3 id="1-처음으로-6개월-이상-꾸준히-하고-있는-운동"><strong>1. 처음으로 6개월 이상 꾸준히 하고 있는 운동</strong></h3>
<img src="https://velog.velcdn.com/images/eunz_juu/post/d8039441-1778-4b54-ab04-e9273e3e4aa5/image.png" width="70%" height="70%">

<p>24년 1,2월만 해도 나는 매주 병원에 가서 도수치료를 받았고 허리 주사를 맞았고 매주 20만원 이상 병원비를 긁었다 😡 
허리가 아프다고 느껴진 순간, 그 전과는 정말 달라진 삶이 된다 
같은 자세로 오래 앉아있기 힘들고, 어딜가도 의자가 편한 지를 체크하고, 내가 어떤 자세로 앉아있는지 의식적으로 생각하곤 한다. 이 글을 읽고 있는 아직 허리 안 아픈 사람들은 자세를 고쳐 앉길 바란다… </p>
<p>2023년 말 처음으로 PT 를 일주일에 2번씩 받으면서 2024년의 나는 허리가 안아프겠지 기대하며 운동을 했다. 
하지만 무게들고 운동을 하면서 허리에 더 무리가 갔고 나는 PT를 그만두었다.
1월부터는 혼자 헬스장을 끊고 운동을 가야겠다고 다짐하였지만 한해의 절반이 간 7월까지도 나는 운동을 하지 않았다.</p>
<p>까지가 나의 2024년 7월까지의 게으른 모습이다...</p>
<p>2024년 8월에 드디어 26년을 살면서 항상 나에게 큰 과제이자 넘어야 할 큰 산처럼 느껴졌던 운동을 시작했다.
운동이 너무 싫어서 운동하느니 차라리 공부를 하겠다고 생각했던 내가 일주일에 최소 1~2번은 운동을 했다. (장족의 발전 😁
사실 가기 싫어서 미루고 미루다가 회원권 끝나기 전에 일주일에 4번씩 간적도 있긴 하다.. 이때 자주가느라 너무 힘들어서 이제는 한 주에 2번은 가는 걸 목표로 하고 있다</p>
<p>맨몸운동이다보니 무게를 많이 들지는 않는데 이젠 조금씩 무게도 올려서 운동을 하게 되었다. 
조금씩이라도 무게를 올리는 재미도 있고, 어느정도 코어가 잡혀가고 있어 다행이다! 
그동안은 땀흘리며 운동하는 거 자체가 찝찝하고 불편하게 느껴졌었는데, 올해는 운동 후의 개운함과 뿌듯함을 느끼게 되어서 감사하다. </p>
<p>운동 후에 단백질을 챙겨먹으라는 강사님의 말도 잘 듣고 있다. 
팀원에게 단백질 음료도 추천 받아서 덕분에 가성비 좋은 단백질 음료도 새로 먹고 있고, 닭가슴살 소세지도 즐겨먹고 있다. 
운동 끝나고 단백질 챙겨먹는 시간들도 나에게 소소한 즐거움이 되었다. 왜 이렇게 단백질 타령을 하냐고 친구가 뭐라 한 적도 있을만큼 단백질 챙겨먹어야 된다고 말을 많이 한 것 같다 ㅎ,,,ㅎ</p>
<p>운동 끝나고 배고파서 붕어빵 2개를 품에 안고 집까지 뛰어간 적이 있는데, 그 순간의 행복이 잊혀지지 않는다.
이게 소확행이구나 싶었고, 그 시점 이후로 운동 후의 (붕어빵 사는) 시간들을 더 좋아하게 된 것 같다. 붕어빵 파는 겨울 너무 소중해 &gt;_&lt;
올해도 열심히 끊어서 <strong>2025년은 근력량 높이기와 허리가 아프지 않은 선에서 안전하게 운동하는 것에 집중</strong>해야겠다 💪 아자아자 !!!!! </p>
<h3 id="2-꾸준한-스트레칭-루틴">2. 꾸준한 스트레칭 루틴</h3>
<p>어깨, 등, 손목 스트레칭 뿐만 아니라 (내가 맹신하는) 정선근 교수님이 알려준 종아리 운동, 신전 운동은 거의 매일 했다.
운동을 못가는 날에도 조금씩이라도 꾸준히 해서 루틴으로 자리잡은 것 같아 다행이다. 
신전운동을 하면 뻣뻣했던 허리가 말랑말랑(?) 해지는 느낌이 들어서 좋다.....ㅎㅎㅎㅎ 허리 아픈 사람들에게 강추
올 한해도 열심히 스트레칭해서 허리 통증을 작년보다 줄여보자 !!!!</p>
<h3 id="3-새로운-도전--마라톤-참여">3. 새로운 도전 : 마라톤 참여</h3>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/e9f10d1e-e703-445c-bacd-ea8cddbae24a/image.png" alt=""></p>
<p>올 여름 제일 더울 때 마라톤에 첫 참가를 했다. 
뛰는 거 + 땀 나는 것을 질색하던 내가 어떤 마음으로 갑자기 러닝이 하고 싶어진지 아직도 나를 잘 모르겠지만... 완주하고 느껴지는 후련함 + 뿌듯함은 이루 말할 수 없었다. 그래서 첫 5km 마라톤을 완주하고 그 자리에서 바로 2번째 마라톤을 신청했다.
사실 러닝 연습은 한번도 안하고 마라톤만 하러 나갔지만 ㅋㅋㅋㅋㅋ 러닝 연습은 안해도 운동을 하고 있어서 체력이 늘어 그런가 매번 페이스가 줄어드는 게 너무 뿌듯했다. 
그런데 이제 더이상 연습 없이는 페이스를 단축시킬 자신이 없어서 ^.ㅠ 내년에는 꼭 연습을 하고 기록단축에도 도전해보고 싶다 !! 
특히 허준런은 5km 를 한 번도 쉬지 않고 뛰었어서 더 뿌듯했던 것 같다,,,내가 5km 를 쉼없이 뛸 수 있는 사람이었다니 !!!!!! 하는 놀라움이 나에게도 있었다.
<img src="https://velog.velcdn.com/images/eunz_juu/post/9f2049da-72c1-454e-9159-8c54652ba09f/image.png" alt=""></p>
<p>그리고 내년에는 10km 마라톤에도 도전해서 70분 안에 완주해보는 걸 목표로 하고 싶다 ! 러닝할 때 신을 운동화도 장만해야지...
그동안 내가 싫어한다고 생각해왔지만 마라톤을 한번 도전해봄으로써 <strong>내가 좋아하는 운동을 발견하게 된 계기</strong>가 되었다. 
2024년에 기억남는 회고 키워드 중 빠지지 않는게 마라톤일 정도로,, ㅎㅎㅎ 내년에도 열심히 달려보자 !!!! 🤸🏻‍♀️ </p>
<h2 id="자기계발">자기계발</h2>
<h3 id="1-매일-조금씩-감사하는-습관">1. 매일 조금씩 감사하는 습관</h3>
<p>글또 9기가 끝난 후 감사회고채널에서 10기가 시작되기 전까지 카톡으로 감사회고를 나눌 사람을 모집하고 있었다 ! 
감사회고채널에서 활동하지 않았는데도 정말 우연하게 그 스레드를 보게 되었다. 운명이었을지도...
감사일기를 예전부터 쓰려고 몇번 시도를 했는데 혼자 메모장에 쓰니까 꾸준히 써지지가 않았다. 길어봤자 한달 정도? 지속되었던 것 같다. 
그래서 여러 명이 함께 있는 채널에서라면 꾸준히 할 수 있을 것 같아 참여를 하게 되었는데 이 습관이 생각보다 길게 지속되고 있다 ! 
<strong>24년 6월 중순을 기점으로 매일 매일 감사회고를 꾸준히 적었다.</strong> 아무리 생각나는 게 없어도 쥐어짜내서 5개씩은 적어보자는 나만의 규칙을 정했다. 
<img src="https://velog.velcdn.com/images/eunz_juu/post/780a6c77-4058-4930-8236-e436b045c41c/image.png" alt="">
어떤 날은 정말 한 게 없어서 저녁이 맛있어서 감사하고 하루종일 누워있어서 감사하고.. 이런 시덥잖은 내용들도 적었지만 그 소소한 일상들마저 감사할 거리라는 것을 느낄 수 있었던 것 같다.</p>
<p>안 좋은 일이 생기더라도 그 순간에 감사하는 마음을 가지기까지는 많은 시간이 필요할 것 같지만 하루를 돌아보면서 작은 것에라도 감사하는 마음이 생긴다는 것만으로도 달라지고 있는 것 같다.. ㅎㅎ 조금씩 긍정적으로 마인드가 바뀌었으면 하는 마음으로 올해도 감사일기 쓰기는 계속 해보아야겠다 🙂</p>
<h3 id="2-책과-친해진-2024년">2. 책과 친해진 2024년</h3>
<p>2024년은 책과 가까워진 한 해였다 !
2023년 7월에 저어어어엉말 오랜만에 책을 읽기 시작했다. 초등학생 때 이후로는 거의 책을 1년에 1권을 읽을까말까 했는데 ㅎㅎ... 
23년 7월에 이유는 모르겠지만 갑자기 &#39;개발 책 좀 읽어보고 싶은데?&#39; 라는 생각이 들어서 친구랑 무작정 시작했던 북스터디가 이렇게까지 내가 책을 좋아하게 만들 줄이야... 역시 사람 일은 모를 일이다 🧐</p>
<p>올 한해 동안은 <strong>개발 9권, 비개발 12권으로 총 21권의 책</strong>을 읽었다. 오랜만에 책을 붙잡고 한 해동안 열심히 읽어보았는데 내 생각보다 많은 책을 읽었다는 생각이 들었다! 
책을 읽다보니까 자연스레 교보문고 어플에 자주 들어가게 되고, 들어가서 재미있어 보이는 책들을 찜해두니까 찜 목록에도 무려 50권의 책이 담겨져있다 ㅋㅋㅋㅋ 앞으로도 많은 책을 다양하게 읽어보면서 견문을 넓히고 싶다 ! 책을 읽으면 다른 사람들이 삶을 통해 배우고 느낀 지혜를 배우는 느낌이라 참 좋다... </p>
<p>올해는 교보문고 오프라인 매장도 많이 갔다. 
방앗간을 그냥 지나치지 못하는 참새처럼... 서점만 보이면 홀린 듯이 들어가서 책 몇권이라도 뒤적거리고 구경하다가 나온 것 같다. 
서점은 언제가도 새로운 책들과 내 관심을 끄는 책들이 많아서 참 힐링되고도 좋은 공간인 것 같다. 
서점가서 이것저것 구경하다가 올해는 학생때 이후로 오랜만에 문구류도 많이 샀다! 
책 읽을 때 밑줄 그을 색연필, 필기할 볼펜들, 제주도 가서 책 읽을 때 필기하려고 산 블랙윙 연필 등... 요즘 블랙윙 연필로 필기하고 노란색 형광색연필로 밑줄 그으며 책읽는게 너무 좋다</p>
<p>조금 아쉬운 점은 분야 편식을 심하게 했다는 점이다.
개발 서적 제외하고는 자기계발 분야나 일에 대한 책들만 골라 읽었다. 
사실 문학도 읽어보고 싶고 특히 경제/재테크 분야 책을 읽고 싶었는데 !!!!!!! 다 우선순위에 밀려서 읽지 못했다 🥲 </p>
<p>그리고 내가 읽은 모든 책에 대해서 밑줄 친 내용을 정리했지만, 한줄평과 같은 찐 요약을 안해놨다보니까 나중에 그 책에서 중요하게 전달하려는 말이 무엇이었는지 기억이 안 난 경우가 많았다.
그래서 이제는 책을 완독하자마자 그 책에 대한 한줄평을 적어보려고 한다.</p>
<p>올 한해는 다양한 분야의 책을 읽고 실제 삶에 적용해보는 경험들을 더더더 많이 해보고 싶다.
아 내용 좋다 ~! 하고 뒤돌아서면 잊어버리지 않고, <strong>삶을 변화시킬 수 있는 액션포인트들을 많이 찾아서 실천</strong>해보자 !!!!!</p>
<h3 id="3-야무지게-사용한-말해보카-1년-이용권">3. 야무지게 사용한 말해보카 1년 이용권</h3>
<img src="https://velog.velcdn.com/images/eunz_juu/post/3e117d4c-4b17-4ca7-a9ce-8848603f181f/image.png" width="50%" height="40%">

<p>2023년 7월? 에 친구 추천으로 말해보카를 처음 하게 되었다. 
말해보카 처음한 날은 재밌어서 새벽 2시까지 하다 잤던 기억이 있다 ㅋㅋㅋㅋ
말해보카에는 1년 이용권이 있음에도 불구하고 내가 1년동안 이걸 꾸준히 할 자신이 없어서 한 3개월을 월 이용권으로 간보다가, 2023년 10월부터 1년 이용권을 끊고 시작하게 되었다.</p>
<p>친구와 매일 조금씩이라도 말해보카를 하자는 목표를 세웠고, 안하면 하루에 천원씩 벌금을 내기로 했다.
그렇게 우리는 2024년 10월까지 1년 이용권을 야무지게 다 사용하고, 또 다시 1년 이용권을 끊었다 !!!!
말해보카에서 2024년 RECAP 을 보내줬는데 365일 중 353일이나 했으니 그래도 꾸준히 잘한 것 같다 ㅎㅎㅎ
내년에는 단어 뿐만 아니라 리스닝/문법도 같이 꾸준히 해보려고 한다 !!!!! 올해도 열심히 말해보카를 영업하는 한 해가 되길... </p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/104f5d76-321e-463e-8d29-bee6d0da39aa/image.png" alt=""></p>
<h2 id="기부">기부</h2>
<p>어렸을 때부터 엄마로부터 &quot;너가 가진 것을 나누면서 살아라~ 베풀면서 살아라~&quot; 이런 류의 이야기를 많이 들으며 자랐다. 그 때는 그냥 별 생각 없이 지나갔던 말들이었는데, 그런 이야기들을 많이 들은 영향이었는지 성인이 되었을 때 내가 생각하는 이상적인 삶은 &quot;<strong>내가 가진 것을 나눌 줄 아는 사람이 되는 것</strong>&quot; 이었다.</p>
<p>나누는 것이 돈일 수도, 내가 가진 능력일수도 있는데, 그 2가지를 모두 나누었던 한 해라 뿌듯했던 것 같다. 아직은 소소한 금액이지만 조금 더 여유가 생기면 더 유의미한 변화를 만들어낼 수 있는 금액으로 나눔을 실천해보고 싶다.</p>
<p><strong>1. 아이케어노트 프로젝트</strong>
회사 내에서 재능 기부 형태로 프로젝트에 참여할 사람을 모집했는데, 이 프로젝트에 참여하였다.
아이케어노트 관련한 내용은 4번 업무 카테고리에서 적어보려고 한다.</p>
<p><strong>2. 승일희망재단</strong>
<img src="https://velog.velcdn.com/images/eunz_juu/post/e7a66308-d1eb-4d53-bb82-cef2ddf244f8/image.png" alt=""> 그룹사 임직원 대상으로 승일희망재단과 협업한 캠페인을 진행했다. 개인적으로 기부하는 것도 좋지만 회사 내에 속해 임직원으로서 캠페인에 참여하여 도움을 주는 것도 의미가 있을 거라고 생각이 들어 바로 참여를 하게 되었다 ! 
일시 기부의 형태였지만 취업하고 돈을 번 이후로 처음으로 기부한 경험이라서 뜻깊었고 뿌듯했던 것 같다 ㅎㅎ 이 시점을 기반으로 조금 더 기부에 관심이 갔달까.. ?!</p>
<p><strong>3. 컴패션</strong>
<img src="https://velog.velcdn.com/images/eunz_juu/post/42717da6-d9d0-4e4c-868d-9a593ea84833/image.png" alt="">
컴패션은 엄마가 기부하고 있는 곳이어서 알게 되었는데 어린이 한명에게 정기적으로 후원하는 형태이다. 아이에게 조금 더 금전적인 도움이 되길 바라는 마음에서, 한국 돈으로 환산했을 때 그 나라에서 더 많은 가치를 가질 수 있는 국가로 선택을 했다.
아이가 2살이라서 아직 스스로 글을 써서 나에게 편지를 보내주진 못하지만..ㅎㅎ 이 아이가 자라가는 과정을 볼 수 있다는 점이 벌써 기대가 된다 ! 편지도 써야하는데 자꾸만 까먹게 된다... 잘 자라라는 의미에서 편지도 이번달에는 꼭 써서 보내야겠다 !! </p>
<p><strong>4. 개발자 잔디 기부 캠페인</strong>
<img src="https://velog.velcdn.com/images/eunz_juu/post/33a6784b-5f31-46d7-ac5c-ff29a75eda77/image.png" width="50%" height="50%"></p>
<p>연말에 항해99 에서 개발자 잔디 기부 캠페인을 한다고 해서 참여했다. 잔디 1개당 100원으로 환산한 금액을 기부하고, 저소득층 아동들에게 코딩 교육을 지원하는데 쓰인다고 한다. 생각보다 2024년에 커밋을 얼마안해서.. 406개라 4만원 밖에 안되지만 😅 그래도 개발과 관련해서 기부할 수 있어서 신기하기도 했고 더 의미있게 느껴졌다 !! 코딩을 배우고 싶어하는 아기들에게 작게나마 도움이 되길,, </p>
<p><strong>5. 사랑의 열매</strong>
<img src="https://velog.velcdn.com/images/eunz_juu/post/990d4277-2b9e-4d9e-8b19-54d2d92481c8/image.png" width="30%" height="30%">
크리스마스에 광화문에 갔는데 사랑의 열매 부스가 있었다. 2천원을 기부하고 열매네컷을 찍는 캠페인이었는데 역대급 소소한 금액이었지만 ㅎ_ㅎ 캠페인에 참여한다는 것에 의의를 두고 사진 한장 남겨왔다 !</p>
<p>올해는 총 5가지의 나눔을 실천해보았는데, 작은 금액이더라도 내가 가진 것을 나누는 것만으로도 나에게 더 큰 행복과 기쁨이 돌아온다는 것을 느낄 수 있었다. 금액에 연연하지 않고 누군가에게 도움이 될 수 있다는 것만으로 뿌듯함을 느끼면서 앞으로도 꾸준히 기부를 해보아야겠다 !! 그리고 돈 뿐만 아니라 재능기부의 형태로 내가 가진 능력을 나누어보는 것도 많이 해보고 싶다 😁</p>
<h2 id="outro">Outro</h2>
<p>아직 업무 회고가 남았긴 하지만 잠깐 2024년을 총 정리(?) 해보려고 한다. ㅎㅎ 
업무 외적인 경험들을 되돌아보면 <code>새로운 시작</code>, <code>작은 반복의 힘</code> 이라는 키워드로 정리해볼 수 있을 것 같다.
마라톤과 운동을 새롭게 시작했고, 매일 책을 읽고 영어공부도 하고, 감사회고도 꾸준히 했다. 
특히 올해 신경쓴 점은 매일 루틴을 정해서 시간을 촘촘하게 사용하려고 노력했는데 그 덕분에 생각보다 많은 기술 서적도 읽었고 자기계발에 많은 시간을 투자할 수 있었다.</p>
<p>최근 읽은 책에 &quot;반복의 위력은 결코 과소평가될 수 없다&quot; 라는 문장이 있었다.
정말 격하게!!!!!!! 공감한다.</p>
<p>매일 조금씩이라도 반복하고 꾸준히 하는 습관. 
그걸 나의 강점이라고 생각하고 있고, 실제 갤럽 강점 검사에서도 성취가 1등으로 나왔다,,ㅎ
내가 생각보다 성취감에 죽고 못사는 사람인 것 같다는 걸 2024년의 경험을 통해 느꼈다.</p>
<p>최근에도 친구랑 대화를 하다가 &quot;너 꾸준하게 하는 거 하나는 진짜 잘한다&quot; 라는 이야기를 들었다. 
솔직히 진짜 기분이 좋았다. 내가 생각하는 장점이자 강점을 다른 사람의 입을 통해 한번 더 확인 받은 느낌이라 그랬던 것 같다.</p>
<p><strong>꾸준함을 통해 얻을 수 있는 성취감, 작은 성취감들이 모여서 더 큰 도전을 할 수 있는 원동력</strong>이 된다.
그리고 이 성취감들이 모여서 내가 어떤 것에 도전하더라도 난 할 수 있을 거라는 믿음을 갖게 되는 것 같다.
요즘 참 근자감인지 뭔지 모르겠지만,,, 어떤 것을 처음 시작할 때는 두렵게 느껴지지만 어느 순간 &#39;뭐 나는 해낼 수 있겠지&#39; 라는 생각이 들면서 나 자신에 대한 믿음이 커진 것 같다. 
그게 결국 무언가를 조금씩 반복하며 내가 원하는 삶을 만들어가고 있기 때문에 느낄 수 있었던 감정인 것 같다.</p>
<p>2024년은 꾸준함의 힘을 체감한 한 해였는데, 2025년에는 이를 더 단단한 일상으로 만들어보고 싶다.
또한 취미 부자가 되는 게 목표여서 온전히 나로 존재할 수 있을 수 있는 퇴근 후, 주말의 시간들을 더 다채롭게 꾸며보고 싶다.
*<em>삶을 재미있게 내가 좋아하는 것들로 가득 채워서 즐겁게 살고 싶다. 
*</em>2025년은 다채로운 삶의 시작점이 될 수 있길 !!! 🌺🌺🌺</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초 1] 4. 처리율 제한 장치의 설계]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-4.-%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/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-4.-%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>Sun, 19 Jan 2025 12:32:09 GMT</pubDate>
            <description><![CDATA[<h1 id="4장-처리율-제한-장치의-설계">4장. 처리율 제한 장치의 설계</h1>
<ul>
<li>처리율 제한 장치 (rate limiter) : 클라이언트 혹은 서비스가 보내는 <strong>트래픽의 처리율을 제어</strong>하기 위한 장치<blockquote>
<p>❓ circuit breaker 랑 차이점?</p>
</blockquote>
<ul>
<li>HTTP 를 예로 들면 이 장치는 <code>특정 기간</code> 내에 전송되는 <code>클라이언트 요청 횟수</code>를 제한함</li>
<li><strong>API 요청 횟수</strong>가 제한 장치에 <strong>정의된 임계치를 넘어서면, 추가로 도달한 모든 호출은 처리가 중단</strong>됨</li>
</ul>
</li>
<li>API 에 처리율 제한 장치를 두면 좋은 점<ul>
<li>DoS 공격에 의한 자원 고갈 방지</li>
<li>비용 절감<ul>
<li>우선순위가 높은 API에 더 많은 자원을 할당할 수 있음</li>
</ul>
</li>
<li>서버 과부하 <ul>
<li>봇에서 오는 트래픽이나 사용자의 잘못된 이용패턴으로 유발된 트래픽을 걸러내는 데 활용할 수 있음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1단계-문제-이해-및-설계-범위-확정">1단계. 문제 이해 및 설계 범위 확정</h2>
<ul>
<li>서버측 API를 위한 장치</li>
<li>다양한 형태의 제어규칙을 정의할 수 있는 유연한 시스템<blockquote>
<p>❓어떤 사례가 있을까?</p>
</blockquote>
</li>
<li>대규모 요청 처리할 수 있어야 함</li>
<li>분산 환경에서 동작해야 함</li>
<li>한 서버에 대해 제어 규칙이 바뀔 수도 있다는 전제</li>
<li>독립된 서비스 or 애플리케이션 코드 포함할 것인가?</li>
<li>사용자의 요청이 걸러진 경우, 사용자에게 그 사실을 알려야 함</li>
</ul>
<h3 id="요구사항">요구사항</h3>
<ul>
<li>낮은 응답시간</li>
<li>가능한 한 적은 메모리</li>
<li>분산형 처리율 제한 : 여러 서버나 프로세스에서 공유할 수 있어야 함</li>
<li>예외 처리</li>
<li>높은 결함 감내성 : 제한 장치에 장애가 생겨도 전체 시스템에 영향을 주어서는 안됨</li>
</ul>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/1%EB%8B%A8%EA%B3%84%20%EC%84%A4%EA%B3%84.png" alt=""></p>
<h2 id="2단계-개략적-설계안-제시-및-동의-구하기">2단계. 개략적 설계안 제시 및 동의 구하기</h2>
<h3 id="처리율-제한-장치는-어디에-둘-것인가">처리율 제한 장치는 어디에 둘 것인가?</h3>
<ul>
<li>클라이언트 측에 둘 경우, 클라이언트 요청은 쉽게 위변조가 가능해서 모든 클라이언트의 구현을 통제하는 것이 어려울 수 있다</li>
<li>처리율 제한 장치를 API 서버에 두는 대신 처리율 제한 미들웨어를 만들어 처리할 수도 있다.</li>
<li>마이크로서비스의 경우, 처리율 제한 장치는 보통 API 게이트웨이에 구현된다.<blockquote>
<p>❓ 마이크로서비스가 아닌 경우에는 보통 어디에 구현되는가?</p>
</blockquote>
</li>
</ul>
<h3 id="처리율-제한-알고리즘">처리율 제한 알고리즘</h3>
<h4 id="토큰-버킷-알고리즘">토큰 버킷 알고리즘</h4>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/image.png" alt="alt text"></p>
<ul>
<li>간단하고, 인터넷 기업들이 보편적으로 사용하고 있다</li>
<li>아마존, 스트라이프가 API 요청을 통제하기 위해 이 알고리즘 사용한다</li>
<li>동작 원리<ul>
<li>토큰 버킷 : 지정된 용량을 갖는 컨테이너</li>
<li>해당 버킷에 사전 설정된 양의 토큰이 주기적으로 채워짐 </li>
<li>토큰이 꽉 찬 버킷에는 더이상 토큰이 추가되지 않음</li>
<li>버킷이 가득차면 추가 공급된 토큰은 버려짐</li>
<li>각 요청은 처리될 때마다 1개의 토큰 사용<ul>
<li>충분한 토큰이 있다면, 버킷에서 토큰을 꺼낸 후 요청을 시스템에 전달</li>
<li>충분한 토큰이 없다면, 해당 요청은 버려짐</li>
</ul>
</li>
</ul>
</li>
<li>토큰 버킷 알고리즘은 2가지 인자를 받는다<ul>
<li>버킷 크기 : 버킷에 담을 수 있는 토큰 최대 갯수</li>
<li>토큰 공급률 : 초당 몇 개의 토큰이 버킷에 공급되는지<blockquote>
<p>토큰 공급률이 결국 처리량과 관련있는 지표이므로, 얘도 경계에 요청이 몰리는 문제는 해결 불가능</p>
</blockquote>
</li>
</ul>
</li>
<li>통상적으로 API 엔드포인트마다 별도의 버킷을 둔다</li>
<li>장점<ul>
<li>구현이 쉽다</li>
<li>메모리 사용 측면에서 효율적이다<blockquote>
<ol>
<li>단순한 데이터 구조 : 버킷 크기와 현재 토큰 수만 관리하면 됨 (레디스)</li>
<li>누출 버킷 알고리즘과 달리 개별 요청을 큐에 저장할 필요 없어 메모리 사용 줄일 수 있음</li>
</ol>
</blockquote>
</li>
<li><strong>짧은 시간에 집중되는 트래픽도 처리 가능</strong>하다. 버킷에 남은 토킷만 있으면 요청은 시스템에 전달된다.<blockquote>
<p>단기간 집중되는 트래픽 처리 불가능한 알고리즘 == 누출 버킷 알고리즘 <br>
토큰 버킷 알고리즘은 초당 10만개의 토큰이 공급된다면 </p>
<ul>
<li>0.1초만에 10만 개의 요청이 와도 처리가능. </li>
<li>0.9초동안 온 5만개의 요청은 버려짐 <br></li>
</ul>
<p>하지만 누출 버킷 알고리즘 (요청 수용 가능한 큐 크기 :15만개 / 처리율 10만개라면) 은 0.1초만에 10만개의 요청이 올 경우 0.9초동안 온 나머지 5만개의 트래픽 요청은 큐에 담겨 있다가 1초뒤에 처리됨</p>
</blockquote>
</li>
</ul>
</li>
<li>단점<ul>
<li>버킷 크기, 토큰 공급률이라는 2가지 인자를 적절히 튜닝하는 것이 어렵다<blockquote>
<p>버킷 크기 : 머신 러닝이나 예측 알고리즘을 사용하여 감지된 패턴에 따라 버킷 크기 동적 조절 필요</p>
<ul>
<li>Akamai의 2023년 연구에 따르면, 동적 토큰 버킷 크기 조정은 전자상거래 애플리케이션의 피크 트래픽 기간 동안 지연 시간을 15% 감소시켜 고객 만족도를 향상시켰다고 함<br></li>
</ul>
<p>토큰 공급률 : 예상 평균 트래픽과 일치해야 함.</p>
<ul>
<li>분당 100개의 요청이 예상되는 API는 분당 약 100개의 토큰을 허용하되, 약간 더 높은 버스트 허용치를 두어야 함</li>
</ul>
<p>실제 적용 사례</p>
<ol>
<li>Twitter의 API 관리</li>
</ol>
<ul>
<li>도전 과제: Twitter는 개발자들에게 관대한 API 한도를 제공하면서도 남용을 방지해야 함</li>
<li>해결책: 토큰 버킷 알고리즘을 구현하여 높은 API 트래픽을 관리하고 조용한 시간대에는 버스트를 허용. 사용자 행동에 따라 버킷 크기를 동적으로 조정하여 공정성과 성능의 균형을 맞춤</li>
<li>결과: Twitter는 정당한 개발자들의 원활한 경험을 유지하면서 API 남용 사건을 40% 줄임</li>
</ul>
<ol start="2">
<li>Zoom의 대역폭 관리</li>
</ol>
<ul>
<li>도전 과제: Zoom은 팬데믹 기간 동안 대규모 트래픽 급증을 경험했고, 이는 인프라에 부담을 주었음</li>
<li>해결책: 토큰 버킷 메커니즘으로 비디오 스트리밍 대역폭을 제어하여 고해상도 스트림을 위한 짧은 버스트를 허용하면서 피크 사용 시간 동안 지속적인 트래픽을 제한</li>
<li>결과: Zoom은 고트래픽 기간 동안 서비스 중단을 최소화하고 대역폭 관련 불만을 25% 줄임</li>
</ul>
<p>Reference; <a href="https://medium.com/@keployio/the-importance-of-the-token-bucket-algorithm-key-strategies-and-best-practices-for-businesses-faeb52c597d2">https://medium.com/@keployio/the-importance-of-the-token-bucket-algorithm-key-strategies-and-best-practices-for-businesses-faeb52c597d2</a></p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4 id="누출-버킷-알고리즘">누출 버킷 알고리즘</h4>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/image-1.png" alt="alt text"></p>
<ul>
<li>누출 버킷 알고리즘은 토큰 버킷과 비슷하지만 <strong>요청 처리율이 고정되어 있다</strong>는 점이 다르다<blockquote>
<p>요청 처리율이 고정되어 있다 == 지정된 시간당 처리 가능한 양 (처리율) 이 고정되어 있어서 트래픽이 몰리더라도, 한번에 처리되지 않고 처리율에 따라 일정하게 출력된다</p>
</blockquote>
</li>
<li>보통 <strong>FIFO 큐</strong>로 구현한다</li>
<li>동작 원리<ul>
<li>요청이 도착하면 큐가 가득 차 있는지 확인. 빈자리가 있는 경우에는 큐에 요청을 추가</li>
<li>큐가 가득 차 있는 경우에는 새 요청은 버림</li>
<li>지정된 시간마다 큐에서 요청을 꺼내어 처리 (처리율)</li>
</ul>
</li>
<li>누출 버킷 알고리즘은 2가지 인자를 사용한다<ul>
<li>버킷 크기 : 큐 사이즈와 동일</li>
<li>처리율 : 지정된 시간 당 몇 개의 항목을 처리할지 지정하는 값. 보통 초 단위로 표현</li>
</ul>
</li>
<li>장점<ul>
<li>큐의 크기가 제한되어 있어서 <strong>메모리 사용량 측면에서 효율적</strong></li>
<li>고정된 처리율을 가지므로 <strong>안정적 출력</strong>이 필요한 경우가 적합하다.</li>
</ul>
</li>
<li>단점<ul>
<li>단시간 트래픽이 몰릴 경우, <strong>오랜 요청이 쌓여 최신 요청이 버려진다.</strong></li>
<li>토큰 버킷과 마찬가지로 2개의 파라미터를 올바르게 튜닝하기 어렵다.</li>
</ul>
</li>
</ul>
<h4 id="고정-윈도-카운터-알고리즘">고정 윈도 카운터 알고리즘</h4>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/image-2.png" alt="alt text"></p>
<ul>
<li><p>동작 원리 </p>
<ul>
<li>타임라인을 고정된 간격의 윈도로 나누고, 각 윈도마다 카운터를 붙임</li>
<li>요청이 접수될 때마다 이 카운터의 값은 1씩 증가</li>
<li>이 카운터의 값이 사전에 설정된 임계치에 도달하면 새로운 요청은 새 윈도가 열릴 때까지 버려짐</li>
</ul>
</li>
<li><p>이 알고리즘의 가장 큰 문제는 <strong>윈도의 경계 부근에 순간적으로 많은 트래픽이 집중될 경우, 윈도에 할당된 양보다 더 많은 요청이 처리될 수 있다</strong>는 점이다.</p>
</li>
<li><p>장점</p>
<ul>
<li>메모리 효율이 좋다<blockquote>
<p>레디스를 활용해서 카운터 값만 저장하고 있으면 됨</p>
</blockquote>
</li>
<li>이해하기 쉽다</li>
<li>윈도가 닫히는 시점에 카운터를 초기화하는 방식은 특정한 트래픽 패턴을 처리하기에 적합하다.<blockquote>
<p>❓어떤 트래픽 패턴?</p>
</blockquote>
</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li>윈도 경계 부근에서 <strong>일시적으로 많은 트래픽이 몰려드는 경우, 기대했던 시스템의 처리 한도보다 많은 양의 요청을 처리</strong>하게 됨</li>
</ul>
</li>
</ul>
<h4 id="이동-윈도-로깅-알고리즘">이동 윈도 로깅 알고리즘</h4>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/image-4.png" alt="alt text"></p>
<ul>
<li>이동 윈도 로깅 알고리즘은 <strong>고정 윈도 카운터 알고리즘의 문제</strong>인, 윈도 경계 부근에 트래픽이 집중되는 경우 시스템에 설정된 한도보다 많은 요청을 처리하는 것을 <strong>해결한다.</strong></li>
<li>동작 원리<ul>
<li>요청의 타임스탬프를 추적한다. <strong>타임스탬프 데이터는 보통 레디스의 정렬 집합 같은 캐시에 보관</strong>한다.</li>
<li>새 요청이 오면 만료된 타임스탬프는 제거 (만료된 타임스탬프 == 현재 윈도 시작 시점보다 오래된 타임스탬프)<ul>
<li>ex. 1:01:40 에 새로운 요청이 오면 만료된 타임 스탬프 (== 1:01:40 요청의 현재 윈도 시작 시점인 1:00:40 이전 요청) 인 1:00:01, 1:00:30 제거</li>
</ul>
</li>
<li>새 요청의 타임스탬프를 로그에 추가<blockquote>
<p>그러면 1:01:31 에 요청이 들어온 순간 1:00:50 요청, 1:01:31 의 요청이 둘다 시스템에 전달됨 <br>
1:01:42 에 새 요청이 들어오면 1:00:42 ~ 1:01:42 사이의 로그가 3이니까 1:01:42 도 요청 거부됨
즉, 이동 윈도 로깅 알고리즘은 호출 횟수를 제한한다 (=요청량을 제한 / 처리율 제한 x)</p>
</blockquote>
</li>
<li>로그의 크기가 허용치보다 같거나 작으면 요청을 시스템에 전달. 그렇지 않은 경우에는 처리 거부</li>
</ul>
</li>
<li>장점<ul>
<li>어느 순간의 윈도를 보더라도, 허용되는 요청의 개수는 시스템 처리율 한도를 넘지 않음</li>
</ul>
</li>
<li>단점<ul>
<li><strong>거부된 요청의 타임스탬프도 보관하기 때문에 다량의 메모리를 사용</strong>함. <blockquote>
<p>❓개선 가능한 방법은 없을까? </p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4 id="이동-윈도-카운터-알고리즘">이동 윈도 카운터 알고리즘</h4>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/image-5.png" alt="alt text"></p>
<ul>
<li><strong>고정 윈도 카운터 알고리즘과 이동 윈도 로깅 알고리즘을 결합</strong>한 것이다.<blockquote>
<p>고정 윈도 카운터 알고리즘의 </p>
<ul>
<li>윈도 개념</li>
<li>카운터 사용</li>
<li>메모리 효율성 <br></li>
</ul>
<p>이동 윈도 로깅 알고리즘의</p>
<ul>
<li>윈도 경계에서 발생할 수 있는 트래픽 문제 해결</li>
<li>이전 윈도 데이터 활용</li>
</ul>
</blockquote>
</li>
<li>현재 윈도에 몇 개의 요청이 온 것인지 계산하는 방법<ul>
<li>현재 1분간의 요청 수 + 직전 1분간의 요청 수 x 이동 윈도와 직전 1분이 겹치는 비율</li>
<li>반올림 혹은 내림하여 쓸 수 있음</li>
</ul>
</li>
<li>장점<ul>
<li>이전 시간대의 평균 처리율에 따라 <strong>현재 윈도의 상태를 계산하므로 짧은 시간에 몰리는 트래픽에도 잘 대응</strong>한다.</li>
<li>메모리 효율이 좋다.<blockquote>
<ol>
<li>거부된 요청 타임스탬프를 보관하는 이동 윈도 로깅 알고리즘보다 좋다</li>
<li>카운터를 사용하는 알고리즘 자체가 좋다 (고정 윈도 카운터 알고리즘에도 동일한 장점이 적혀 있음)</li>
</ol>
</blockquote>
</li>
</ul>
</li>
<li>단점<ul>
<li>직전 시간대에 도착한 요청이 균등하게 분포되어 있다고 가정한 상태에서 추정치를 계산하기 때문에 다소 느슨하다.</li>
</ul>
</li>
</ul>
<h3 id="개략적인-아키텍처">개략적인 아키텍처</h3>
<ul>
<li>처리율 제한 알고리즘의 기본 아이디어<ul>
<li>얼마나 많은 요청이 접수되었는지 추적할 수 있는 카운터를, </li>
<li><strong>추적 대상</strong>별 (사용자별 / IP주소별 / API 엔드포인트나 서비스 단위) 로 두고, </li>
<li>카운터의 값이 <strong>어떤 한도를 넘어서면 한도를 넘어 도착한 요청은 거부</strong>하는 것이다</li>
</ul>
</li>
<li>카운터를 보관하는 곳은 메모리상에서 동작하는 <code>캐시</code>가 바람직하다<ul>
<li>빠른데다 시간에 기반한 <code>만료 정책</code>을 지원하기 때문이다</li>
</ul>
</li>
<li>동작 원리<ul>
<li>클라이언트 -&gt; 처리율 제한 미들웨어에게 요청 전송</li>
<li>처리율 제한 미들웨어는 레디스의 지정 버킷에서 카운터를 조회, 한도 도달 여부 검증</li>
<li>도달하지 않았다면 요청을 API 서버로 전달, 미들웨어는 카운터 값을 증가시킨 후 다시 레디스에 저장</li>
</ul>
</li>
</ul>
<h2 id="3단계-상세-설계">3단계. 상세 설계</h2>
<ul>
<li>처리율 제한 규칙은 어덯게 만들어지고 어디에 저장되는가?</li>
<li>처리가 제한된 요청들은 어떻게 처리되는가?</li>
</ul>
<h3 id="처리율-제한-규칙">처리율 제한 규칙</h3>
<ul>
<li>규칙들은 보통 <strong>설정 파일 형태로 디스크에 저장</strong>된다</li>
</ul>
<h3 id="처리율-한도-초과-트래픽의-처리">처리율 한도 초과 트래픽의 처리</h3>
<ul>
<li>요청이 한도 제한에 걸리면 API 는 HTTP 429 응답을 클라이언트에게 보낸다</li>
</ul>
<h4 id="처리율-제한-장치가-사용하는-http-헤더">처리율 제한 장치가 사용하는 HTTP 헤더</h4>
<ul>
<li>아래와 같은 HTTP 응답헤더를 Response 에 담아 클라이언트에 반환<pre><code class="language-http">X-RateLimit-Remaining: 59 // 윈도 내에 남은 처리 가능 요청 수
X-RateLimit-Limit: 60 // 매 윈도마다 클라이언트가 전송할 수 있는 요청 수
X-RateLimit-Retry-After: 100 // 한도 제한에 걸리지 않으려면 몇 초 뒤에 요청을 다시 보내야 하는 지 알림</code></pre>
</li>
</ul>
<h3 id="상세-설계">상세 설계</h3>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/image-6.png" alt="alt text"></p>
<ul>
<li>처리율 제한 규칙은 디스크에 보관하며, 작업 프로세스는 수시로 규칙을 디스크에서 읽어 캐시에 저장한다<ul>
<li>❓ 수시로라면 얼마나 잦은 주기로 읽는거지?</li>
</ul>
</li>
<li>클라이언트가 요청을 보내면 먼저 처리율 제한 미들웨어에 도달한다.</li>
<li>처리율 제한 미들웨어는 제한 규칙을 캐시에서 가져온다.</li>
</ul>
<h3 id="분산-환경에서의-처리율-제한-장치의-구현">분산 환경에서의 처리율 제한 장치의 구현</h3>
<h4 id="경쟁-조건">경쟁 조건</h4>
<ul>
<li>경쟁 조건 문제를 해결하는 가장 널리 알려진 해결책은 락(lock)이다. 하지만 락은 시스템의 성능을 상당히 떨어뜨린다는 문제가 있다.</li>
<li>위 설계에서 락 대신 쓸 수 있는 해결책이 2가지 있는데, <strong>루아 스크립트와 정렬 집합이라고 불리는 레디스 자료구조</strong>를 쓰는 것이다.<ul>
<li>❓ 루아스크립트, 정렬집합 찾아보기</li>
</ul>
</li>
</ul>
<h4 id="동기화-이슈">동기화 이슈</h4>
<ul>
<li>수백만 사용자를 지원하려면 처리율 제한 장치 서버 한대로는 충분하지 않을 수 있다.</li>
<li>웹계층은 무상태이므로 클라이언트는 요청을 각기 다른 제한장치로 보낼 수 있기 때문에 <strong>모든 처리율 제한 장치 서버의 동기화가 필요</strong>해진다.</li>
<li>1가지 해결책은 고정 세션을 활용하여 같은 클라이언트로부터의 요청은 항상 같은 처리율 제한 장치로 보낼 수도 있다<ul>
<li>하지만 규모면에서 확장 불가하며 유연하지 않다</li>
</ul>
</li>
<li>더 나은 해결책으로는 <strong>레디스와 같은 중앙 집중형 데이터 저장소</strong>를 쓰는 것이다</li>
</ul>
<h4 id="성능-최적화">성능 최적화</h4>
<ul>
<li>여러 데이터 센터를 지원하는 문제는 처리율 제한 장치에 매우 중요하다</li>
<li>데이터센터에서 멀리 떨어진 사용자를 지원하려다보면 지연시간이 증가할 수밖에 없기 때문이다<ul>
<li><strong>사용자의 트래픽을 가장 가까운 에지 서버로 전달</strong>하여 지연시간을 줄여야 한다</li>
</ul>
</li>
<li>제한 장치 간에 데이터를 동기화할 때 최종 일관성 모델을 사용해야 한다</li>
</ul>
<h4 id="모니터링">모니터링</h4>
<ul>
<li>채택한 알고리즘이 효과적인지, 정의했던 제한 규칙이 효과적인지를 기본적으로 확인해야한다.</li>
<li>제한 규칙이 너무 빡빡하면 많은 유효한 요청이 처리되지 못할 수도 있다는 점을 알아야 한다.</li>
<li>깜짝 세일 때문에 트래픽이 급증할 때 토큰 버킷 알고리즘으로 바꾸는 등 <strong>트래픽 패턴에 맞는 알고리즘 변경을 고려</strong>해야 한다.</li>
</ul>
<p><img src="https://raw.githubusercontent.com/lab-of-song/system-design-study/4c8a70d4f3e375f8380812b826321d6fb30150f4/%EC%A0%95%EB%A6%AC/Chapter%204/%EC%9D%80%EC%A3%BC/%EC%83%81%EC%84%B8%20%EC%84%A4%EA%B3%84.jpg" alt=""></p>
<h2 id="4단계-마무리">4단계. 마무리</h2>
<ul>
<li>경성 또는 연성 처리율 제한<ul>
<li>경성 처리율 제한: 요청 개수는 임계치를 절대 넘길 수 없다.</li>
<li>연성 처리율 제한: 요청 개수는 잠시 동안은 임계치를 넘어설 수 있다.<ul>
<li>❓ 실 사용 예시 + 기준을 어떻게 설정할까</li>
</ul>
</li>
</ul>
</li>
<li>다양한 계층에서의 처리율 제한<ul>
<li>이번장에서는 <code>애플리케이션 계층</code>에서의 처리율 제한에 대해서만 살펴보았지만 다른 계층에서도 처리율 제한이 가능하다.</li>
<li>iptables를 사용해 IP 주소 (3번 네트워크 계층) 를 기준으로 처리율을 제한 할 수도 있다.<ul>
<li>❓ 실제 예시 찾아보기</li>
</ul>
</li>
</ul>
</li>
<li>처리율 제한을 회피하는 방법. 클라이언트를 어떻게 설계하는 것이 최선인가?<ul>
<li>클라이언트 측 캐시를 사용해서 API 호출 횟수를 줄인다.</li>
<li>retry 로직을 구현할 때는 충분한 back-off 시간을 둔다.<ul>
<li>❓ back off 의 개념</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초 1] 3장. 시스템 설계 면접 공략법]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-3%EC%9E%A5.-%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/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-3%EC%9E%A5.-%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>Fri, 20 Dec 2024 13:10:15 GMT</pubDate>
            <description><![CDATA[<h1 id="3장-시스템-설계-면접-공략법">3장. 시스템 설계 면접 공략법</h1>
<ul>
<li>시스템 설계 면접에는 정해진 결말도 없고, 정답도 없다.</li>
<li>설계 기술을 시연하는 자리이고, 설계 과정에서 내린 결정들에 대한 방어 능력을 보이는 자리이다.</li>
<li><strong>면접관의 피드백을 건설적인 방식으로 처리할 자질이 있음</strong>을 보이는 자리이다.</li>
<li>시스템 설계 면접이 잘 진행되면, 지원자가 <code>협력에 적합한</code> 사람인지, <code>압박이 심한 상황도 잘 헤쳐나갈</code> 자질이 있는지, <code>모호한 문제를 건설적으로 해결할 능력이 있는지</code> 등을 살펴볼 수 있다</li>
<li>설계의 순수성에 집착한 나머지 타협적 결정을 도외시하고 과도한 엔지니어링을 하고 마는 엔지니어들이 현업에도 많다</li>
</ul>
<blockquote>
<p>✅ <strong>오버 엔지니어링의 위험을 피할 수 있는 방법</strong> <br>
<strong>1. 기술 의사 결정 과정 기록하기</strong></p>
<ul>
<li>다음과 같은 패턴을 보인다면, 기술 부채가 계속 쌓이면서 오버 엔지니어링이 일어날 가능성이 높다<ul>
<li>해결할 문제와 솔루션에 대한 충분한 검토없이 기술 도입 결정</li>
<li>잘못된 선택이나 변화에 대한 두려움 때문에 필요한 결정을 하지 않음</li>
<li>의사 결정 과정이 투명하지도 추적되지도 않아 중복된 논의가 계속됨<br></li>
</ul>
</li>
<li>이런 경우, <code>아키텍처 결정 기록 (Architectural Decision Records, ADR) 프로세스</code>를 사용할 수 있다. <ul>
<li>ADR : <strong>소프트웨어 아키텍처에 필요한 구성 요소에 대한 의사 결정, 동기 및 맥락 및 그리고 결과에 대해 설명하는 간단한 문서</strong></li>
<li>ADR 프로세스를 사용함으로써 동일한 기술 주제에 대한 반복적인 토론을 방지하고, 기존 직원 혹은 신규 입사자에게 기술 도입 과정을 효과적으로 전달할 수 있다</li>
</ul>
</li>
<li>“왜, RDB가 아니라 NoSQL을 선택했는지, 왜 구현 방법으로 Amazon DynamoDB를 선택했는지”, “왜 쿠버네티스를 선택했는지, 왜 운영 방법으로 Amazon EKS를 선택했는지”, “왜, API의 도메인 범위를 이렇게 나누었는지” 등등 각 의사 결정 사항을 ADR 문서로 저장한다</li>
<li>ADR 문서는 다양한 템플릿이 존재하는데, 보통 <strong>‘문제에 대한 정의’, ‘의사 결정에 대한 동기 및 맥락’, ‘최종 결정 사항’, ‘예상 결과’, ‘결정 참여자’</strong> 등의 항목을 짧더라도 구체적으로 담아야 한다.</li>
</ul>
<p><strong>2. 해결하려는 문제 정확히 파악하기</strong></p>
<ul>
<li>해결하려는 문제에 대해 정의를 내리고, <strong>도입하려는 기술에 대한 동기와 맥락을 정확히 알아야만</strong> 올바른 의사 결정을 할 수 있다</li>
<li>ex) 매일 발생하는 대량 로그 처럼 일회적인 쓰기 사용량이 높다고 해서 무턱대고 NoSQL을 선택하는 경우<ul>
<li>쓰기 이후에도 읽기나 분석 요구가 많은 경우라면, 간단한 파일 저장소나 Elasticsearch 같은 다른 솔루션을 고려할 필요도 있음. </li>
</ul>
</li>
</ul>
<p><strong>3. 문제 해결 기술 후보를 선정하고 테스트하기</strong></p>
<ul>
<li>몇 가지 후보를 선택하고 이를 테스트한 후, 나온 결과를 보고 선택해야 해야 한다<br>

</li>
</ul>
<p>추가적으로 가끔 오버엔지니어링 처럼 보이는 것이 필요할 때도 있다. 예를 들어, 시스템 보안이나 개인 정보 보호 같은 영역은 법적인 규정 준수 문제들이 있기 때문에 현재 가지고 있는 것 보다 더 많은 고려가 필요하다.</p>
</blockquote>
<blockquote>
<p>✅ <strong>오버 엔지니어링에 대한 현직자의 생각</strong> <br>
확장이나 축소 가능한 상태를 유지하는게 핵심<br>
ex) 현 요구사항보다 +a 를 구현했더라도 축소 가능한 상태로 설계가 되어 있다면 괜찮음.<br> (from. <a href="https://youtu.be/yDOT2-6KPZc?si=nOAjBWaujwo0FJZ2">https://youtu.be/yDOT2-6KPZc?si=nOAjBWaujwo0FJZ2</a>)</p>
</blockquote>
<h2 id="효과적-면접을-위한-4단계-접근법">효과적 면접을 위한 4단계 접근법</h2>
<h3 id="1단계--문제-이해-및-설계-범위-확정">1단계 : 문제 이해 및 설계 범위 확정</h3>
<ul>
<li><strong>요구사항을 완전히 이해하지 않고 답을 내놓는 행위는 아주 엄청난 부정적 신호다</strong></li>
<li>답부터 들이밀지 말라. 속도를 늦춰라. 깊이 생각하고 질문하여 요구사항과 가정들을 분명히 하라</li>
<li>엔지니어가 가져야 할 가장 중요한 기술<ul>
<li>올바른 질문을 하는 것  </li>
<li>적절한 가정을 하는 것</li>
<li>시스템 구축에 필요한 정보를 모으는 것</li>
</ul>
</li>
<li><strong>요구사항을 정확히 이해하는 데 필요한 질문을 하라</strong><ul>
<li>구체적으로 어떤 기능인가?</li>
<li>제품 사용자 수는?</li>
<li>회사의 규모는 얼마나 되리라 예상하는가?</li>
<li>회사가 주로 사용하는 기술스택은?</li>
</ul>
</li>
<li><strong>요구사항을 이해하고 모호함을 없애</strong>는 게 이 단계에서 가장 중요하다</li>
</ul>
<h3 id="2단계--개략적인-설계안-제시-및-동의-구하기">2단계 : 개략적인 설계안 제시 및 동의 구하기</h3>
<ul>
<li>설계안에 대한 <strong>최초 청사진을 제시하고 의견을 구하라</strong></li>
<li><strong>핵심 컴포넌트를 포함하는 다이어그램</strong>을 그려라</li>
<li>최초 설계안이 <strong>시스템 규모에 관계된 제약 사항들을 만족하는지를 개략적으로 계산</strong>해보라</li>
</ul>
<h3 id="3단계--상세-설계">3단계 : 상세 설계</h3>
<ul>
<li>면접관과 해야할 일은 설계 대상 컴포넌트 사이의 우선순위를 정하는 것이다<ul>
<li>대부분의 경우 면접관은 <strong>특정 시스템 컴포넌트들의 세부사항을 깊이 있게 설명하는 것</strong>을 보길 원한다</li>
</ul>
</li>
<li>불필요한 세부사항에 시간을 쓰지 말라</li>
</ul>
<h3 id="4단계--마무리">4단계 : 마무리</h3>
<ul>
<li><strong>만든 설계를 다시 한번 요약</strong>해주는 것도 도움이 될 수 있다</li>
<li>오류가 발생하면 무슨 일이 생기는지 (서버 오류, 네트워크 장애 등) 따져보자</li>
<li>메트릭은 어떻게 수집하고 <strong>모니터링</strong>은 어떻게 할 것인가? 로그는? <strong>시스템은 어떻게 배포</strong>해 나갈 것인가?<blockquote>
<p>❓ 시스템의 배포 방식 종류</p>
</blockquote>
</li>
<li><strong>규모 확장 요구</strong>에 어떻게 대처할 것인가?</li>
</ul>
<h4 id="해야할-것">해야할 것</h4>
<ul>
<li><strong>질문을 통해 확인하라. 스스로 내린 가정이 옳다 믿고 진행하지 말라</strong></li>
<li>요구사항을 정확히 이해했는지 다시 확인하라</li>
<li>면접관과 소통하라</li>
<li>가능하다면 여러 해법을 함께 제시하라</li>
<li>가장 중요한 컴포넌트부터 진행하라</li>
<li>면접관의 아이디어를 이끌어내라</li>
<li>포기하지 말라</li>
</ul>
<h4 id="하지-말아야-할-것">하지 말아야 할 것</h4>
<ul>
<li>요구사항이나 가정들을 분명히 하지 않은 상태에서 설계를 제시하지 말라</li>
<li>개략적 설계를 마친 뒤에 세부사항으로 나아가라</li>
<li>소통을 주저하지 말라</li>
</ul>
<h3 id="시간-배분">시간 배분</h3>
<ul>
<li>1단계 - 문제 이해 및 설계 범위 확정 (3~10분)</li>
<li>2단계 - 개략적인 설계안 제시 및 동의 구하기 (10~15분)</li>
<li>3단계 - 상세 설계 (10~25분)</li>
<li>4단계 - 마무리 (3~5분)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초 1] 2장. 개략적인 규모 추정]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-2%EC%9E%A5.-%EA%B0%9C%EB%9E%B5%EC%A0%81%EC%9D%B8-%EA%B7%9C%EB%AA%A8-%EC%B6%94%EC%A0%95</link>
            <guid>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-2%EC%9E%A5.-%EA%B0%9C%EB%9E%B5%EC%A0%81%EC%9D%B8-%EA%B7%9C%EB%AA%A8-%EC%B6%94%EC%A0%95</guid>
            <pubDate>Fri, 20 Dec 2024 13:08:13 GMT</pubDate>
            <description><![CDATA[<h1 id="2장-개략적인-규모-추정">2장. 개략적인 규모 추정</h1>
<ul>
<li>개략적 규모 추정 : 추정치를 계산하는 행위로서, <strong>어떤 설계가 요구사항에 부합할 것인지 보기 위한 것</strong></li>
</ul>
<h2 id="모든-프로그래머가-알아야-하는-응답지연-값">모든 프로그래머가 알아야 하는 응답지연 값</h2>
<ul>
<li>L1 캐시 참조</li>
<li>분기 예측 오류<blockquote>
<p>✅ 분기 예측 오류란</p>
<ul>
<li>분기 예측 : <strong>다음 실행될 조건문이 어떤 곳으로 분기할 것인지를 확실히 알게 되기 전에 미리 추측</strong>하는 CPU 기술</li>
<li>파이프라인 병목<ul>
<li>cpu는 효율성을 위해 파이프라인(pipeline) 이라는 구조를 사용한다</li>
<li>이 파이프라인에 명령어(코드)를 한줄 한줄 집어넣어서 동작하는데, if문 (분기문) 을 만나면 다음에 오는 명령어는 2가지 경우의 수가 생긴다. </li>
<li>이 다음에 올 명령어를 if문이 계산되기 전 까지 알 수 없으므로 cpu는 if 문이 계산되는 동안 놀게 된다. (파이프라인이 속도가 느려지는 경우 : control hazard)</li>
</ul>
</li>
<li>파이프라인 병목을 해결하기 위한 방법 -&gt; <code>분기 예측</code></li>
<li>만약 파이프라인이 조건 값의 계산이 끝날 때까지 대기한다면, 조건 분기 명령이 전체 파이프라인을 통과할 때까지 다음 명령은 파이프라인에서 수행되지 못하고 대기하게 될 것이다.
<br>분기 예측기는 이런 낭비를 막기 위해 <strong>단순한 알고리즘에 따라 다음 명령을 미리 추론한 후 미리 실행시</strong>킨다.<ul>
<li>분기 예측기의 예측이 맞을 경우 -&gt; 파이프라인은 낭비 없이 계속 수행</li>
<li>분기 예측기의 예측이 틀릴 경우 -&gt; 미리 실행되던 명령이 파이프라인에서 모두 취소되고 올바른 명령이 다시 실행</li>
</ul>
</li>
<li>✅ 분기 예측 오류 : 분기 예측기가 <strong>올바른 예측에 실패하면 파이프라인의 개수 만큼의 클럭 주기가 낭비된다. 복잡한 파이프라인 구조를 가진 현대 아키텍처에서는, 분기 예측기가 실패하면 10-20 클럭 주기가 낭비된다.</strong></li>
<li>조건 분기문이 처음 파이프라인에 들어왔을 때 활용할 수 있는 정보는 많지 않다. <br>분기 예측기는 해당 조건 분기문이 분기한 이력이나 루프 구조, 함수 구조 등의 정보를 바탕으로 다음 분기를 예측한다.</li>
<li>reference;
<a href="https://m.blog.naver.com/ektjf731/223052793786?recommendTrackingCode=2">https://m.blog.naver.com/ektjf731/223052793786?recommendTrackingCode=2</a> <br> <a href="https://blog.skby.net/%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EB%B6%84%EA%B8%B0%EC%98%88%EC%B8%A1%EA%B3%BC-%EC%98%88%EC%B8%A1%EC%8B%A4%ED%96%89/">https://blog.skby.net/%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EB%B6%84%EA%B8%B0%EC%98%88%EC%B8%A1%EA%B3%BC-%EC%98%88%EC%B8%A1%EC%8B%A4%ED%96%89/</a></li>
</ul>
</blockquote>
</li>
<li>zippy 로 1KB 압축<blockquote>
<p>✅ Zippy 에 대하여</p>
<ul>
<li>zippy는 메모리 내 데이터를 직접 압축할 수 있는 기능을 제공하므로, 파일 시스템을 거치지 않고도 압축할 수 있음</li>
<li>zippy는 기본적으로 deflate 압축 알고리즘 사용 -&gt; 이는 ZIP 형식에서 가장 일반적으로 사용되는 압축 방식</li>
<li>deflate : 널리 사용되는 무손실 데이터 압축 알고리즘</li>
</ul>
</blockquote>
</li>
<li>메모리는 빠르지만, <strong>디스크는 아직 느리다</strong></li>
<li>디스크 탐색은 가능한 한 피하라</li>
<li>단순한 압축 알고리즘은 빠르다    </li>
<li>데이터를 인터넷으로 전송하기 전, <strong>가능한 압축하라</strong></li>
<li>데이터 센터는 보통 여러 지역에 분산되어 있으며, 센터간 데이터 주고받는 데 시간이 소요된다</li>
</ul>
<h2 id="가용성에-관계된-수치들">가용성에 관계된 수치들</h2>
<ul>
<li><code>고가용성</code> : 시스템이 오랜 시간동안 지속적으로 중단 없이 운영될 수 있는 능력</li>
<li>대부분 서비스는 99% ~ 100% 사이의 값을 갖는다</li>
<li>SLA (Service Level Agreement) : 서비스 사업자와 고객 사이에 맺어진 합의<ul>
<li>서비스 사업자가 제공하는 <code>서비스 가용시간</code>이 공식적으로 기술됨</li>
</ul>
</li>
</ul>
<h2 id="예제--트위터-qps-와-저장소-요구량-추정">예제 : 트위터 QPS 와 저장소 요구량 추정</h2>
<ul>
<li>Query Per Second란<ul>
<li>데이터베이스나 웹 서버 API 등에서 <strong>특정 작업이나 요청이 초당으로 얼마나 처리되는지를 나타내는 지표</strong></li>
</ul>
</li>
<li>QPS(Query Per Second) 추정치<ul>
<li>일간 능동 사용자 = 월간 능동 사용자 * 50% = 1.5억</li>
<li>QPS = 일간 능동 사용자 * 2트윗 / (24시간 * 3600초) = 약 3500</li>
<li>최대 QPS = 2 * QPS = 약 7000<blockquote>
<p>✅ <strong>최대 QPS (peek QPS)</strong></p>
<ul>
<li>최대 QPS : 시스템이 처리해야 할 것으로 예상되는 최고 쿼리 비율을 나타내며, 주로 사용량이 많거나 트래픽이 급증하는 시기에 발생</li>
<li>파레토 법칙은 많은 성과에서 결과의 약 80%가 20%의 원인에서 발생</li>
<li>ex) 예상 분포 특성을 기반으로 계산하여, 많은 시스템에서 하루 중 20%의 시간 동안 80%의 트래픽이 발생한다고 가정할 수 있음 (파레토 원칙을 변형) 이러한 패턴을 고려하면 평균 QPS의 2배 정도가 최대 QPS로 나타날 수 있음</li>
<li>왜 2를 곱했을까? <ul>
<li>2배라는 수치는 절대적인 규칙은 아니고, 실제 시스템의 특성, 과거 데이터, 예상되는 성장률 등을 고려하여 적절한 배수를 선택해야 함 (일부 시스템에서는 1.5배나 3배를 사용하기도 함)</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="팁">팁</h2>
<ul>
<li>근사치를 활용한 계산</li>
<li>가정을 적어두라</li>
<li>단위를 붙이라</li>
<li>QPS, 최대 QPS, 저장소 요구량, 캐시 요구량, 서버 수 등을 추정하는 문제가 많이 출제된다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초 1] 1장. 사용자 수에 따른 규모 확장성
]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-1%EC%9E%A5-%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/@eunz_juu/%EA%B0%80%EC%83%81-%EB%A9%B4%EC%A0%91-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EA%B8%B0%EC%B4%88-1-1%EC%9E%A5-%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>Sun, 24 Nov 2024 11:32:47 GMT</pubDate>
            <description><![CDATA[<h1 id="1장-사용자-수에-따른-규모-확장성">1장. 사용자 수에 따른 규모 확장성</h1>
<h2 id="단일-서버">단일 서버</h2>
<ul>
<li>웹 앱, 데이터베이스, 캐시 등이 전부 서버 한대에서 실행된다</li>
</ul>
<ol>
<li>사용자는 도메인 이름을 이용하여 웹사이트에 접속<ul>
<li>접속을 위해서는 도메인 이름을 도메인 이름 서비스 (DNS) 에 질의하여 IP 주소로 변환하는 과정 필요</li>
</ul>
</li>
<li>DNS 조회 결과로 웹 서버의 IP 주소 반환</li>
<li>해당 IP 주소로 HTTP 요청 전달</li>
<li>요청을 받은 웹 서버는 HTML 페이지나 JSON 형태의 응답 반환</li>
</ol>
<h2 id="데이터베이스">데이터베이스</h2>
<ul>
<li>사용자가 늘면 서버 하나로는 충분하지 않아 여러 서버를 두어야 한다<ul>
<li>웹/모바일 트래픽 처리 용도</li>
<li>데이터베이스용</li>
</ul>
</li>
</ul>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-1.png" alt="alt text"></p>
<h3 id="어떤-데이터베이스를-사용할-것인가">어떤 데이터베이스를 사용할 것인가?</h3>
<ul>
<li>비관계형 데이터베이스는 흔히 NoSQL로 불리며 Cassandra, Amazon DynamoDB 등이 있다</li>
<li>NoSQL은 키-값 저장소, 그래프 저장소, 칼럼 저장소, 문서 저장소 등으로 분류된다.<blockquote>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-2.png" alt="alt text">
reference ; <a href="https://ganganichamika.medium.com/deep-dive-into-nosql-database-types-80340598124">https://ganganichamika.medium.com/deep-dive-into-nosql-database-types-80340598124</a></p>
<ul>
<li>키-값 저장소 <ul>
<li>데이터가 키와 값의 쌍으로 저장<br></li>
<li>대표적인 데이터베이스: Redis, Riak, Amazon DynamoDB <br></li>
</ul>
</li>
<li>칼럼 저장소 <ul>
<li>데이터가 로우(키 값), 컬럼 패밀리, 컬럼 이름으로 구성 <br></li>
<li>2차원 키-값 저장소 <br></li>
<li>대표적인 데이터베이스: HBase, Cassandra, Hypertable</li>
</ul>
</li>
<li>문서 저장소 <br><ul>
<li>키-값 모델을 확장한 형태로, 데이터가 문서(보통 JSON이나 XML 형식) 형태로 저장 <br></li>
<li>대표적인 데이터베이스: MongoDB, CouchDB<br></li>
</ul>
</li>
</ul>
</blockquote>
</li>
<li>비관계형 데이터베이스는 일반적으로 조인 연산은 지원하지 않는다</li>
<li>비관계형 데이터베이스가 바람직한 선택인 경우<ul>
<li>아주 낮은 응답 지연시간이 요구될 때<blockquote>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-3.png" alt="alt text"></p>
<p>reference ; <a href="https://velog.io/@park2348190/%EB%85%BC%EB%AC%B8%EB%B2%88%EC%97%AD-SQL-vs-NoSQL-A-Performance-Comparison">https://velog.io/@park2348190/%EB%85%BC%EB%AC%B8%EB%B2%88%EC%97%AD-SQL-vs-NoSQL-A-Performance-Comparison</a> <br></p>
<ul>
<li>NoSQL 데이터베이스는 일반적으로 밀리초(ms) 단위의 매우 낮은 응답 지연시간을 제공할 수 있다 </li>
</ul>
</blockquote>
</li>
<li>다루는 데이터가 비정형이라서 관계형 데이터가 아닐 때<blockquote>
<p>비정형 데이터 : 미리 정의된 데이터 모델이나 스키마가 없는 데이터</p>
<ul>
<li>텍스트 파일 (워드 문서, PDF 등)</li>
<li>멀티미디어 파일 (이미지, 오디오, 비디오)</li>
<li>소셜 미디어 게시물</li>
<li>이메일 내용</li>
<li>센서 데이터</li>
<li>로그 파일 <br></li>
</ul>
<p>이러한 데이터는 고정된 스키마를 가진 관계형 데이터베이스에 저장하기 어려우므로, 유연한 스키마를 제공하는 NoSQL 데이터베이스를 활용하여 다양한 형태의 데이터를 쉽게 저장 및 관리 가능</p>
</blockquote>
</li>
<li>데이터를 직렬화하거나 역직렬화만 하면 될 때<blockquote>
<p>NoSQL 데이터베이스, 특히 문서 저장소는 JSON, YAML, XML 등의 형식으로 데이터를 직접 저장 가능 -&gt; 객체와 저장 형식 간의 변환 작업이 줄어듦</p>
</blockquote>
</li>
<li>아주 많은 양의 데이터를 저장할 필요가 있을 때<blockquote>
<p>일반적으로 테라바이트(TB) 또는 페타바이트(PB) 규모의 데이터를 다룰 때 NoSQL이 유리할 수 있다</p>
<ul>
<li>NoSQL이 대용량 데이터 처리에 유리한 이유<ul>
<li>수평적 확장성: 새로운 서버를 추가하여 쉽게 용량을 늘릴 수 있다 </li>
<li>분산 아키텍처: 데이터를 여러 노드에 분산 저장하여 대규모 병렬 처리 가능</li>
<li>스키마 유연성: 다양한 형태의 대용량 데이터를 효율적으로 저장 가능</li>
<li>높은 쓰기 성능: 많은 NoSQL 데이터베이스는 대량의 쓰기 작업에 최적화되어 있음</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="수직적-규모-확장-vs-수평적-규모-확장">수직적 규모 확장 vs 수평적 규모 확장</h2>
<ul>
<li><code>스케일 업</code> (수직적 규모 확장) : 서버에 고사양 자원 (더 좋은 CPU, 더 많은 RAM 등) 을 추가하는 것</li>
<li><code>스케일 아웃</code> (수평적 규모 확장) : 더 많은 서버를 추가하여 성능을 개선하는 것</li>
<li>서버로 유입되는 트래픽 양이 적을 때는 스케일 업이 좋은 선택이다.<blockquote>
<p>트래픽 양이 &quot;적다&quot;는 것에 대한 정확한 수치 기준은 없고 상황에 따라 상대적이며, 다음과 같은 요소들을 고려할 수 있다. </p>
<ol>
<li>현재 서버의 성능: CPU, 메모리, 디스크 I/O 등의 <strong>리소스 사용률이 낮은 수준</strong>(예: 50% 미만) 유지</li>
<li>응답 시간: 사용자에게 <strong>허용 가능한 응답 시간</strong> 내에 서비스를 제공 가능</li>
<li>동시 사용자 수: 일반적으로 수천 명 이하의 동시 사용자를 처리할 수 있다면 단일 서버로 충분할 수 있음</li>
<li>성장률: 트래픽이 급격히 증가하지 않고 <strong>안정적이거나 완만하게 증가</strong>한다면 스케일 업으로 대응 가능</li>
<li>비용 효율성: 스케일 업이 스케일 아웃보다 비용 효율적인 경우</li>
<li>애플리케이션의 특성: 일부 애플리케이션은 수직적 확장에 더 적합할 수 있다. <ul>
<li>단일 스레드 작업이 많은 경우 CPU 성능 향상이 효과적일 수 있음</li>
</ul>
</li>
</ol>
</blockquote>
</li>
<li>다만 스케일 업에는 한계가 있다. </li>
<li>자동복구나 다중화 방안이 제시되지 않아 장애 발생 시 취약하다</li>
<li>따라서 대규모 애플리케이션을 지원하는 데는 스케일 아웃이 더 적절하다</li>
<li>너무 많은 사용자가 접속하여 웹 서버가 한계 상황에 도달하게 되면, 응답 속도가 느려지거나 서버 접속이 불가능해질 수도 있다<ul>
<li>이 때, 부하 분산기 또는 로드밸런서를 도입한다</li>
</ul>
</li>
</ul>
<h3 id="로드밸런서">로드밸런서</h3>
<p><img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-4.png" alt="alt text"></p>
<ul>
<li>로드밸런서 : <strong>부하 분산 집합에 속한</strong> 웹 서버들에게 <strong>트래픽 부하를 고르게 분산</strong>해주는 역할을 한다</li>
<li>서버 접속 흐름<ul>
<li>사용자는 로드 밸런서의 <strong>공개 IP 주소</strong>로 접속 (웹 서버는 클라이언트 접속을 직접 처리하지 않음)</li>
<li>로드밸런서는 웹 서버와 통신하기 위해 <strong>사설 IP 주소</strong> 이용<ul>
<li>사설 IP 주소 : 같은 네트워크에 속한 서버 사이의 통신에서만 쓰일 수 있는 IP 주소로, 인터넷을 통해서는 접속 불가능</li>
</ul>
</li>
</ul>
</li>
<li>로드밸런서의 부하 분산 집합에 웹서버를 추가하면 장애 자동 복구하지 못하는 문제 해소되며, 웹 계층 가용성 (시스템이 정상 사용 가능한 정도) 이 향상된다<ul>
<li>서버 1 다운 시 모든 트래픽은 서버 2 로 전송</li>
<li>트래픽이 가파르게 증가할 경우, 웹 서버 계층에 더 많은 서버를 추가하기만 하면 로드밸런서가 자동적으로 트래픽을 분산하기 시작한다 <blockquote>
<p>✅ <strong>로드밸런서의 트래픽 분산 원리</strong></p>
<ul>
<li>각 서버의 상태(사용량, 처리 능력/용량 등)를 모니터링하고, 적절한 알고리즘을 사용하여 트래픽을 분산한다.</li>
<li>로드밸런싱 알고리즘은 크게 2가지 범주로 나뉜다<ul>
<li><code>정적 로드 밸런싱</code> : 고정된 규칙을 따르며 현 서버 상태와 무관<ul>
<li>라운드 로빈 방식 : 클라이언트 요청 순서대로 서버를 배정하는 방식</li>
<li>가중 라운드 로빈 방식 : 서버마다 가중치를 설정하고 가중치에 따라 서버를 배정하는 방식 </li>
<li>IP 해시 방식 : 클라이언트 IP 주소를 해싱이라고 하는 계산을 수행하여 숫자로 변환하여, 개별 서버에 매칭해 처리</li>
</ul>
</li>
<li><code>동적 로드 밸런싱</code> : 서버의 현재 상태 검사<ul>
<li>최소 연결 방식 : 연결이 가장 적은 서버를 확인하고 해당 서버로 트래픽 전송</li>
<li>최소 응답시간 방식 : 응답 속도가 빠른 서버부터 우선적으로 트래픽 로드 배분</li>
<li>리소스 기반 방식 : 현재 서버 부하를 분석하여 트래픽 전송. 에이전트라고 하는 특수 소프트웨어는 각 서버에서 실행되며 컴퓨팅 용량 및 메모리와 같은 <strong>서버 리소스의 사용량을 계산</strong>하고, 해당 서버에 트래픽을 배포하기 전에 에이전트에 충분한 여유 리소스가 있는지 확인함</li>
</ul>
</li>
</ul>
</li>
<li>가장 많이 사용되는 알고리즘은 ? <ul>
<li><strong>라운드 로빈 방식은 구현이 간단하고 공평한 분배가 가능하다는 장점이 있어 많은 로드밸런서에서 기본적으로 사용되는 알고리즘</strong></li>
<li>NGINX: NGINX는 웹 서버이자 로드밸런서로 널리 사용되며, 기본적으로 라운드 로빈 방식 사용</li>
<li>AWS Application Load Balancer (ALB): 이전에는 ALB가 라운드 로빈 알고리즘만을 사용하여 수신 요청을 백엔드 대상에 배포했음</li>
<li>HAProxy: 오픈 소스 로드밸런서</li>
<li>Apache HTTP Server mod_proxy_balancer: Apache의 로드밸런싱 모듈</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h3 id="데이터베이스-다중화">데이터베이스 다중화</h3>
<ul>
<li>서버 사이에 주(master)-부(slave) 관게를 설정하고, 데이터 원본은 주 서버에, 사본은 부 서버에 저장하는 방식이다</li>
<li><code>쓰기 연산</code>은 <strong>마스터</strong>에서만 지원한다<ul>
<li><strong>부 데이터베이스</strong>는 주 데이터베이스로부터 사본을 전달받으며, <code>읽기 연산</code> 만을 지원한다</li>
</ul>
</li>
<li>대부분 애플리케이션은 읽기 연산의 비중이 쓰기 연산보다 훨씬 높다<ul>
<li>통상 부 데이터베이스의 수가 주 데이터베이스의 수보다 많다</li>
</ul>
</li>
<li>데이터베이스를 다중화했을 때 얻을 수 있는 이득<ul>
<li>더 나은 성능<ul>
<li>읽기 연산은 부 데이터베이스 서버들로 분산되므로, <strong>병렬로 처리될 수 있는 질의 수가 늘어나서</strong> 성능이 향상됨</li>
</ul>
</li>
<li>안정성<ul>
<li>자연 재해 등의 이유로 데이터베이스 서버가 파괴되어도 데이터는 보존됨 -&gt; 지역적으로 떨어진 여러 장소에 다중화시켜 놓을 수 있기 때문</li>
</ul>
</li>
<li>가용성<ul>
<li>하나의 데이터베이스 서버에 장애가 발생해도, 다른 서버에 있는 데이터를 가져와 계속 서비스할 수 있음</li>
</ul>
</li>
</ul>
</li>
<li>부 서버가 1대 뿐인데 다운되었다면, 읽기 연산은 한시적으로 모두 주 데이터베이스로 전달된다. 또한 즉시 새로운 부 데이터베이스 서버가 장애 서버를 대체할 것이다<blockquote>
<p>✅ 즉시 새로운 부 데이터베이스가 추가되니까, 장애가 난 부 서버를 대체한다는 건가 ? 즉시 어떻게 데이터베이스가 추가되지 ?
실제로는 새로운 서버를 준비하고 데이터를 동기화하는 데 어느 정도 시간이 소요된다.
이 기간 동안 시스템은 degraded mode(성능 저하 모드)로 운영될 수 있다. <br>
고가용성 시스템에서는 이러한 상황에 대비해 <strong>미리 준비된 대기 서버(standby server)를 두어 빠르게 전환할 수 있도록 하는 경우도 있음</strong></p>
<p>스탠바이 서버 (Standby Server)</p>
<ul>
<li>주로 고가용성(HA) 시스템에서 사용되는 용어</li>
<li>주 서버(Primary/Master)의 장애 시 <strong>즉시 대체할 수 있도록 준비된 서버</strong></li>
</ul>
<p>슬레이브 서버 (Slave Server):</p>
<ul>
<li>마스터-슬레이브 복제 구조에서 사용되는 용어</li>
<li>마스터 서버의 데이터 변경사항을 복제하여 동기화하는 서버 <br></li>
</ul>
<p>공통점</p>
<ul>
<li>데이터 동기화: 모두 주 서버(마스터)의 데이터 변경사항을 지속적으로 동기화</li>
<li>읽기 전용 작업: 일반적으로 읽기 전용 쿼리를 처리 가능</li>
<li>장애 대비: 주 서버 장애 시 대체 역할을 할 수 있음</li>
</ul>
<p>차이점</p>
<ul>
<li>스탠바이 서버는 주로 고가용성을 위해 사용되며, <strong>즉시 주 서버로 전환될 수 있도록 준비된 상태를 유지</strong><ul>
<li>슬레이브 서버는 주로 읽기 부하 분산을 위해 사용되며, 항상 마스터 서버의 복제본 역할을 함</li>
<li>PostgreSQL에서는 이러한 서버를 &quot;standby server&quot;라고 부르며, <strong>데이터를 지속적으로 동기화하고 필요시 주 서버로 승격될 수 있음</strong></li>
<li>이 서버는 &quot;warm standby&quot; 또는 &quot;hot standby&quot; 모드로 운영될 수 있으며, 후자의 경우 읽기 전용 쿼리를 처리할 수 있음</li>
<li>기본적으로 주 서버의 데이터를 복제하고 필요시 대체 역할을 하는 서버를 지칭한다고 볼 수 있음</li>
</ul>
</li>
</ul>
</blockquote>
</li>
<li>주 데이터베이스 서버가 다운되면, 부 데이터베이스 서버가 새로운 주 서버가 될 것이다<ul>
<li>하지만 이 경우, 부 서버에 보관된 데이터가 최신상태가 아닐 수 있으므로 없는 데이터는 <code>복구 스크립트</code> 를 돌려서 추가하거나, <code>다중 마스터</code>, <code>원형 다중화</code> 방식을 도입하여 이런 상황에 대처할 수 있다<blockquote>
<p><strong>다중 마스터</strong></p>
<ul>
<li><strong>여러 데이터베이스 서버가 동시에 마스터 역할을 수행하는 구조</strong><ul>
<li>모든 노드가 읽기 및 쓰기 작업을 처리할 수 있음</li>
<li>높은 가용성과 확장성 제공</li>
<li>지리적으로 분산된 환경에서 유용</li>
<li>데이터 충돌 해결 메커니즘 필요</li>
</ul>
</li>
</ul>
<p><strong>원형 다중화 방식</strong></p>
<ul>
<li>데이터베이스 서버들이 원형 구조로 연결되어 데이터를 복제하는 방식<ul>
<li>각 노드는 다음 노드에 데이터를 복제</li>
<li>마지막 노드는 첫 번째 노드에 데이터를 복제하여 원형 구조 형성</li>
<li>단일 장애점(Single Point of Failure) 제거</li>
<li>네트워크 트래픽 분산</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="캐시">캐시</h2>
<ul>
<li>캐시 : 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고, 뒤 이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소</li>
<li>애플리케이션 성능은 <code>데이터베이스를 얼마나 자주 호출하느냐</code>에 크게 좌우되는데, 캐시는 그런 문제를 완화할 수 있다</li>
</ul>
<h3 id="캐시-계층">캐시 계층</h3>
<ul>
<li><p>캐시 계층 : 데이터가 잠시 보관되는 곳</p>
<ul>
<li>캐시 계층을 두면 성능이 개선될 뿐만 아니라, 데이터베이스 부하를 줄일 수 있고, 캐시 계층 규모를 독립적으로 확장시키는 것도 가능하다<blockquote>
<p>수평적 확장 (Scale-Out)</p>
<ul>
<li>가장 일반적인 방법으로, 캐시 서버의 수를 늘리는 것</li>
<li>여러 캐시 서버를 클러스터로 구성하여 부하를 분산시킴</li>
<li>일반적으로 consistent hashing (안정 해시) 알고리즘을 사용하여 데이터를 여러 캐시 서버에 분산 저장 <br></li>
</ul>
<p>수직적 확장 (Scale-Up)</p>
<ul>
<li>개별 캐시 서버의 리소스를 증가시키는 방법</li>
<li>메모리, CPU, 네트워크 대역폭 등을 증가시켜 단일 서버의 성능을 향상시킴</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p><code>캐시 우선 읽기 전략 (read-through)</code></p>
<ul>
<li>요청 받은 웹서버가 캐시에 응답이 저장된지 확인</li>
<li>캐시에 저장되어 있다면 해당 데이터를 클라이언트에 반환. </li>
<li>없는 경우에는 데이터베이스 질의를 통해 데이터를 찾아 캐시에 저장 후 클라이언트에 반환 </li>
</ul>
<blockquote>
<p><strong>계층화된 캐시 (Layered Caching)</strong>
<img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-7.png" alt="alt text">
CPU Register &gt; L1 Cache &gt; L2 Cache &gt; L3 Cache &gt; RAM &gt; SSD &gt; HDD 순의 접근 속도를 지니는데, <strong>이 중에 Redis가 사용하는 계층이 RAM이다.</strong></p>
<ul>
<li>Redis : 주로 메인 메모리 (RAM) 에 데이터를 저장하고 관리하는 인메모리 데이터 구조 저장소</li>
<li>L1 캐시: CPU 내부에 위치 / CPU 코어마다 별도로 존재하는 가장 빠른 속도로 접근할 수 있는 캐시 </li>
<li>L2 캐시: L2 캐시는 CPU 회로판에 별도의 칩으로 내장된다. L1 캐시를 먼저 뒤지고, L2 캐시를 뒤져 데이터를 찾는다.</li>
<li>L3 캐시 : L2 캐시로 충분히 커버할 수 있기 때문에 웬만한 프로세서에서는 L3 캐시 메모리를 달고있지 않다. L1/L2 캐시 메모리 정도만 CPU 성능에 직접적인 영향을 미치기에 L3 캐시는 크게 신경쓰지 않는것이 일반적인 추세다. L3 캐시는 CPU가 아닌 메인보드에 내장되는 경우가 더 많다.</li>
</ul>
<p>지역 분산 캐시 (Geographically Distributed Cache)</p>
<ul>
<li>전 세계적으로 분산된 사용자를 위해 여러 지역에 캐시 서버를 배치</li>
<li>CDN과 유사한 개념으로, 사용자와 가까운 위치의 캐시 서버를 사용<ul>
<li>Amazon ElastiCache for Redis를 사용하면 여러 AWS 리전에 걸쳐 글로벌 데이터 스토어를 구성 가능</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
<h3 id="캐시-사용-시-유의할-점">캐시 사용 시 유의할 점</h3>
<ul>
<li>캐시가 바람직한 상황<ul>
<li>데이터 갱신은 자주 일어나지 않지만 참조는 빈번하게 일어나는 경우</li>
</ul>
</li>
<li>캐시는 데이터를 휘발성 메모리에 두므로, 영속적으로 보관해야 하는 데이터는 캐시에 두면 안된다<ul>
<li>캐시 서버가 재시작되면 캐시 내의 모든 데이터는 사라진다</li>
</ul>
</li>
<li>캐시에 보관된 데이터의 만료 정책을 어떻게 가져갈 것인가?<ul>
<li>만료 기한이 너무 짧으면 데이터베이스를 너무 자주 읽게 되고, 너무 길면 원본과 차이날 가능성이 존재한다<blockquote>
<p>✅ <strong>레디스의 만료기한 도래 시 데이터 삭제 정책</strong>
레디스는 기본적으로 TTL 이 만료된 데이터를 아래 2가지 방법으로 삭제</p>
<ul>
<li>주기적으로(100ms) 만료시간이 설정된 일부 키를 임의로 선택하여 만료여부를 확인 후 삭제<ul>
<li>레디스에 저장된 모든 키를 확인할 경우 CPU 부하가 심해진다. 레디스는 이를 막기위해 일부 키만을 선택하여 확인한다.</li>
</ul>
</li>
<li>만료된 데이터에 접근시 삭제<ul>
<li>키를 가져올 때 만료시간이 설정되어 있을 경우 만료여부를 확인한다. 만료되었다면 이 시점에 키를 삭제하고 아무것도 반환하지 않는다.</li>
</ul>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li>저장소 원본 갱신 연산과 캐시 갱신 연산이 단일 트랜잭션으로 처리되지 않는 경우, 일관성 (데이터 저장소의 원본과 캐시 내의 사본이 같은지 여부) 이 깨질 수 있다</li>
<li>캐시 서버를 한 대만 두면 SPOF 가 될 수 있다<ul>
<li>따라서 여러 지역에 걸쳐 캐시 서버를 분산시켜야 한다</li>
</ul>
</li>
<li>캐시메모리가 너무 작으면 데이터가 너무 자주 캐시에서 밀려나 (eviction) 캐시의 성능이 떨어지게 된다<ul>
<li>캐시 메모리를 과할당하여 이 문제를 해결할 수 있다</li>
</ul>
</li>
<li>데이터 방출 정책 : 캐시가 꽉 차면 추가로 캐시에 데이터를 넣어야할 경우 기존 데이터를 내보내야 하는 것<ul>
<li>가장 널리 쓰이는 정책 : LRU (Least Recently Used - 마지막으로 사용된 시점이 가장 오래된 데이터를 내보내는 정책)<blockquote>
<p>레디스의 데이터 방출 정책</p>
<ul>
<li>noeviction: 메모리가 고갈된 경우 새로운 쓰기 작업을 하려고 할 때 에러를 반환한다.</li>
<li>allkeys-lru: 가장 최근에 사용되지 않은 키를 삭제한다.</li>
<li>allkeys-random: 키 공간에서 임의의 키를 삭제한다.</li>
<li>allkeys-lfu: 사용빈도가 적은 키를 삭제한다.</li>
<li><strong>volatile-lru: 만료기간이 설정된 키 중에서 가장 최근에 사용되지 않은 키를 삭제한다.</strong></li>
<li>volatile-random: 만료기간이 설정된 키 중에서 임의의 키를 삭제한다.</li>
<li>volatile-lfu: 만료기간이 설정된 키 중에서 사용빈도가 적은 키를 삭제한다.</li>
<li>volatile-ttl: 만료기간이 설정된 키 중에서 ttl 이 가장 짧은것을 삭제한다.</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="콘텐츠-전송-네트워크-cdn">콘텐츠 전송 네트워크 (CDN)</h2>
<ul>
<li>CDN : <code>정적 콘텐츠</code>를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크<ul>
<li>이미지, 비디오, CSS, javascript 파일 등을 캐시할 수 있음</li>
<li>동적 콘텐츠 캐싱 : 요청 경로, query string, cookie, request header 등의 정보에 기반하여 HTML 페이지를 캐시하는 것</li>
</ul>
</li>
<li>CDN 동작 원리<ul>
<li>사용자가 웹사이트 방문하면, 그 사용자에게 가장 가까운 CDN 서버가 정적 콘텐츠를 전달한다</li>
</ul>
<ol>
<li>사용자 A 가 이미지 URL 을 이용해 image.png 에 접근한다</li>
<li>CDN 서버의 캐시에 해당 이미지가 없는 경우, 서버는 원본 서버에 요청하여 파일을 가져온다
(원본 서버는 웹서버일 수도, 아마존 S3 같은 온라인 저장소일 수도...)</li>
<li>원본 서버가 파일을 CDN 서버에 반환한다<ul>
<li><code>응답 HTTP 헤더에는 해당 파일이 얼마나 오래 캐시될 수 있는지</code> 설명하는 TTL (Time-To-Live) 값이 들어있다</li>
</ul>
</li>
<li>CDN 서버는 파일을 캐시하고 사용자에게 반환하며, 이미지는 TTL에 명시된 시간이 끝날 때까지 캐시됨</li>
<li>또다른 사용자가 같은 이미지에 대한 요청을 CDN 에 전송한다</li>
<li>만료되지 않았다면 캐시를 통해 처리된다</li>
</ol>
</li>
</ul>
<h3 id="cdn-사용-시-고려해야-할-사항">CDN 사용 시 고려해야 할 사항</h3>
<ul>
<li>비용</li>
<li>적절한 만료 시한 설정</li>
<li>CDN 장애에 대한 대처 방안 : CDN 자체가 죽었을 때, 해당 문제를 감지하여 원본 서버로부터 직접 콘텐츠를 가져오도록 구성하는 것이 필요할 수도 있음</li>
<li>콘텐츠 무효화 방법 : 아직 만료되지 않은 콘텐츠라 해도 CDN 에서 제거 가능</li>
</ul>
<h2 id="무상태-stateless-웹-계층">무상태 (stateless) 웹 계층</h2>
<ul>
<li><code>상태 정보</code> (사용자 세션 데이터와 같은) 를 웹 계층에서 제거해야 수평적으로 확장할 수 있다</li>
<li><code>무상태 웹 계층</code> : 상태정보를 관계형 데이터베이스나 NoSQL 같은 지속성 저장소에 보관하고, 필요할 때 가져오도록 하는 것</li>
</ul>
<h3 id="상태-정보-의존적인-아키텍처">상태 정보 의존적인 아키텍처</h3>
<ul>
<li>상태를 보관하는 서버는 클라이언트 정보, 즉 상태를 유지하여 요청들 사이에 공유되도록 한다</li>
<li>문제는 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송되어야 한다<ul>
<li>대부분의 로드밸런서가 이를 지원하기 위해 <code>고정 세션</code> (sticky-session) 기능을 제공하고 있음</li>
<li>하지만 이는 로드밸런서에 부담을 주며, 뒷단에 서버를 추가하거나 제거하기에 까다로우며 장애 처리도 복잡해짐</li>
</ul>
</li>
</ul>
<h3 id="무상태-아키텍처">무상태 아키텍처</h3>
<ul>
<li><strong>웹 서버는 상태 정보가 필요할 경우, 공유 저장소로부터 데이터를 가져온다</strong><ul>
<li>따라서 상태정보는 웹서버로부터 물리적으로 분리되어 있다</li>
</ul>
</li>
<li>공유 저장소는 관계형 데이터베이스, Memcached/Redis 같은 캐시 시스템, NoSQL 모두 가능하다<ul>
<li>NoSQL 은 규모 확장 (트래픽 양에 따라 웹 서버를 자동으로 추가하거나 삭제하는 기능) 이 간편하다</li>
</ul>
</li>
<li><strong>상태 정보가 웹 서버로부터 제거되었으므로, 트래픽 양에 따라 웹 서버를 넣거나 빼기만 하면 자동으로 규모를 확장할 수 있게 된다</strong></li>
</ul>
<h2 id="데이터-센터">데이터 센터</h2>
<ul>
<li><code>지리적 라우팅</code> (geo-routing, geoDNS-routing) 장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내된다<ul>
<li>사용자의 위치에 따라 도메인 이름을 어떤 IP 주소로 변환할지 결정할 수 있도록 해주는 DNS 서비스</li>
</ul>
</li>
<li>데이터 센터 중 하나에 심각한 장애가 발생하면 모든 트래픽은 장애가 없는 데이터 센터로 전송된다</li>
<li>다중 데이터센터 아키텍처를 만들 때 해결해야 하는 기술적 난제<ul>
<li>트래픽 우회 : 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법 찾기</li>
<li>데이터 동기화<ul>
<li>데이터 센터마다 별도 데이터베이스를 쓴다면 장애가 복구되어 트래픽이 다른 DB로 우회한다 해도, 해당 데이터센터에는 찾는 데이터가 없을 수 있다</li>
<li>이런 상황을 막는 보편적 전략은 <strong>데이터를 여러 데이터 센터에 걸쳐 다중화하는 것</strong></li>
</ul>
</li>
<li>테스트와 배포 : 웹 사이트 또는 애플리케이션을 여러 위치에서 테스트해 보는 것이 중요</li>
</ul>
</li>
</ul>
<h2 id="메시지-큐">메시지 큐</h2>
<ul>
<li>메시지 큐 : 메시지의 무손실(즉, 메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때까지 안전히 보관된다는 특성) 을 보장하는, 비동기 통신을 지원하는 컴포넌트</li>
<li>메시지 큐를 이용하면 서비스 또는 서버 간 결합이 느슨해져서, 규모 확장성이 보장되어야 하는 안정적 애플리케이션을 구성하기 좋다.</li>
<li>생산자와 소비자 서비스의 규모는 각기 독립적으로 확장될 수 있다. <ul>
<li>큐의 크기가 커지면 더 많은 작업 프로세스를 추가해야 처리 시간을 줄일 수 있다</li>
</ul>
</li>
</ul>
<h2 id="로그-메트릭-그리고-자동화">로그, 메트릭 그리고 자동화</h2>
<ul>
<li>로그: 에러 로그는 서버 단위로 모니터링 할 수도 있지만, 로그를 단일 서비스로 모아주는 도구 (ex. ELK 스택 (Elasticsearch, Logstash, Kibana)) 를 활용하면 더 편리하게 검색하고 조회할 수 있다. </li>
<li>메트릭: 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수도 있고, 시스템의 현재 상태를 손쉽게 파악할 수도 있다.<ul>
<li>호스트 단위 메트릭: CPU, 메모리, 디스크 I/O에 관한 메트릭</li>
<li>종합(aggregated) 메트릭: 데이터베이스 계층의 성능, 캐시 계층의 성능</li>
<li>핵심 비즈니스 메트릭: 일별 능동 사용자(daily active user), 수익(rev-enue), 재방문(retention)</li>
</ul>
</li>
<li>자동화: 지속적 통합을 도와주는 도구를 활용</li>
</ul>
<h2 id="데이터베이스의-규모-확장">데이터베이스의 규모 확장</h2>
<h3 id="수직적-확장">수직적 확장</h3>
<ul>
<li>스케일 업이라고도 부르는 수직적 규모 확장법은 기존 서버에 더 많은, 또는 고성능의 자원(CPU, RAM, 디스크 등)을 증설하는 방법<ul>
<li>데이터베이스 서버 하드웨어에는 한계가 있으므로 CPU, RAM 등을 무한 증설할 수는 없다.</li>
<li>SPOF(Single Point of Failure) 로 인한 위험성이 크다.</li>
<li>비용이 많이 든다.</li>
</ul>
</li>
</ul>
<h3 id="수평적-확장">수평적 확장</h3>
<ul>
<li>데이터베이스의 수평적 확장은 샤딩(sharding) 이라고도 부르는데, 더 많은 서버를 추가함으로써 성능을 향상시킬 수 있도록 한다<blockquote>
<p>✅ 데이터베이스의 수평적 확장 방법 <br></p>
<ol>
<li>샤딩(Sharding): 대규모 데이터셋을 더 작은 조각(샤드)으로 나누어 여러 데이터베이스에 <code>분산 저장</code>하는 기법</li>
<li>레플리케이션(Replication): 동일한 데이터베이스 복사본을 여러 서버에 생성하는 방식 (데이터베이스 다중화)</li>
<li>페더레이션(Federation): 기능별로 데이터베이스를 분할하는 방식 <br>ex) 결제, 주문, 프로필 등 각 기능별로 별도의 데이터베이스를 사용하여 각 데이터베이스의 읽기/쓰기 트래픽을 줄이고 전반적인 성능을 향상시킴</li>
<li>파티셔닝(Partitioning): 큰 테이블을 더 작고 관리하기 쉬운 부분으로 나누는 기법</li>
</ol>
</blockquote>
</li>
<li>샤딩 : 대규모 <strong>데이터베이스를 샤드(shard)라고 부르는 작은 단위로 분할하는 기술</strong><ul>
<li><strong>모든 샤드는 같은 스키마를 쓰지만, 샤드에 보관되는 데이터 사이에는 중복이 없다.</strong><blockquote>
<p>✅  데이터베이스를 분할하는 2가지 방법 : 샤딩과 파티셔닝</p>
<ol>
<li>파티셔닝 <ul>
<li>매우 큰 테이블을 여러개의 테이블로 각 행들을 분할하는 작업</li>
<li>수평 파티셔닝 : 하나의 테이블의 각 행을 다른 테이블에 분산시키는 것 <br>(<strong>보통 수평 분할은 하나의 데이터베이스 안에서 이루어지는 경우를 지칭) &lt;-&gt; 샤딩 : 서로 다른 데이터베이스로 분산</strong>
<img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-6.png" alt="alt text"></li>
<li>수직 파티셔닝 : 테이블의 일부 열을 빼내는 형태로 분할.
   정규화도 수직 파티셔닝과 관련된 거라고 할 수 있지만, 수직 파티셔닝은 이미 정규화된 데이터를 분리하는 과정이라고 생각해야 함
<img src="https://github.com/lab-of-song/system-design-study/raw/50833ec55819c96ddc53b4a6cb178b50dec8e736/%EC%A0%95%EB%A6%AC/Chapter%201/%EC%9D%80%EC%A3%BC/image-6.png" alt="alt text"></li>
</ul>
</li>
</ol>
<p>단일 서버 내 파티셔닝</p>
<ul>
<li>일반적으로 말하는 파티셔닝은 하나의 데이터베이스 서버 내에서 데이터를 분할하는 기법</li>
<li>이 경우 서버 대수를 늘리지 않아도 됨. 주로 성능 최적화와 데이터 관리를 위해 사용됨</li>
</ul>
<p>다중 서버 파티셔닝 (분산 파티셔닝)</p>
<ul>
<li>테이블 A와 테이블 B를 서로 다른 서버에 분산하여 저장하는 방식</li>
<li>이 경우 서버 대수를 늘려야 함</li>
</ul>
<ol start="2">
<li>샤딩<ul>
<li>샤딩 : 동일한 스키마를 가지고 있는 <strong>여러대의 데이터베이스 서버들에 데이터를 작은 단위로 나누어 분산 저장</strong>하는 기법<ul>
<li>물리적으로 서로 다른 컴퓨터에 데이터를 저장하므로, 쿼리 성능 향상과 더불어 부하가 분산되는 효과까지 얻을 수 있다. 즉, 샤딩은 데이터베이스 차원의 수평 확장(scale-out)인 셈!</li>
</ul>
</li>
</ul>
</li>
</ol>
</blockquote>
</li>
</ul>
</li>
<li><strong>샤딩 전략을 구현할 때 고려해야 할 가장 중요한 것은 샤딩 키를 어떻게 정하느냐 하는 것</strong><ul>
<li>샤딩 키는 파티션 키라고도 부르는데, <code>데이터가 어떻게 분산될지 정하는 하나 이상의 칼럼</code>으로 구성된다. </li>
<li>샤딩 키를 정할 때는 <strong>데이터를 고르게 분할할 수 있도록 하는 게</strong> 가장 중요하다.</li>
</ul>
</li>
<li>샤딩을 도입하면 풀어야 할 새로운 문제<ul>
<li>데이터의 재 샤딩<ul>
<li>데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때</li>
<li>샤드 간 데이터 분포가 균등하지 못하여 어떤 샤드에 할당된 공간 소모가 다른 샤드에 비해 빨리 진행될 때<ul>
<li>샤드 소진(shard exhaustion) 이라고도 부르는 이런 현상이 발생하면 샤드 키를 계산하는 함수를 변경하고 데이터를 재 배치하여야 한다</li>
</ul>
</li>
</ul>
</li>
<li>유명 인사 문제: 핫스팟 키(hotspot key) 문제라고도 부르는데, 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제</li>
<li>조인과 비정규화 : 하나의 데이터베이스를 여러 샤드 서버로 쪼개고 나면, 여러 샤드에 걸친 데이터를 조인하기가 힘들어진다<ul>
<li>데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행되도록 하면 문제를 해결할 수 있다</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="백만-사용자-그리고-그-이상">백만 사용자, 그리고 그 이상</h3>
<ul>
<li>웹 계층은 무상태 계층으로</li>
<li>모든 계층에 다중화 도입</li>
<li>가능한 한 많은 데이터를 캐시할 것</li>
<li>여러 데이터 센터를 지원할 것</li>
<li>정적 콘텐츠는 CDN을 통해 서비스 할 것</li>
<li>데이터 계층은 샤딩을 통해 그 규모를 확장할 것</li>
<li>각 계층은 독립적 서비스로 분할할 것</li>
<li>시스템을 지속적으로 모니터링 하고, 자동화 도구들을 활용할 것</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java 인터페이스를 활용한 선언적 HTTP 서비스 : HttpInterface ]]></title>
            <link>https://velog.io/@eunz_juu/Java-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%96%B8%EC%A0%81-HTTP-%EC%84%9C%EB%B9%84%EC%8A%A4-HttpInterface</link>
            <guid>https://velog.io/@eunz_juu/Java-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%96%B8%EC%A0%81-HTTP-%EC%84%9C%EB%B9%84%EC%8A%A4-HttpInterface</guid>
            <pubDate>Sun, 10 Nov 2024 07:07:08 GMT</pubDate>
            <description><![CDATA[<p>Spring 6 버전과 Spring Boot 3 버전부터 스프링 프레임워크는 Java 인터페이스로 원격 HTTP 서비스를 프록시할 수 있는 기능을 지원하는데, 바로 <code>HttpInterface</code> 이다.</p>
<blockquote>
<p>스프링의 HttpInterface는 <strong>HTTP 요청을 위한 서비스를 자바 인터페이스와 애노테이션으로 정의할 수 있도록</strong> 해준다.
그리고 <strong>해당 서비스를 구현하는 프록시 객체를 생성</strong>하면 이를 통해 쉽게 HTTP 요청을 보낼 수 있다. </p>
</blockquote>
<p>즉, HttpInterface 를 사용하면 코드가 <u>HTTP 호출의 세부사항에 의존하지 않고도</u> <strong>HTTP 메서드와 URL만 알면 사용할 수 있도록</strong> <code>추상화</code>된다. 
코드를 통해 HttpInterface 사용 예시를 알아본 후 HttpInterface 의 장점에 대해 마지막으로 다시 정리를 해보겠다 !</p>
<p>이 글은 회원 (User) 에 대한 간단한 CRUD 코드를 기반으로 작성하였다.</p>
<h3 id="httpexchange-애노테이션을-활용한-인터페이스-코드-작성">@HttpExchange 애노테이션을 활용한 인터페이스 코드 작성</h3>
<p>우선 아래와 같이 인터페이스를 생성하여 @HttpExchange 어노테이션을 붙이고, HTTP 요청을 보내는 각 메소드마다 HTTP Method 에 맞는 애노테이션을 붙여준다. </p>
<pre><code class="language-java">@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE)
public interface UserApiHttpClient {

    // GET 요청: 특정 userId에 해당하는 User 정보를 가져옵니다.
    @GetExchange(&quot;/users/{userId}&quot;)
    ResponseEntity&lt;User&gt; getUser(@PathVariable long userId);

    // POST 요청: 새로운 User 데이터를 생성합니다.
    @PostExchange(&quot;/users&quot;)
    ResponseEntity&lt;User&gt; createUser(@RequestBody User newUser);

    // PATCH 요청: 특정 userId에 해당하는 User의 일부 데이터를 업데이트합니다.
    @PatchExchange(&quot;/users/{userId}&quot;)
    ResponseEntity&lt;User&gt; updateUserPartially(@PathVariable long userId, @RequestBody Map&lt;String, Object&gt; updates);

    // PUT 요청: 특정 userId에 해당하는 User 데이터를 전체 업데이트합니다.
    @PutExchange(&quot;/users/{userId}&quot;)
    ResponseEntity&lt;User&gt; updateUser(@PathVariable long userId, @RequestBody User updatedUser);

    // DELETE 요청: 특정 userId에 해당하는 User 데이터를 삭제합니다.
    @DeleteExchange(&quot;/users/{userId}&quot;)
    ResponseEntity&lt;Void&gt; deleteUser(@PathVariable long userId);
}</code></pre>
<blockquote>
<p>@HttpExchange : Http 엔드포인트를 지정하는 일반적인 애노테이션으로, <strong>인터페이스 수준에서 사용하면 모든 메소드에 적용된다</strong><br>
@GetExchange: HTTP GET 요청을 위한 @HttpExchange를 지정
@PostExchange: HTTP POST 요청을 위한 @HttpExchange를 지정
@PutExchange: HTTP PUT 요청을 위한 @HttpExchange를 지정
@DeleteExchange: HTTP DELETE 요청을 위한 @HttpExchange를 지정
@PatchExchange: HTTP PATCH 요청을 위한 @HttpExchange를 지정</p>
</blockquote>
<p>@HttpExchange 애노테이션은 인터페이스 수준에서 사용하면 모든 메소드에 적용된다. 
예를 들어, 현재 /users 라는 URI 가 모든 메소드에 공통으로 사용되는데 이를 @HttpExchange 의 속성으로 옮기면 모든 메소드에 동일하게 적용된다.
아래와 같이 공통된 URI (/users) 를 @HttpExchange 에 설정하여 코드를 간결하게 만들어줄 수 있다.</p>
<pre><code class="language-java">@HttpExchange(url = &quot;/users&quot;, accept = MediaType.APPLICATION_JSON_VALUE)
public interface UserApiHttpClient {

    @GetExchange(&quot;/{userId}&quot;)
    ResponseEntity&lt;User&gt; getUser(@PathVariable long userId);

    @PostExchange
    ResponseEntity&lt;User&gt; createUser(@RequestBody User newUser);

    @PatchExchange(&quot;/{userId}&quot;)
    ResponseEntity&lt;User&gt; updateUserPartially(@PathVariable long userId, @RequestBody Map&lt;String, Object&gt; updates);

    @PutExchange(&quot;/{userId}&quot;)
    ResponseEntity&lt;User&gt; updateUser(@PathVariable long userId, @RequestBody User updatedUser);

    @DeleteExchange(&quot;/{userId}&quot;)
    ResponseEntity&lt;Void&gt; deleteUser(@PathVariable long userId);

}
</code></pre>
<p>여기까지 작성해본 UserApiHttpClient 는 HTTP 요청을 수행하는 메소드의 형태와 내용은 정의되어있지만, 직접 HTTP 요청을 보내는 로직이 없다.
UserApiHttpClient 인터페이스에 정의된 메소드를 호출했을 때 실제 HTTP 요청이 되게 하려면, 해당 인터페이스에 대한 프록시 객체를 만들어주어야 한다.</p>
<h3 id="인터페이스에-대한-프록시-객체-생성을-위한-설정-추가">인터페이스에 대한 프록시 객체 생성을 위한 설정 추가</h3>
<p>프록시를 생성하기 위해서는 아래와 같은 WebClient 설정들이 추가로 필요한데, 하나씩 찬찬히 살펴보자 !</p>
<pre><code class="language-java">@Configuration
public class WebClientConfig {

    public UserApiHttpClient userApiHttpClient() {
        // #1
        WebClient webClient = WebClient
                .builder()
                .baseUrl(&quot;https://localhost:8081&quot;)
                .build();

        // #2
        WebClientAdapter adapter = WebClientAdapter.create(webClient);

        // #3
        HttpServiceProxyFactory factory = HttpServiceProxyFactory
                .builderFor(adapter)
                .build();

        // #4
        return factory.createClient(UserApiHttpClient.class);
   }
}    </code></pre>
<h4 id="1-webclient-객체-생성">1. WebClient 객체 생성</h4>
<pre><code class="language-java">WebClient webClient = WebClient
        .builder()
        .baseUrl(&quot;https://localhost:8081&quot;)
        .build();</code></pre>
<p>WebClient 객체를 생성하는 코드이며, WebClient는 Spring의 비동기 및 동기 HTTP 요청 클라이언트로 HTTP 요청을 보낼 수 있도록 설정된다.
위 예제코드에서는 baseUrl 을 지정해줌으로써 모든 HTTP 요청의 기본 URL이 이 값으로 시작되도록 구성하였다.
기본 URL 과 HttpInterface 에서 작성한 아래 API path 와 연결되어 &quot;<a href="https://localhost:8081/%7BuserId%7D&quot;">https://localhost:8081/{userId}&quot;</a> 로 HTTP 요청이 전송된다.</p>
<pre><code class="language-java">  @GetExchange(&quot;/{userId}&quot;)
    ResponseEntity&lt;User&gt; getUser(@PathVariable long userId);</code></pre>
<h4 id="2-webclientadapter-객체-생성">2. WebClientAdapter 객체 생성</h4>
<pre><code class="language-java">WebClientAdapter adapter = WebClientAdapter.create(webClient);</code></pre>
<p>WebClientAdapter는 WebClient 객체를 HttpServiceProxyFactory가 사용할 수 있는 형태로 래핑하는 역할을 한다. 
이를 통해 WebClient가 HTTP 요청을 수행할 때 HttpServiceProxyFactory에서 활용할 수 있는 어댑터 역할을 한다.</p>
<h4 id="3-httpserviceproxyfactory-생성">3. HttpServiceProxyFactory 생성</h4>
<pre><code class="language-java">HttpServiceProxyFactory factory = HttpServiceProxyFactory
        .builderFor(adapter)
        .build();</code></pre>
<p>builderFor() 메서드를 통해 앞에서 생성한 WebClientAdapter를 사용하여 HttpServiceProxyFactory 객체를 만든다.
다음 4번째 단계에서 이 팩토리의 메소드를 활용하면, <strong>인터페이스의 메서드 호출을 프록시 객체로 변환하여 실제 HTTP 요청을 처리할 수 있다.</strong></p>
<h4 id="4-userapihttpclient-인터페이스의-프록시-객체-생성">4. UserApiHttpClient 인터페이스의 프록시 객체 생성</h4>
<pre><code class="language-java">UserApiHttpClient userApiHttpClient = factory.createClient(UserApiHttpClient.class);
return userApiHttpClient;</code></pre>
<p>HttpServiceProxyFactory 의 createClient 메소드를 활용하여 <strong>UserApiHttpClient 인터페이스의 프록시 객체를 생성한다.</strong>
이 프록시 객체는 UserApiHttpClient에 정의된 <strong>메서드를 호출할 때 자동으로 WebClient를 사용하여 해당 HTTP 요청을 보내도록 처리</strong>한다.
userApiHttpClient를 통해 정의된 메서드 호출 시 실제 HTTP 요청이 수행된다.</p>
<p>위와 같은 코드가 작성되고 나면 아래와 같이 호출 했을 때 정상적으로 1번 유저에 대한 객체가 반환된다!</p>
<pre><code class="language-java">userApiHttpClient.getUser(1); 
</code></pre>
<p>지금까지 간단한 회원 CRUD API 명세가 작성된 인터페이스 코드를 살펴보고, 해당 인터페이스를 활용하여 실제 HTTP 요청을 보내기 위한 WebClient 설정 코드에 대해 알아보았다.</p>
<h3 id="프록시-객체-없이-http-요청을-직접-구현한다면">프록시 객체 없이 HTTP 요청을 직접 구현한다면?</h3>
<p>HttpServiceProxyFactory 의 createClient 메소드를 통해 만들 수 있는 프록시 객체 없이, 직접 HTTP 요청 로직을 작성하게 되면 HTTP 호출에 대한 반복적이고 복잡한 코드가 많아질 수 있다.</p>
<p>극단적인 예시로, 프록시 객체 없이 HTTP 요청을 직접 작성하게 되면 아래와 같이 길고 장황한 (...) 코드를 짜게 될 수도 있다.
각 요청마다 <strong>반복</strong>되는 코드들이 생기고, HTTP 요청의 세부 구현을 포함하게 되어서 <strong>비즈니스 로직과 HTTP 호출 코드가 섞여 응집도도 낮아진다.</strong>
또한 API의 기본 URL이 변경되면 모든 메서드의 URI를 수정해야 하므로 <strong>유지보수에 어려움을 겪을 수 있다.</strong></p>
<pre><code class="language-java">public class UserApiService {

    private final WebClient webClient;

    public UserApiService() {
        this.webClient = WebClient.builder()
                .baseUrl(&quot;https://localhost:8081&quot;)
                .build();
    }

    public User getUser(long userId) {
        return webClient.get()
                .uri(&quot;/users/{userId}&quot;, userId)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .block();
    }

    public User createUser(User newUser) {
        return webClient.post()
                .uri(&quot;/users&quot;)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(newUser)
                .retrieve()
                .bodyToMono(User.class)
                .block();
    }

    public User updateUserPartially(long userId, Map&lt;String, Object&gt; updates) {
        return webClient.patch()
                .uri(&quot;/users/{userId}&quot;, userId)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(updates)
                .retrieve()
                .bodyToMono(User.class)
                .block();
    }

    public User updateUser(long userId, User updatedUser) {
        return webClient.put()
                .uri(&quot;/users/{userId}&quot;, userId)
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(updatedUser)
                .retrieve()
                .bodyToMono(User.class)
                .block();
    }

    public void deleteUser(long userId) {
        webClient.delete()
                .uri(&quot;/users/{userId}&quot;, userId)
                .retrieve()
                .bodyToMono(Void.class)
                .block();
    }
}</code></pre>
<h3 id="프록시-객체-사용하여-http-통신-로직을-추상화한다면">프록시 객체 사용하여 HTTP 통신 로직을 추상화한다면?</h3>
<p>프록시 객체를 사용하면 각 메서드에 직접적인 HTTP 로직을 작성할 필요가 없어진다
인터페이스에 따라 메서드 호출만으로 자동으로 HTTP 요청이 이루어져 <strong>간결하고 유지보수하기 쉬운 코드</strong>를 작성할 수 있다.
또한 코드가 더 깔끔해지고, <strong>비즈니스 로직과 HTTP 통신 로직을 분리</strong>할 수 있다.
필요에 따라 다른 HTTP 클라이언트로 교체할 때도 쉽게 변경할 수도 있다.</p>
<blockquote>
<p>프록시 구현체는 이처럼 <strong>인터페이스만 정의해두고도 실제 HTTP 통신이 가능한 클라이언트를 사용할 수 있어</strong> 
<code>인터페이스 기반 프로그래밍</code>을 가능하게 하며, 
코드의 재사용성, 유지보수성, 테스트 용이성을 높여줄 수 있다</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[열거형 전용 Set : EnumSet 에 대해 알아보기]]></title>
            <link>https://velog.io/@eunz_juu/%EC%97%B4%EA%B1%B0%ED%98%95-%EC%A0%84%EC%9A%A9-Set-EnumSet%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/%EC%97%B4%EA%B1%B0%ED%98%95-%EC%A0%84%EC%9A%A9-Set-EnumSet%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 27 Oct 2024 02:59:29 GMT</pubDate>
            <description><![CDATA[<h3 id="enumset-에-대하여">EnumSet 에 대하여</h3>
<ul>
<li>EnumSet 은 열거형 과 함께 사용하기 위한 Set 인터페이스의 특수 구현 중 하나이다</li>
<li>EnumSet은 Set 인터페이스를 완벽히 구현하며, 타입 안전하고, 다른 Set 구현체와도 함께 사용할 수 있다</li>
<li>EnumSet을 사용하면 해당 Set이 특정 enum 타입의 값만을 다룬다는 점이 명확히 표현되어 가독성을 높일 수 있다</li>
<li>null 값 추가를 허용하지 않으며, 시도하면 NullPointerException 이 발생한다</li>
<li>thread safe 하지 않으므로 필요한 경우 외부에서 동기화가 필요하다</li>
<li>EnumSet의 내부는 <code>비트 벡터</code>로 구현되어 있으며 <strong>원소의 개수가 64개 이하라면 long 변수 하나로 표현할 수 있어서</strong> 비트필드에 비견되는 성능을 보여준다<blockquote>
<p>비트벡터 : 중복되지 않는 정수 집합을 비트로 나타내는 방식 (자리에 해당하는 수에 0, 1 사용하여 표현) <br>-&gt; 메모리 사용 크게 감소 가능</p>
</blockquote>
</li>
</ul>
<p>EnumSet 의 내부 noneOf 메소드를 살펴보면, 2가지 구현체로 구현되고 있다.</p>
<pre><code class="language-java">public static &lt;E extends Enum&lt;E&gt;&gt; EnumSet&lt;E&gt; noneOf(Class&lt;E&gt; elementType) {
        Enum&lt;?&gt;[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + &quot; not an enum&quot;);

        if (universe.length &lt;= 64)
            return new RegularEnumSet&lt;&gt;(elementType, universe);
        else
            return new JumboEnumSet&lt;&gt;(elementType, universe);
    }</code></pre>
<ul>
<li>RegularEnumSet<ul>
<li>RegularEnumSet은 <strong>단일 long</strong>을 사용하여 비트 벡터를 나타낸다</li>
<li>long 의 <strong>각 비트는 enum 의 값</strong>을 나타냄 -&gt; 열거형의 i번째 값은 i번째 비트에 저장되므로 값이 있는지 여부를 아는 것이 매우 용이하다</li>
<li>long 은 64비트 데이터 유형 이므로 이 구현에서는 최대 64개의 요소를 저장 가능하다</li>
</ul>
</li>
<li>JumboEnumSet<ul>
<li>JumboEnumSet은 <strong>long 배열</strong>을 비트 벡터로 사용한다</li>
<li>이를 통해 64개 이상의 요소를 저장 가능하다</li>
<li>이는 RegularEnumSet 과 거의 유사하게 작동하지만 값이 저장된 배열 인덱스를 찾기 위해 몇 가지 추가 계산을 수행한다</li>
<li>배열의 첫 번째 long 요소는 enum 의 첫 번째 값 64개를 저장, 두 번째 요소는 다음 64개 등을 저장한다</li>
</ul>
</li>
<li>당연히 RegularEnumSet 성능이 JumboEnumSet 보다 조금 더 좋다. 
데이터를 찾기 위해 별도의 배열을 검색할 필요 없이 모든 데이터를 객체 내부에 저장하기 때문이다.</li>
</ul>
<h3 id="enumset-의-이점">EnumSet 의 이점</h3>
<ul>
<li>EnumSet의 모든 메서드는 <strong>산술 비트 단위 연산을 사용하여 구현</strong>된다</li>
<li>이러한 계산은 매우 빠르므로 모든 기본 작업이 <code>상수 시간</code> 내에 수행된다</li>
<li>EnumSet을 HashSet 과 같은 다른 Set 구현 과 비교하면, <strong>값이 예측 가능한 순서로 저장되고 각 계산에 대해 1비트만 검사하면 되기 때문에 일반적으로 더 빠르다.</strong></li>
<li>HashSet 과 달리 <strong>올바른 버킷을 찾기 위해 해시코드 를 계산할 필요가 없다</strong> .</li>
<li>게다가 비트 벡터의 특성으로 인해 EnumSet은 매우 컴팩트하고 효율적이다. </li>
<li>따라서 <strong>메모리를 덜 사용</strong>하면서도 모든 이점을 누릴 수 있다.</li>
</ul>
<h3 id="결론">결론</h3>
<p>&quot;Enum을 집합으로 표현하고 싶다면 그냥 HashSet에 넣어도 똑같은 거 아닌가?&quot;</p>
<ul>
<li>물론 기능은 같지만 성능적으로 큰 차이가 있다</li>
<li>EnumSet의 모든 메서드는 <code>비트 연산</code>을 사용하고 있으며 64개 이하라면 하나의 long 비트만을 사용</li>
<li>각 계산에 대해 <strong>하나의 비트만 검사</strong>하는 EnumSet과 <strong>해시 코드를 계산</strong>해야 하는 HashSet을 비교한다면 당연히 EnumSet이 빠르다</li>
<li>즉, Enum 값을 집합으로 저장할 일이 있다면 가능한 EnumSet을 사용하는 것이 좋다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[글또 10기를 시작하며]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B8%80%EB%98%909%EA%B8%B0-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0-10%EA%B8%B0%EB%AA%A9%ED%91%9C-%EC%A0%81%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/%EA%B8%80%EB%98%909%EA%B8%B0-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0-10%EA%B8%B0%EB%AA%A9%ED%91%9C-%EC%A0%81%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 13 Oct 2024 13:16:50 GMT</pubDate>
            <description><![CDATA[<p>글또 9기에 이어 글또 10기에 참여하게 되었다
10기가 마지막인 만큼 9기에서 못해서 아쉬웠던 것들을 10기에서는 꼭꼭 얻어가기 위해 짧게나마 다짐글을 적어보려고 한다</p>
<h3 id="9기에서-다짐했던-것-😎">9기에서 다짐했던 것 😎</h3>
<ul>
<li>같은 직군 사람들과 커피챗 하기 (한달에 2번 이상)</li>
<li>벽또 참여하기</li>
<li>관악또 참여하기</li>
<li>단순한 지식 정리가 아닌, 나의 경험을 담은 트러블 슈팅 또는 기술 관련 글 작성하기</li>
<li>같은 조 분들의 글 출퇴근 시간에 읽기</li>
</ul>
<h3 id="하지만-이루지-못했던-것-😵">하지만 이루지 못했던 것 😵</h3>
<p>&#39;같은 조 분들의 글 출퇴근 시간에 읽기&#39; 빼고는 아무것도 이루지 못한 것 같다.
사실 이것도 초반 1~2개월까지는 흥미로운 글들을 골라서 읽어보기도 했는데, 이후에는 흐지부지되었다. 막상 코어 채널만 보더라도 흥미로운 글들이 많았는데 그런 양질의 글들을 눈 앞에서 놓친 느낌이라 아쉽게 느껴졌다.</p>
<p>커피챗 + 소모임 참여는 퇴근 후 시간을 투자해서 참여를 해야하는 활동들이지만, 프로젝트 일정이 바빠지면서 그런 시간을 내는 것이 버겁게 느껴졌다.
그래서 적극적으로 참여하지 못하게 되었던 것 같아 9기가 끝나고도 너무너무 아쉬웠다. 글또는 많은 개발자들이 모인 커뮤니티인 만큼 다양한 사람들을 만날 수 있다는 게 큰 장점이라고 생각했는데, 그런 장점을 내가 잘 받아먹지 못한 느낌 😰 사실 지금도 여유로운 프로젝트 일정은 아니지만 9기에서 아쉬움을 느낀 만큼 10기때는 최대한 시간을 내보면서 참여하고 싶다.</p>
<p>단순한 지식 정리 뿐 아니라 나의 경험을 담은 글을 쓰는 것 또한 시간적인 여유가 없는게 가장 컸다. 이 쯤 되면 못한 거에 대해 다 핑계를 대는 느낌이라 민망하지만 이번 글또에서는 작성할 글감을 정리해보면서 조금 체계적으로 글을 써본다면 이 목표도 조금은 이룰 수 있지 않을까 기대해본다.</p>
<h3 id="10기에서는-이루고-싶은-것-💪">10기에서는 이루고 싶은 것 💪</h3>
<p><strong>커피챗 제발하기</strong></p>
<p>최근에 친구들과 만나면서도 다른 회사 이야기를 듣고 커리어와 관련된 대화를 할 때가 가장 재미있고 즐겁다는 생각이 들었다. 
그래서 내가 잘 모르는 도메인, 회사 문화 등 새로운 분들의 이야기도 많이 들어보면 참 재미있을 것 같다는 생각이 들어서 커피챗을 적극적으로 활용해보고 싶다 (제발) 요즘 일은 왜 열심히 해야할까, 꼭 개발자로서 성장해야할까? 와 같은 한심한 고민들을 하고 있지만 커리어와 관련된 성장 욕심이 있으신 분들과 이야기를 하면서 동기부여도 받고 내 마인드셋도 정비를 하고 싶다. 
최근 흑백 요리사를 보면서 자신의 일에 열정이 가득하고, 요리를 정말 사랑한다고 이야기하는 셰프분들이 멋있다고 느꼈다. 나는 과연 나의 일에 열정적으로 임하고 있을까, 나도 저런 마인드를 언젠가는 가질 수 있게 될까에 대한 의구심이 들면서 생각이 많아졌다.
일에 과도하게 매몰되는 것은 좋은 것이 아니지만, 나는 결국 일을 재미있게 할 때 가장 행복하고 뿌듯한 것 같았다. 하지만 이런 마음과는 상반되게 퇴근 후에는 아무것도 안하고 싶고 그저 누워 있고 싶은 마음도 드는 것 같다. 
아무튼 이런 고민들을 다른 분들과도 나눠보면서 여러 자극과 동기부여를 받기도 하고 나 또한 나의 루틴과 일상들을 나눠보면서 서로 좋은 영향을 받을 수 있는 관계들이 생겼으면 한다!</p>
<p><strong>대나무숲 활용하여 고민을 나눠 보기</strong>
글또 대나무숲은 9기 때부터 참 재미있게 보던 채널이다. 내가 고민하고 있는 부분들을 똑같이 올려주시는 분들도 있어서 덕분에 나의 고민에 대한 해답도 얻고 다양한 인사이트를 얻는 경우들이 많았다. 지금까지는 올라오지 않은 나의 고민도 나눠 본다면 나와 같은 고민을 하던 누군가에게는 도움이 되지 않을까? 하는 생각이 들었다. 지금까지는 주로 친구들이나 부모님께 고민을 나누었는데, 다양한 사람들이 있는 대나무숲도 잘 활용하면 좋을 것 같아 이번 기수에는 활용해보려고 한다</p>
<p><strong>영어해또 / 감사회고해또 매일 인증 참여하기</strong>
9기에서는 사실 무언가를 인증하거나 작성하는 소모임에 하나도 참여하지 않았다 (귀차니즘으로 인해). 9기에서도 감사회고 소모임이 있다는 것은 알았지만 나혼자서도 메모장에 감사일기를 매일 적고 있었기에 굳이 다른 채널에 써야할까? 하는 생각이었다. 하지만 혼자 하는 것이 흐지부지되는 순간들이 항상 있었다. 그래서 글또 9기가 끝나고 카톡방에서 진행되는 감사회고 9.5기에 참여하게 되었다. 9.5기에 참여하면서 매일 꾸준히 감사회고를 쓸 수 있었고, 다른 분들의 회고를 읽으면서 나 또한 좋은 자극을 받고 행복해지는 순간들 있었다. 그러면서 혼자 감사회고를 작성하는 것보다 다같이 있는 공간에서 감사회고를 나누는 것이 동기부여도 되고 좋은 에너지를 얻을 수 있다는 확신이 들었다. 그래서 10기에는 감사회고해또 채널에 매일 올리고 있고 내가 기대한 것과 같이 좋은 시너지가 나고 있는 것 같다! </p>
<p>영어해또 또한 감사회고해또와 동일한 이유로 참여하게 되었다.
말해보카를 매일하는 루틴을 갖고는 있지만 다른 분들이 어떻게 영어공부를 하는지 궁금하기도 하고, 같이 으쌰으쌰하는 분위기가 좋아서 참여하게 되었다! 다른 분들의 인증을 보면서 나도 새로운 수단과 방법으로 영어와 가까워지는 계기가 될 수 있을 것 같다.</p>
<p><strong>글또 슬랙 매일 주기적으로 확인하며 인사이트 얻기</strong>
9기와 비교했을 때 더 자주 슬랙을 확인하고 있다. 슬랙 알림은 꺼두었지만 하루에도 몇 번씩 슬랙에 들어가고 있어서 많은 글들을 확인하고 있다! 특히 책읽어또 채널에 주목하고 있다. 인증에 참여하고 있지는 않지만, 다른 분들이 읽는 책 리스트를 보면서 내가 읽고 싶은 책을 담아가고 있다. 요즘 책 읽는 재미에 빠져 있어서 인증글들을 보면서 나도 다양한 책을 읽을 기회가 생길 거 같아 기대가 된다. 글을 제출하면서부터는 큐레이션 채널의 글들도 많이 읽어보려고 한다. 더 부지런하게 슬랙의 좋은 정보들을 얻어가고 싶다!</p>
<br>
이 활동을 하면 후회할까, 안하면 후회할까? 에 대해 질문해보았을 때 "안했을 때 후회할 거 같다" 가 크다면 나는 그 활동을 항상 해왔다. 글또도 그 활동 중 하나이기에 9기에 이어 10기에도 하게 되었다. 마지막인 만큼 후회없는 활동으로, 하길 잘했다고 생각하는 활동으로 남기고 싶다. 화이팅 !!!!!
]]></description>
        </item>
        <item>
            <title><![CDATA[[글또 X 유데미] Java 멀티스레딩, 병행성 및 성능 최적화 - 전문가 되기]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B8%80%EB%98%90-X-%EC%9C%A0%EB%8D%B0%EB%AF%B8-Java-%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%94%A9-%EB%B3%91%ED%96%89%EC%84%B1-%EB%B0%8F-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%A0%84%EB%AC%B8%EA%B0%80-%EB%90%98%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/%EA%B8%80%EB%98%90-X-%EC%9C%A0%EB%8D%B0%EB%AF%B8-Java-%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%94%A9-%EB%B3%91%ED%96%89%EC%84%B1-%EB%B0%8F-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%A0%84%EB%AC%B8%EA%B0%80-%EB%90%98%EA%B8%B0</guid>
            <pubDate>Sun, 14 Apr 2024 10:34:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/eunz_juu/post/117722cf-c5f5-4aa9-972b-4b02b5b42bc6/image.png" alt=""></p>
<blockquote>
<p>강의 링크
<a href="https://www.udemy.com/course/java-multi-threading/?couponCode=UPGRADE02223">【한글자막】 Java 멀티스레딩, 병행성 및 성능 최적화 - 전문가 되기
</a></p>
</blockquote>
<h3 id="강의를-선택한-이유">강의를 선택한 이유</h3>
<p>우선 이 강의를 수강하게 된 이유는, 멀티스레드 환경에서 어떻게 올바르게 코드를 작성할 수 있을 지에 대한 궁금함 때문이었다. 
그동안 개발은 계속 해왔지만 멀티스레드 환경이나 병행성, 성능에 대한 내용을 고려하지 못했던 것 같아서 해당 내용을 통해서 조금이나마 감을 잡을 수 있을 거 같았다.</p>
<h3 id="강의-내용">강의 내용</h3>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/1c426d8e-c9d7-4d92-97e2-c475b6163e0a/image.png" alt="">
강의는 총 5시간 24분의 분량으로, 개인적으로 그렇게 분량이 많다는 생각은 안들었지만 하나하나 이론과 실습을 따라하다 보니 1.5배~2배의 시간은 들었던 것 같다. 
그리고 영어 부족 이슈로 인해.. 영어 자막 없이 소리만 들어서는 쉽게 내용 파악이 불가능하여 자막에 의존하며 듣다보니 훨씬 오래 걸렸던 것 같다 ㅎㅎ;;</p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/047af264-f232-4618-a35f-59d3ba5ce329/image.png" alt=""></p>
<p>강의는 운영체제에서 배운 내용들에 대한 개념 설명부터 시작한다. 위 사진은 강의를 들으며 필기한 일부 내용이다 !</p>
<p>멀티쓰레드가 필요한 이유, 멀티쓰레드 활용 시 성능, 컨텍스트 스위칭, 스레드 스케줄링 등등... <strong>운영체제 수업에서 배웠던 내용들을 한번씩 복습</strong>하고 넘어가는 느낌이어서 좋았다 !</p>
<p>OS 기본 개념부터 시작해서, 지연시간을 어떻게 최소화할 것이며, 여러가지 락킹기법, 논블로킹/블로킹 등 심화 개념까지 다뤄주고 있어서 멀티스레드, 병행성과 관련하여 큰 사이클을 모두 경험해볼 수 있는 것도 이 강의의 장점이다.</p>
<p>보통 강의들이 이론이면 이론, 실습이면 실습에 조금씩 치우치는 경우들도 있었는데, 해당 강의는 
이론과 실습 중 어떤 것에도 치우침 없이 이론에 대한 언급 + 해당 이론을 실제 어떻게 구현하는지를 바로 보여주어서 이해하기가 편했던 것 같다.</p>
<p>각 챕터마다 퀴즈, 코딩 연습이 준비되어 있어서 실습할 수 있는 환경을 강의에서 제공해준다. 그래서 이론만 알고 구현은 어려워했던 나에게 좋은 수강 방식이었던 것 같다</p>
<p>이 강의를 통해서, 운영체제에 대한 개념에 대한 이해를 바탕으로, 병행성/동시성 이론을 자바 코드로 어떻게 구현하는 지 실제로 경험해볼 수 있었다. 배운 내용을 바탕으로 실무에서도 적용해볼 수 있는 기회가 있었으면 하는 생각이 들었다.</p>
<h3 id="추천-대상">추천 대상</h3>
<p>이론으로만 배운 멀티스레딩, 병행성 및 성능 최적화에 대한 내용을 실제 코드로 어떻게 활용될 수 있을지 궁금한 사람들에게 추천하고 싶다. </p>
<p>이해하기 쉽게 설명해주시긴 하지만, 어느정도의 OS 지식을 갖고 있는 사람들이 이해하기 편하고 더 많은 내용을 얻어갈 것 같다는 생각도 들었다.</p>
<hr>
<p>해당 포스트는 글또 9기를 통해 Udemy 강의 쿠폰을 제공받아 작성하였습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LSM 트리의 조회, 쓰기 요청 처리 알아보기]]></title>
            <link>https://velog.io/@eunz_juu/LSM-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A1%B0%ED%9A%8C-%EC%93%B0%EA%B8%B0-%EC%9A%94%EC%B2%AD-%EC%B2%98%EB%A6%AC-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/LSM-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A1%B0%ED%9A%8C-%EC%93%B0%EA%B8%B0-%EC%9A%94%EC%B2%AD-%EC%B2%98%EB%A6%AC-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 31 Mar 2024 12:50:05 GMT</pubDate>
            <description><![CDATA[<h3 id="lsm-트리--log-structured-merge-tree-"><strong>LSM 트리 ( Log Structured Merge Tree )</strong></h3>
<ul>
<li>LSM 트리는 2개 이상의 tree 로 구성될 수 있는데, 1개의 tree 를 제외하고 나머지 tree 는 디스크에 위치한다</li>
<li><code>메모리</code>에 위치한 자료구조를 C0 tree, <code>디스크</code>에 위치한 자료구조를 C1 tree라고 표현한다</li>
</ul>
<h3 id="lsm-트리에서-쓰기-요청-처리"><strong>LSM 트리에서 쓰기 요청 처리</strong></h3>
<ol>
<li>쓰기 작업이 요청된다</li>
<li>시스템 다운 시 복구를 위해 별도의 로그성 파일에 데이터를 기록한다 (sequential log)</li>
<li><strong>메모리의 C0 tree에 해당 쓰기 요청 반영</strong> / C0 tree는 메모리에서만 존재하기 때문에 디스크를 위한 최적화는 필요하지 않다</li>
<li>C0 tree가 임계치 이상의 크기에 도달하면 rolling merge를 실시하며
Rolling merge를 통해 C0 tree의 연속되는 <strong>메모리 영역을 디스크 상의 C1 tree와 병합한다</strong> → 병합을 통해 C0이 사용할 수 있는 메모리 공간을 확보한다</li>
</ol>
<ul>
<li><p>Rolling merge</p>
<ul>
<li><p>Rolling merge의 시작은 디스크상의 C1 tree에 위치한 leaf node 데이터를 multi-page block 단위로 메모리에 적재한다</p>
</li>
<li><p>메모리에 적재된 C1 tree의 leaf node 데이터를 disk page 크기 단위로 읽고 이를 C0 tree의 left node데이터와 병합하여 새로운 leaf node를 생성한다</p>
</li>
<li><p>생성된 leaf node는 C1의 parent node가 가리킴으로써 병합과정이 완료된다</p>
</li>
<li><p>메모리에서 병합된 데이터를 디스크로 플러쉬할 때 기존 데이터의 위치에 덮어쓰지 않고 새로운 위치에 저장한다</p>
<p>  <img src="https://velog.velcdn.com/images/eunz_juu/post/4008b099-8cac-4125-82f0-2632b933627f/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>log-structured (데이터를 순차적으로 로그에 기록) 형식으로 데이터를 저장하면, 항상 파일에 추가만 해야하니까 디스크 공간이 부족해진다</p>
</li>
<li><p>SS 테이블의 형식으로 디스크에 key-value 데이터를 저장하는 색인 방식</p>
<ul>
<li><p>log-structured 데이터 형식에 “모든 key-value 쌍을 <code>키를 기준으로 정렬</code> 한다” 라는 요구사항을 추가한 형식 : <code>SS 테이블</code></p>
</li>
<li><p>SS 테이블은 LSM 트리에서 <code>실제로 데이터를 저장하는 단위</code> 로, 디스크 상에 정렬된 세그먼트 파일이다</p>
</li>
<li><p>새로운 데이터가 쓰기 동작으로 인해 <strong>메모리에 먼저 쓰여지고 나중에 디스크에 저장될 때 SS 테이블이 형성된다</strong></p>
<p>  <img src="https://velog.velcdn.com/images/eunz_juu/post/94fda489-03d6-49bc-958b-a8200e98edfa/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<pre><code>- SS 테이블은 주기적인 병합 작업을 통해, 데이터를 특정 크기의 segment 로 나누고, 주기적으로 compaction (중복된 키를 버리고 각 키의 최신 값만 유지하는 것) 을 수행한다

    ![](https://velog.velcdn.com/images/eunz_juu/post/14487ea2-8431-46dc-ba48-6452dfa55dd7/image.png)



1. 쓰기가 들어오면 인메모리 균형 트리(balanced tree) 데이터 구조(ex. red-black tree)에 추가한다
    - 트리가 이미 키로 정렬된 키-값 쌍을 유지하므로 효율적인 수행이 가능하다
    - 이러한 인메모리 트리를 `멤테이블`(memtable)이라고 한다
        - 멤테이블의 크기가 임계값(보통 수 MB)보다 커지면 SS테이블 파일로 디스크에 기록한다
        - 메모리의 휘발성 특성 때문에 주기적으로 디스크에 기록되거나 SS 테이블로 이동된다
2. 새로운 SS 테이블 파일은 DB 에서 가장 최신 세그먼트가 되고, SS 테이블을 디스크에 기록하는 동안 쓰기는 새로운 멤테이블 인스턴스에 기록된다
3. 읽기 요청을 처리하려면 먼저 멤테이블에서 키를 찾고, 디스크상의 최신 세그먼트부터 찾는다. (인메모리 해시맵이나 bloom filter 를 사용하여 탐색 시간을 줄일 수 있다.)
4. 백그라운드에서 지속적으로 컴팩션 과정을 수행한다</code></pre><h3 id="lsm-트리에서-조회-요청-처리"><strong>LSM 트리에서 조회 요청 처리</strong></h3>
<ul>
<li>C0, C1, ... Cn 순서로 수행한다</li>
<li>C0 tree (메모리) 에서 원하는 데이터를 찾을 수 없다면 C1 tree를 찾고, 이후에도 데이터를 찾을 수 없다면 다음 tree를 확인한다<ul>
<li>C0를 제외한 나머지 tree는 디스크에 위치하기 때문에 다수의 tree를 탐색해야 한다면 B-Tree와 비교하여 조회 성능이 안 좋을 수 있다</li>
<li>Bloom filter : LSM 트리와 같은 DB 시스템에서 사용되는 확률적 자료구조<ul>
<li>확률적인 자료 구조 “존재할 것이라고 판단되면 실제로는 존재하지 않을 가능성이 있다” 는 오류 발생 가능성이 있다</li>
<li><code>불필요한 디스크 액세스 감소</code> : 특정 데이터가 어떤 레벨의 트리에 있을 지 확률적으로 판단하여 불필요한 디스크 액세스를 줄인다</li>
<li><code>데이터 조회 최적화</code> : 특정 데이터 존재 여부를 확률적으로 판단하므로, 실제 존재하지 않는 데이터 조회를 빠르게 거부 가능하다 (읽기 성능 향상)</li>
<li><code>메모리 효율성</code> : 비트 배열로 구성되어 메모리 사용량이 적다</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스트림을 주의해서 사용하기]]></title>
            <link>https://velog.io/@eunz_juu/%EC%8A%A4%ED%8A%B8%EB%A6%BC%EC%9D%84-%EC%A3%BC%EC%9D%98%ED%95%B4%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/%EC%8A%A4%ED%8A%B8%EB%A6%BC%EC%9D%84-%EC%A3%BC%EC%9D%98%ED%95%B4%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 17 Mar 2024 10:26:04 GMT</pubDate>
            <description><![CDATA[<p>이펙티브 자바 스터디를 진행하면서, 스트림 관련 아이템을 읽고 추가적으로 정리한 내용들을 작성해보았다.</p>
<h3 id="스트림이란">스트림이란</h3>
<ul>
<li><code>스트림</code> : 데이터 원소의 유한/무한 시퀀스</li>
<li><code>스트림 파이프라인</code> : 데이터 원소들로 수행하는 연산 단계를 표현하는 개념<ul>
<li>소스 스트림에서 시작해 종단연산으로 끝난다</li>
<li>그 사이에 하나 이상의 중간 연산이 있을 수 있다 (중간 연산 - 스트림을 어떠한 방식으로 변환)</li>
<li>중간연산은 한 스트림 → 다른 스트림으로 변환한다</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/abc54c7e-fd1b-4209-9301-146876a50195/image.png" alt=""></p>
<h3 id="스트림의-종단-연산과-중간-연산">스트림의 종단 연산과 중간 연산</h3>
<ul>
<li>종단 연산<ul>
<li><strong>forEach(Consumer&lt;? super T&gt; consumer)</strong> : Stream의 요소를 순회</li>
<li><strong>count()</strong> : 스트림 내의 요소 수 반환</li>
<li><strong>max(Comparator&lt;? super T&gt; comparator)</strong> : 스트림 내의 최대 값 반환</li>
<li><strong>min(Comparator&lt;? super T&gt; comparator)</strong> : 스트림 내의 최소 값 반환</li>
<li><strong>allMatch(Predicate&lt;? super T&gt; predicate)</strong> : 스트림 내에 모든 요소가 predicate 함수에 만족할 경우 true</li>
<li><strong>anyMatch(Predicate&lt;? super T&gt; predicate)</strong> : 스트림 내에 하나의 요소라도 predicate 함수에 만족할 경우 true</li>
<li><strong>noneMatch(Predicate&lt;? super T&gt; predicate)</strong> : 스트림 내에 모든 요소가 predicate 함수에 만족하지않는 경우 true</li>
<li><strong>sum()</strong> : 스트림 내의 요소의 합 (IntStream, LongStream, DoubleStream)</li>
<li><strong>average()</strong> : 스트림 내의 요소의 평균 (IntStream, LongStream, DoubleStream)</li>
</ul>
</li>
<li>중간 연산<ul>
<li>f<strong>ilter(Predicate&lt;? super T&gt; predicate)</strong> : predicate 함수에 맞는 요소만 사용하도록 필터</li>
<li><strong>map(Function&lt;? Super T, ? extends R&gt; function)</strong> : 요소 각각의 function 적용</li>
<li><strong>flatMap(Function&lt;? Super T, ? extends R&gt; function)</strong> : 스트림의 스트림을 하나의 스트림으로 변환</li>
<li><strong>distinct()</strong> : 중복 요소 제거</li>
<li><strong>sort()</strong> : 기본 정렬</li>
<li><strong>sort(Comparator&lt;? super T&gt; comparator)</strong> : comparator 함수를 이용하여 정렬</li>
<li><strong>skip(long n)</strong> : n개 만큼의 스트림 요소 건너뜀</li>
<li><strong>limit(long maxSize)</strong> : maxSize 갯수만큼만 출력</li>
</ul>
</li>
</ul>
<h3 id="스트림-파이프라인의-지연-연산과-최적화">스트림 파이프라인의 지연 연산과 최적화</h3>
<ul>
<li><code>지연 평가</code> : 결과값이 필요할 때까지 계산을 늦추는 기법<ul>
<li>대용량 데이터에서, 실제로 필요하지 않은 데이터들을 탐색하는 것을 방지해 속도를 높일 수 있다</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 </p>
</blockquote>
<ol>
<li>스트림 파이프라인 실행시, JVM 은 곧바로 스트림 연산 실행 X</li>
<li>최소한으로 필수적인 작업만 수행하고자 지연 연산을 위한 준비 작업을 수행 (스트림 파이프라인이 <strong>어떤 중간연산과 종단 연산으로 구성되어있는지</strong>에 대한 검사)<ol start="3">
<li>이를 바탕으로 JVM 은 사전에 최적화 방법을 찾아내 계획함</li>
<li>해당 계획에 따라 개별 요소에 대한 스트림 연산을 수행함</li>
</ol>
</li>
</ol>
<ul>
<li>종단 연산이 호출될 때 이루어지며, 종단 연산에 쓰이지 않는 데이터 원소는 계산에 쓰이지 않는다 <strong>(Short-Circuit 방식)</strong></li>
<li>limit(n) 연산이 내부적으로 자신에게 도달한 요소가 n 개가 되었을 때 스트림 내 다른 요소들에 대해 더 이상 순회하지 않고 탈출하도록 만들었기 때문에, 아래와 같은 출력 결과가 나온다</li>
</ul>
<pre><code class="language-java">void test() {
    List&lt;String&gt; list = List.of(&quot;abcde&quot;, &quot;asdfasdf&quot;, &quot;aa&quot;, &quot;zzzzzzzz&quot;, &quot;bbb&quot;);
        list.stream()
            .filter(x -&gt; x.length() &gt;= 5)
            .peek(x -&gt; System.out.println(&quot;intermediate : &quot; + x))
            .limit(2)
            .forEach(x -&gt; System.out.println(&quot;terminate : &quot; + x));

intermediate : abcde
terminate : abcde
intermediate : asdfasdf
terminate : asdfasdf</code></pre>
<ul>
<li>지연평가가 무한 스트림을 다룰 수 있게 해주는 열쇠<ul>
<li>크기가 정해져있지 않으므로 중복 제거가 불가
  → limit () 과 같은 short-circuit 연산을 통해 유한 스트림으로 변환함으로써 가능해진다<ul>
<li>중복을 제거하는 <code>distinct()</code> 나, 전체 데이터를 정렬하는 <code>sort()</code> 연산들을 <code>Stateful</code> 연산이라고 함
하지만 이는 <strong>지연 평가를 무효화</strong>시키고, 결과를 생성하기 전에 전체 데이터를 탐색하는 결과를 초래한다</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">void test() {
    List&lt;String&gt; list = List.of(&quot;abcde&quot;, &quot;asdfasdf&quot;, &quot;aa&quot;, &quot;zzzzzzzz&quot;, &quot;bbb&quot;);
        list.stream()
            .filter(x -&gt; x.length() &gt;= 5)
            .peek(x -&gt; System.out.println(&quot;intermediate : &quot; + x))
            .sorted()
            .limit(2)
            .forEach(x -&gt; System.out.println(&quot;terminate : &quot; + x));

intermediate : abcde
intermediate : asdfasdf
intermediate : zzzzzzzz
terminate : abcde
terminate : asdfasdf</code></pre>
<ul>
<li>종단 연산이 없는 스트림 파이프라인은 아무일도 하지 않는 명령어와 동일하다</li>
</ul>
<h3 id="char-값을-처리할-때는-스트림-값을-삼가는-편이-나은-이유는-">Char 값을 처리할 때는 스트림 값을 삼가는 편이 나은 이유는 ?</h3>
<ol>
<li><strong>인코딩 문제</strong>: 스트림은 기본적으로 바이트 기반이라, CHAR 값을 처리할 때 스트림을 사용하면 인코딩 문제가 발생할 수 있다<ul>
<li>특히, 문자 데이터를 바이트로 변환하고 다시 CHAR로 변환할 때</li>
</ul>
</li>
<li><strong>텍스트 데이터의 추상화</strong>: 자바에서는 <strong><code>Reader</code></strong> 및 <strong><code>Writer</code></strong> 클래스와 같은 텍스트 데이터를 다루기 위한 특수한 스트림 클래스가 제공되기에, 이 클래스를 사용하면 문자 데이터를 효과적으로 다룰 수 있고, 인코딩 및 디코딩 문제를 줄일 수 있다</li>
</ol>
<h3 id="스트림이-적합한-경우">스트림이 적합한 경우</h3>
<ul>
<li>원소들의 시퀀스를 일관되게 변환한다.</li>
<li>원소들의 시퀀스를 필터링한다.</li>
<li>원소들의 시퀀스를 하나의 연산을 사용해 결합한다. (더하기, 연결하기, 최소값 등..)</li>
<li>원소들의 시퀀스를 컬렉션에 모은다</li>
<li>원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.</li>
</ul>
<h3 id="스트림이-적합하지-않은-경우">스트림이 적합하지 않은 경우</h3>
<ul>
<li>데이터가 파이프라인의 여러 단계(stage)를 통과할 때 이 데이터의 각 단계에서의 값들에 동시에 접근하기 어려운 경우<ul>
<li>스트림 파이프라인은 한 값을 다른 값에 매핑하고 나면 원래의 값을 잃는 구조이기 때문이다</li>
</ul>
</li>
</ul>
<h3 id="결론">결론</h3>
<ul>
<li>스트림을 과도하게 사용하면 읽기 어렵고 유지보수가 힘든 코드가 만들어지므로, 모든 반복문을 스트림으로 바꾸기보단 둘 다 테스트해보고 더 나은 쪽을 선택하는 것이 좋다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[파일 시스템을 활용한 고성능 디자인 구현한 카프카]]></title>
            <link>https://velog.io/@eunz_juu/%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EB%94%94%EC%9E%90%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%9C-%EC%B9%B4%ED%94%84%EC%B9%B4</link>
            <guid>https://velog.io/@eunz_juu/%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EB%94%94%EC%9E%90%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%9C-%EC%B9%B4%ED%94%84%EC%B9%B4</guid>
            <pubDate>Sun, 18 Feb 2024 13:26:32 GMT</pubDate>
            <description><![CDATA[<p>카프카는 데이터를 저장할 때 메모리가 아니라 디스크를 활용함으로써, <strong>별도의 설정 없이도 데이터의 영속성을 보장합니다.</strong>
따라서 서버에 장애가 나도 메세지가 디스크에 저장되어 있으므로 유실될 걱정이 없습니다.
또한 데이터가 디스크에 저장될 때** 순차적으로 저장되기에 디스크 I/O 가 줄어들어 성능이 빠르다**는 장점이 있습니다.</p>
<p>사실 기존 메시징 시스템에서 파일 시스템은,** 메시지의 영속성을 위해서 성능 저하를 감수하면서도 어쩔 수 없이 사용해야하는 애물단지 같은 존재였습니다.
**</p>
<p>디스크는 <code>원하는 데이터가 위치한 블록을 찾기 위한 시간(seek time), 블록을 메모리에 카피하는 시간</code> 등의 오버헤드가 존재합니다. 데이터가 캐시나 메모리에 이미 존재하면 이 과정은 생략될 수 있지만, 그렇지 않을 경우 이러한 동작이 반복적으로 필요합니다. 따라서 디스크는 일반적으로 느리다고 인식됩니다.</p>
<p>*<em>하지만 카프카는 이런 편견을 깨고 파일 시스템을 메시지의 주 저장소로 사용하면서도, 기존의 메시징 시스템보다 뛰어난 성능을 보여주는데요. 어떻게 이러한 구현이 가능했을까요?
*</em></p>
<p>카프카는 3가지 방법을 활용하였습니다.</p>
<blockquote>
<p><strong>1. 디스크의 Sequential I/O (순차적인 입출력) 활용</strong>
<strong>2. 페이지 캐시 전략 활용</strong>
<strong>3. 제로 카피 기법 도입</strong></p>
</blockquote>
<h2 id="첫번째-디스크에-순차적인-io-작업을-수행합니다">첫번째, 디스크에 순차적인 I/O 작업을 수행합니다.</h2>
<p>디스크 I/O는 어떻게 사용하는지에 따라 느릴수도 있고 빠를 수도 있는데요. 아래 그림을 통해 디스크에 순차적 접근 속도는 디스크 랜덤 엑세스에 비해 150,000배 빠르고, <strong>메모리에 랜덤 액세스하는 것보다도 속도가 빠른</strong> 것을 확인하실 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/86bb4131-63fe-4aa0-beb1-627f12976fb0/image.png" alt=""></p>
<p>디스크의 읽기 속도는 실제 데이터를 읽는 속도가 아니라, 데이터가 위치한 블록을 찾기 위한 시간 (seek time) 에 의해 지연됩니다. <strong>카프카는 순차적 접근을 통해, seek time 을 최소화할 수 있습니다.</strong>
이는 카프카가 데이터를 로그(segments)로 저장하기 때문에 가능한 일입니다.
로그는 한 번 쓰여지면 변하지 않는 성질, 즉 immutable 한 특성을 지니고 있습니다. 특히 카프카의 데이터는 컨슈머가 읽어도 지워지지 않고 지속적으로 추가됩니다.
이로 인해 카프카의 데이터는 디스크에 조각(fragments) 으로 나뉘어 저장되지 않고, <strong>가능한 연속적인 블록에 저장됨으로써 seek time 을 최소화 할 수 있습니다.</strong></p>
<p>하지만 이 순차적 접근이 항상 보장되는 것은 아닌데요.
카프카의 데이터가 위치하는 파일 시스템을 다른 어플리케이션이 함께 사용하는 경우, 다른 어플리케이션으로 인해 디스크 단편화가 발생할 가능성은 존재합니다.
따라서 카프카의 *<em>순차적 접근을 보장하기 위해, 카프카의 데이터는 가급적 독립된 파일 시스템에 유지할 것을 권장합니다.
*</em></p>
<p>최신 OS들은 앞으로 필요할 것으로 예상되는 메시지를 미리 읽어들이는 <strong>read-ahead</strong>와 디스크에 실제 쓰는 작업을 비동기적으로 처리하는 <strong>write-behind</strong> 같은 기술을 통해, <strong>순차 입출력 작업이 더 빠르게 수행되도록 지원하고 있습니다.</strong>
또한 디스크 액세스 속도를 더 개선시키기 위해 읽기/쓰기 작업은 OS 페이지 캐시를 최대한 활용한다면 디스크 접근 횟수를 줄일 수 있습니다. OS 페이지 캐시는 2번째 내용에서 조금 더 자세히 다뤄집니다.</p>
<blockquote>
<p>따라서, 카프카는 순차적 I/O 의 혜택을 활용함으로써 메모리에 저장하는 MOM 시스템보다 빠른 성능을 제공할 수 있습니다.</p>
</blockquote>
<h2 id="두번째-페이지-캐시-전략을-활용하였습니다">두번째, 페이지 캐시 전략을 활용하였습니다</h2>
<p><strong>카프카는 메모리에 별도의 캐시를 구현하지 않고 OS의 페이지 캐시에 이를 모두 위임하였습니다.</strong> 따라서, 모든 디스크의 읽기/쓰기는 OS의 페이지 캐시를 거치게 되는데요.
어플리케이션에 의해 관리되는게 아니라 OS에 의해 관리되기 때문에, 사용자 영역과 커널 영역의 중복 저장 없이 2배 정도의 캐시를 저장할 수 있습니다.</p>
<p>만약 애플리케이션 내부에서 캐시를 사용한다면, 같은 내용을 운영체제의 페이지 캐시와 애플리케이션 내부 캐시에 중복 저장하게 되는 것입니다.
<img src="https://velog.velcdn.com/images/eunz_juu/post/4b1d997c-18b5-4e4c-a6c8-e6352cf0adc2/image.png" alt=""></p>
<p>위 그림을 보시면, <strong>카프카는 Producer가 전송한 메시지를 JVM 힙메모리에 저장하지 않고 페이지 캐시에 저장하고 있습니다.</strong>
JVM 힙메모리에 저장하지 않음으로써 메시지가 <code>JVM 객체로 변환되면서 크기가 커지는 것을 방지</code>할 수 있고, 힙 내의 데이터가 증가함에 따라 점점 느려지는 <code>GC 의 성능저하 또한 피할 수</code> 있습니다.</p>
<p>또한 카프카 프로세스가 직접 캐시를 관리하지 않고 OS에 위임하기 때문에 프로세스를 재시작 하더라도 OS의 페이지 캐시는 그대로 남아있습니다. 따라서 <strong>프로세스 재시작 후 캐시를 워밍업할 필요가 없다</strong>는 장점도 있습니다. 여기서 워밍업이란 시스템이나 어플리케이션 시작 시, 또는 특정 작업 수행 전에 캐시를 미리 채워두는 작업을 의미합니다.</p>
<p>요약하면, 카프카는 데이터를 힙 메모리 내에 객체로 저장하는 대신 <strong>운영체제가 훌륭하게 최적화하고 있는 페이지 캐시를 활용한 디스크 저장 방식을 도입하여 성능을 높일 수 있었습니다.</strong></p>
<h2 id="세번째-제로-카피-기법을-도입했습니다">세번째, 제로 카피 기법을 도입했습니다.</h2>
<p>zero-copy 는 디스크의 데이터를 네트워크로 전송할 때 일어나는 데이터 복사 작업을 최소화 한 데이터 전송 방식입니다.
<strong>카프카에서는 파일 시스템에 저장된 메시지를 네트워크를 통해 전송할 때 zero-copy 기법을 사용하여 데이터 전송 성능을 향상시켰습니다.</strong>
일반적으로 파일 시스템에 저장된 데이터를 네트워크로 전송할 때는 아래 그림과 같은 데이터 전달이 이루어집니다.
<img src="https://velog.velcdn.com/images/eunz_juu/post/cb56bda5-404e-4664-a9e4-e9cdde7e38bb/image.png" alt=""></p>
<p><strong>1. 디스크 → Read Buffer (DMA ; Direct Memory Access)</strong></p>
<ul>
<li>유저가 read() 시스템콜을 호출하면, DMA 엔진에 의해 디스크로부터 데이터를 읽어 커널 영역의 Read Buffer 에 저장합니다.</li>
</ul>
<p><strong>2. Read Buffer → Application Buffer (CPU)</strong></p>
<ul>
<li>커널 주소 공간에는 유저가 접근할 수 없으므로, Read Buffer 의 내용을 Application Buffer 로 복사합니다.</li>
</ul>
<p><strong>3. Application Buffer → Socket Buffer (CPU)</strong></p>
<ul>
<li>유저는 Application Buffer 에서 읽어들인 데이터를 Socket Buffer 로 전송하기 위해 send() 함수를 호출하고, send() 함수 호출 시 커널 영역에 있는 Socket Buffer로 데이터를 복사합니다.</li>
</ul>
<p><strong>4. Socket Buffer → NIC Buffer (DMA)</strong></p>
<ul>
<li>Socket Buffer에 있는 데이터를 NIC Buffer로 복사하고 네트워크를 통해 전송합니다.</li>
</ul>
<p>전통적인 데이터 복사 방식은 4번의 컨텍스트 스위칭과 4개의 메모리 복사본이 생기면서 불필요한 복사와 시스템콜이 발생하게 됩니다.
이처럼 비효율적인 동작을 개선하기 위해 소개된 기법이 zero copy 입니다.</p>
<p>리눅스 2.2 버전에서 처음 소개된 sendfile() 시스템 콜이 제로카피 동작을 구현했는데요.</p>
<pre><code class="language-c">#include&lt;sys/sendfile.h&gt;
ssize_t sendfile(int out_fd, int in_fd, off_t * offset &quot;, size_t&quot; &quot; count&quot; );</code></pre>
<p>자바에서는 nio 패키지의 transferTo(), transferFrom() 메소드로 구현되어 있으며, 이 메서드들 역시 sendfile() 시스템콜을 이용해 구현되어 있습니다.</p>
<pre><code class="language-java">public void transferTo(long position, long count, WritableByteChannel target);</code></pre>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/201dfc0c-93a0-4794-a54e-16a87cb4186a/image.png" alt=""></p>
<p>zero copy 를 사용하면, 커널 영역의 Read Buffer 에서 Socket Buffer로 직접 복사가 가능하여 효율적으로 데이터를 전송할 수 있습니다. read(), send() 2번의 시스템콜이 transferTo() 1번의 호출로 가능해졌습니다.</p>
<ol>
<li>유저가 <strong>transferTo() 메서드</strong>를 통해 파일 전송을 요청합니다.</li>
<li>read() 시스템콜과 동일하게, DMA 엔진이 디스크에서 파일을 읽어 커널 주소 공간에 위치한 Read Buffer에 데이터를 복사합니다.</li>
<li><strong>커널 모드에서 유저 모드로 컨텍스트 스위칭 없이, 바로 Socket Buffer로 데이터를 복사</strong>합니다.</li>
<li>Socket Buffer에 복사된 데이터를 DMA 엔진을 통해 NIC Buffer로 복사합니다.</li>
</ol>
<p>따라서 transferTo() 메서드 호출시 커널모드로 1번, 종료시 유저모드로 1번, 총 2번으로 컨텍스트 스위칭 횟수가 줄었고, 데이터의 복사본이 4군데에서 3군데로 줄어들었습니다.
*<em>컨텍스트 스위칭 횟수와 복사본의 개수가 줄어든만큼 CPU 자원의 낭비가 줄어들게 되어 성능이 향상되었습니다.
*</em></p>
<p>리눅스 커널 2.4 이후부터는 NIC 장비가 “Gather Operation”을 지원할 경우 복사본을 더 줄일 수 있게 되었습니다.
<img src="https://velog.velcdn.com/images/eunz_juu/post/f75aff64-8afb-4d04-83aa-61fb98bb46a4/image.png" alt=""></p>
<ol>
<li>사용자가 transferTo() 메서드를 호출합니다. DMA 엔진이 디스크에서 파일을 읽어 커널에 위치한 Read Buffer로 데이터를 복사합니다.</li>
<li>데이터를 <strong>Socket Buffer 로 복사하지 않는 대신 데이터가 저장된 위치와 데이터 사이즈에 대한 정보와 함께 디스크립터가 Socket Buffer 에 추가됩니다</strong>.</li>
<li>DMA 엔진은 이 정보를 이용해 Read Buffer에 있는 데이터를 NIC Buffer 에 바로 복사하고, 네트워크로 데이터를 전송합니다.</li>
</ol>
<p>이러한 최적화를 위해서는 Gather operation과 프로토콜의 checksum 기능이 추가적으로 필요합니다.</p>
<p>아래 그래프는 실제 리눅스 2.6 커널 버전에서 성능을 비교한 것인데요.
전통적인 방식의 파일 전송과 transfer() 메소드를 이용한 파일 전송 속도를 비교했을 때, <strong>수행시간이 50% 이상 줄어들었습니다.</strong>
<img src="https://velog.velcdn.com/images/eunz_juu/post/bab78f37-087b-432a-8f62-1a660ed2da82/image.png" alt=""></p>
<p>디스크에서 <strong>파일을 읽은 후 추가 작업 없이</strong> 바로 네트워크로 전송하는 파일 서버나 <strong>정적 파일을 전송</strong>하는 웹 서버의 경우 zero copy 기법을 사용하면 성능 개선 효과를 얻을 수 있습니다.
특히 네트워크 속도가 매우 빨라서 성능상 병목점이 CPU로 몰릴 수록 <strong>불필요한 데이터 복사를 제거하여 성능 개선을 도모</strong>할 수 있습니다.</p>
<p>요약하자면, 디스크에서 파일을 읽고 네트워크로 전달하는 과정에서 read(), send() 두번의 시스템 콜로 인한 컨텍스트 스위칭과 데이터 복사에 따른 성능 저하가 따랐습니다. zero copy의 transferTo() 메서드를 통한 하나의 시스템콜을 활용함으로써, <strong>컨텍스트 스위칭과 데이터 복사에 필요한 비용을 줄여 성능 개선을 이루었습니다</strong>.</p>
<p><strong>디스크의 순차적 I/O, 페이지 캐시, 제로 카피.</strong></p>
<p>이 3가지 방법을 사용하여 카프카는 파일 시스템을 메시지의 주 저장소로 사용하면서도 뛰어난 처리량을 구현해낼 수 있었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zero Copy 에 대하여]]></title>
            <link>https://velog.io/@eunz_juu/Zero-copy-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@eunz_juu/Zero-copy-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Sun, 21 Jan 2024 10:15:11 GMT</pubDate>
            <description><![CDATA[<p>카프카에 대해 공부하다가 제로카피(Zero-copy) 라는 키워드를 보게 되었다. 
카프카 서버의 성능 개선을 위해 사용한 기법이라고 하여 관련 내용을 자세히 작성해보려고 한다.</p>
<h2 id="zero-copy">zero-copy</h2>
<p>zero-copy 는 디스크의 데이터를 네트워크로 전송할 때 일어나는 데이터 복사 작업을 최소화 한 데이터 전송 방식이다.
*<em>카프카에서는 파일 시스템에 저장된 메시지를 네트워크를 통해 전송할 때 zero-copy 기법을 사용하여 데이터 전송 성능을 향상시켰다.
*</em>
일반적으로 파일 시스템에 저장된 데이터를 네트워크로 전송할 때는 아래와 같은 데이터 전달 절차로 이루어진다.</p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/8db62978-fddc-42e2-8dc3-870f5246a9cf/image.png" alt=""></p>
<ol>
<li>디스크 → Read Buffer (DMA ; Direct Memory Access)<ul>
<li>유저가 read() 시스템콜을 호출하면, DMA 엔진에 의해 디스크로부터 데이터를 읽어 커널 영역의 Read Buffer 에 저장</li>
</ul>
</li>
<li>Read Buffer → Application Buffer (CPU)<ul>
<li>커널 주소 공간에는 유저가 접근할 수 없으므로, Read Buffer 의 내용을 Application Buffer 로 복사</li>
</ul>
</li>
<li>Application Buffer → Socket Buffer (CPU)<ul>
<li>유저는 Application Buffer 에서 읽어들인 데이터를 Socket Buffer 로 전송하기 위해 send() 함수 호출</li>
<li>send() 함수 호출 시 커널 영역에 있는 Socket Buffer로 데이터 복사</li>
</ul>
</li>
<li>Socket Buffer → NIC Buffer (DMA)<ul>
<li>Socket buffer에 있는 데이터를 NIC buffer로 복사하고 네트워크를 통해 전송<br>

</li>
</ul>
</li>
</ol>
<p>전통적인 데이터 복사 방식은 4번의 컨텍스트 스위칭과 4개의 메모리 복사본이 생기면서 불필요한 복사와 system call이 발생하게 된다.
이처럼 비효율적인 동작을 개선하기 위해 소개된 기법이 zero copy 이다.</p>
<p>리눅스 2.2 버전에서 처음 소개된 sendfile() 시스템 콜이 제로카피 동작을 구현하고 있다.</p>
<pre><code class="language-c">#include&lt;sys/sendfile.h&gt;
ssize_t sendfile(int out_fd, int in_fd, off_t * offset &quot;, size_t&quot; &quot; count&quot; );</code></pre>
<p>자바에서는 nio 패키지의 transferTo(), transferFrom() 메소드로 구현되어 있으며, 2가지 메서드 역시 sendfile() 시스템 콜을 이용해 구현되어있다.</p>
<pre><code class="language-c">public void transferTo(long position, long count, WritableByteChannel target);
cs</code></pre>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/0aeb531e-7ed0-4548-832a-5fa2b5d420c1/image.png" alt=""></p>
<p>zero copy 를 사용하면, 커널 영역의 Read Buffer 에서 Socket Buffer로 직접 복사가 가능하여 효율적으로 데이터를 전송할 수 있다.
read(), send() 두번의 시스템콜이 transferTo() 한번의 호출로 가능해진다.</p>
<ol>
<li>유저가 transferTo() 메서드를 통해 파일 전송 요청</li>
<li>read() 시스템콜과 동일하게, DMA 엔진이 디스크에서 파일을 읽어 커널 주소 공간에 위치한 Read buffer에 데이터를 복사</li>
<li>커널 모드에서 유저 모드로 컨텍스트 스위칭 없이, 바로 Socket buffer로 데이터를 복사</li>
<li>Socket buffer에 복사된 데이터를 DMA 엔진을 통해 NIC buffer로 복사</li>
</ol>
<p>따라서 transferTo() 메서드 호출시 커널모드로 1번, 종료시 유저모드로 1번 총 2번으로 컨텍스트 스위칭으로 줄었고, 데이터의 복사본이 4군데에서 3군데로 줄어들었다.
컨텍스트 스위칭 횟수와 복사본의 개수가 줄어든만큼 CPU 자원의 낭비가 줄어들게 되어 성능이 향상되었다.</p>
<p>리눅스 커널 2.4 이후부터는 NIC 장비가 “Gather Operation”을 지원할 경우 복사본을 더 줄일 수 있게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/fe4a6aee-fc51-4ef2-8874-6d3caecb77c3/image.png" alt=""></p>
<ol>
<li>사용자가 transferTo() 메서드를 호출한다. DMA엔진이 디스크에서 파일을 읽어 커널에 위치한 Read Buffer로 데이터를 복사한다.</li>
<li>데이터를 Socket Buffer 로 복사하지 않는 대신 데이터가 저장된 위치와 데이터 사이즈에 대한 정보와 함께 디스크립터가 Socket Buffer 에 추가된다.</li>
<li>DMA 엔진은 이 정보를 이용해 Read Buffer에 있는 데이터를 NIC Buffer 에 바로 복사하고, 네트워크로 데이터를 전송한다.</li>
</ol>
<p>이러한 최적화를 위해서는 Gather operation과 프로토콜의 checksum 기능이 추가적으로 필요하다.</p>
<p>아래 표는 실제 리눅스 2.6 커널 버전에서 성능을 비교한 것인데 전통적인 방식의 파일 전송과 transfer() 메소드를 이용한 파일 전송 속도를 비교했을 때, <strong>수행시간이 약 65% 줄어들었다.</strong>
<img src="https://velog.velcdn.com/images/eunz_juu/post/09deffdb-7553-48cc-9680-6092a5251edf/image.png" alt=""></p>
<p>디스크에서 파일을 읽은 후 추가 작업 없이 바로 네트워크로 전송하는 파일 서버나 정적 파일을 전송하는 웹 서버의 경우 zero copy 기법을 사용하면 성능 개선 효과를 얻을 수 있다.
특히 네트워크 속도가 매우 빨라서 성능상 병목점이 CPU로 몰릴 수록 불필요한 데이터 복사를 제거하여 성능 개선을 도모할 수 있다.</p>
<blockquote>
<p>요약하자면, 디스크에서 파일을 읽고 네트워크로 전달하는 과정에서 read(), send() 두번의 시스템 콜로 인한 컨텍스트 스위칭과 데이터 복사에 따른 성능 저하가 따랐다. 
zero copy의 transferTo() 메서드를 통한 하나의 시스템콜을 활용함으로써, <strong>컨텍스트 스위칭과 데이터 복사에 필요한 비용을 줄여 성능 개선을 이룰 수 있다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[정확한 계산을 위한 BigDecimal 사용하기]]></title>
            <link>https://velog.io/@eunz_juu/%EC%A0%95%ED%99%95%ED%95%9C-%EA%B3%84%EC%82%B0%EC%9D%84-%EC%9C%84%ED%95%9C-BigDecimal-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/%EC%A0%95%ED%99%95%ED%95%9C-%EA%B3%84%EC%82%B0%EC%9D%84-%EC%9C%84%ED%95%9C-BigDecimal-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 07 Jan 2024 13:15:08 GMT</pubDate>
            <description><![CDATA[<p>자바에는 BigDecimal 클래스가 존재한다. 
현 프로젝트에서 주문 모듈에 속해 일을 하면서, 여러 가격 정보들을 BigDecimal 타입으로 관리하는 것을 확인할 수 있었다. 또한, 최근에 읽은 이펙티브 자바 아이템 60 [정확한 답이 필요하다면 float 와 double 은 피하라] 내용 중 일부로도, 아래와 같은 해답을 제시하고 있다.</p>
<blockquote>
<p>금융계산에는 BigDecimal, int 혹은 long 을 사용하라</p>
</blockquote>
<p><strong>즉, 정확한 결과가 필요할 때는 BigDecimal 을 사용해야 한다.</strong>
이 게시물에서는 BigDecimal 의 개념과 사용 시 유의해야 할 점들 위주로 정리를 해보려고 한다.</p>
<h2 id="bigdecimal-이란">BigDecimal 이란?</h2>
<p><a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/math/BigDecimal.html">공식문서</a> 설명은 아래와 같이 설명하고 있는데, 이 중 핵심만 뽑아내보겠다.</p>
<blockquote>
<p><strong>Immutable, arbitrary-precision signed decimal numbers</strong>. 
A BigDecimal consists of an <strong>arbitrary precision integer unscaled value and a 32-bit integer scale</strong>. 
If the scale is zero or positive, the scale is the number of digits to the right of the decimal point. 
If the scale is negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).</p>
</blockquote>
<ul>
<li>Immutable, arbitrary-precision signed decimal numbers. 
: <code>불변의, 임의 정밀도와 부호를 가진 10진수</code> </li>
<li>arbitrary precision integer unscaled value and a 32-bit integer scale
: 임의 정밀도를 나타내는 <code>unscaled value</code>와 32bit의 <code>scale</code>로 이루어짐</li>
</ul>
<p>여기서 임의 정밀도를 나타내는 unscaled value 와 scale 은 무엇을 뜻할까?</p>
<ul>
<li><code>unscaled value</code>는 정수부를 표현하고, <code>scale</code>은 소수점 아래 자릿수를 표현한다.
ex) BigDecimal 3.14의 경우 <code>unscaled value</code>는 314이고 <code>scale</code>은 2가 된다.</li>
</ul>
<p>여기서 임의 정밀도라는 개념이 나오게 되는데 조금 더 자세히 알아보자!</p>
<h2 id="임의-정밀도란">임의 정밀도란?</h2>
<ul>
<li>정수나 소수를 정확히 표현하기 위해 제한이 없는 정밀도</li>
<li>정밀 계산이 필요한 금융, 과학, 통계 분야에서 유용하게 사용된다.</li>
<li>큰 숫자 = [int] + [int] + [int] ... 의 형태로, <strong>정수를 숫자 배열로 간주하고 자릿수 단위로 쪼개서 배열형태로 표현함</strong>으로써 무제한 자릿수의 정수를 담는 방식이라고 생각하면 된다.</li>
</ul>
<h2 id="그렇다면-bigdecimal-을-사용할-때-주의해야-할-점은-무엇일까">그렇다면, BigDecimal 을 사용할 때 주의해야 할 점은 무엇일까?</h2>
<h3 id="1-bigdecimal-초기화할-때-string-으로-초기화하자"><strong>1. BigDecimal 초기화할 때, String 으로 초기화하자!</strong></h3>
<pre><code class="language-java">    // double 타입을 그대로 초기화하면 기대값과 다른 값을 가진다.
    // 0.01000000000000000020816681711721685132943093776702880859375
    new BigDecimal(0.01);

    // 문자열로 초기화하면 정상 인식
    // 0.01
    new BigDecimal(&quot;0.01&quot;);

    // 위와 동일한 결과, double#toString을 이용하여 문자열로 초기화
    // 0.01
    BigDecimal.valueOf(0.01);</code></pre>
<p>double 타입을 그대로 초기화하면, 기댓값과 다른 결과를 낳는데 왜 double 타입을 받는 생성자가 있을까? 하는 의문이 들어 BigDecimal 클래스를 살펴보았다.</p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/d41b6cef-2bf0-41d9-b0e8-c57d7c45df8b/image.png" alt=""></p>
<blockquote>
<ol>
<li>이 생성자의 결과는 예측하기 어려울 수 있습니다. 
예를 들어, Java에서 new BigDecimal(0.1)을 작성하면 0.1과 정확히 일치하는 BigDecimal이 생성되는 것으로 생각할 수 있지만, 실제로는 0.1000000000000000055511151231257827021181583404541015625와 같이 나타납니다. <strong>이는 0.1을 double로 정확히 표현할 수 없기 때문에 발생하는 현상</strong>입니다.<ol start="2">
<li>반면에 String 생성자는 예측 가능합니다. new BigDecimal(&quot;0.1&quot;)을 작성하면 기대한 대로 0.1과 정확히 일치하는 BigDecimal이 생성됩니다. 따라서 <strong>일반적으로 이 생성자 대신 BigDecimal(String) 를 사용하는 것이 권장됩니다.</strong></li>
</ol>
</li>
</ol>
</blockquote>
<p>주석을 통해서도 확인할 수 있듯이, BigDecimal 클래스에서도 String 생성자를 사용하는 것을 권장하고 있다.</p>
<p>그래서 double 생성자 존재 이유에 대한 결론을 내리면, 
BigDecimal 클래스의 double 타입을 받는 생성자는 부동 소수점 오차를 피하고 정확한 10진수 연산을 위해 제공하고 있다.
<strong>명시적으로 이런 생성자를 제공함으로써 double -&gt; BigDecimal 로 변환하도록 도와주긴 하지만, 이미 double 자체가 정확한 10진수로 변환될 수 없는 부정확한 값을 가지고 있기 때문에 정확성을 보장할 수 없다고 한다.</strong></p>
<h3 id="2-bigdecimal은-기본-타입이-아닌-오브젝트이기-때문에-동등-비교-연산-시-유의하자">2. BigDecimal은 기본 타입이 아닌 오브젝트이기 때문에 동등 비교 연산 시 유의하자!</h3>
<ul>
<li>equals() : unscaled value, scale 을 모두 비교</li>
<li>compareTo() : 소수점 맨 끝의 0을 무시하고 값만을 비교하고 싶을 때 사용</li>
</ul>
<pre><code class="language-java">    final BigDecimal b1 = new BigDecimal(&quot;7.10&quot;);
    final BigDecimal b2 = new BigDecimal(&quot;7.1&quot;);

    System.out.println(b1 == b2); 
    // false _ 주소값 비교

    System.out.println(b1.equals(b2)); 
    // false _ 7.10과 7.1은 논리적으로 같은 수일지라도, 소수점아래 자릿수가 다르므로 equals의 결과는 false

    System.out.println(b1.compareTo(b2)); 
    // 0 (true) _ 0을 무시하고 7.1 로만 비교하니까 0 출력</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[저는 2023년에 이렇게 살았습니다.]]></title>
            <link>https://velog.io/@eunz_juu/%EC%A0%80%EB%8A%94-2023%EB%85%84%EC%97%90-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%82%B4%EC%95%98%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@eunz_juu/%EC%A0%80%EB%8A%94-2023%EB%85%84%EC%97%90-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%82%B4%EC%95%98%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 24 Dec 2023 12:10:05 GMT</pubDate>
            <description><![CDATA[<p>2023년을 단어로 정리해보자면 <code>새로움</code>, <code>도전</code> 이다.
작년을 내 나름대로는 최선을 다해서 보냈다고 생각해서, 올해는 별거 없을 거라고 생각했는데…
곱씹어보니 내 나름대로는 큰 도전들을 해왔고, 새로운 것들을 많이 경험한 한 해였다 !!!!</p>
<p>1) 2022년에 세웠던 <code>2023년 목표를 얼마나 이루었는지</code> 되돌아보고
2) 2023년을 크게 <code>4가지 키워드 [취업, 독서, 운동]</code> 로 되돌아보고
3) 2024년의 <code>액션아이템</code>을 정해보려고 한다 </p>
<p><strong>그럼 스따또.</strong></p>
<h1 id="2022년에-적은-2023년-목표-되돌아보기">2022년에 적은 2023년 목표 되돌아보기</h1>
<p>위의 목표는 2022년 회고에 적었던 내용을 그대로 들고 왔다!</p>
<blockquote>
<p><strong>2023년 목표</strong> *&quot;미니멀리스트가 되자&quot;*</p>
<p><strong>1. 버릴 줄 아는 사람이 되자</strong>
(중략) 조급하면 될 것도 안된다 … 인생 80살까지 산다고 생각하면 당장 눈앞에 1년 뭐가 중요할까,,, 여유를 가질 줄 아는 사람이 되자,,, (중략)</p>
<p><strong>2. 내 감정을 스스로 컨트롤할 줄 아는 사람이 되자</strong>
(중략) 부정적인 감정을 훌훌 털 줄 알고 그 과정에서 내 옆에 있는 사람들에게도 잘하는 사람이 되고 싶다 (중략)</p>
<p><strong>3. 제발 운동하자 제발제발제발제발제발제발 미친사람아 제발 🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏</strong>
(중략) 제발 싸피하면서 건강챙기자 ...............제발... 정말 간절한 목표다 ..............제발 내가 했으면 ...........일주일에 2번이라도 하자..... (중략)</p>
<p>늘 그랬듯 내가 덜 후회 할 선택을 하자 !!!</p>
</blockquote>
<h2 id="그래서-목표를-이루셨는지">그래서.. 목표를 이루셨는지?</h2>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/f99b1512-6dc9-4575-b6f1-5e9b9d511d1c/image.png" alt="">
<strong>Q1. 버릴 줄 아는 사람이 되었는가?</strong> 
<strong>A1. 적당히 버렸다.</strong> 
취업하고 3월 말부터 6월말까지 정말 회사일 외에는 아무것도 안했다.. 집에오면 진빠져서 누워있는 이슈 + 회사 적응부터 하자 였기 때문에..</p>
<p>매일 같이 퇴근하고 노는게 일상이었고 3개월 내내 탱자탱자 신나게 놀았다</p>
<p>그러고 나니 7월부터는 슬슬 노는게 지겨워지고 (?) 무언가를 새로 시작할 힘이 생겨서 하반기는 북스터디로 야무지게 달렸다!! 3개월 논거 내 나름대로 많이 버렸다고 생각한다… 내나름대론…</p>
<p><strong>Q2. 내 감정을 컨트롤 할 줄 아는 사람이 되었는가?</strong> 
<strong>A2. 잘 모르겠어요.</strong> 
부정적인 감정… 그렇게 올 한해는 크게 뭔일이 나진 않아서 2번에 대해 고려해본적이 없는 거 같은데.. 그치만 올 한 해 인간관계에서 가장 크게 느낀 것은 하나 있는데.. 나한테 잘해주는 사람 아니면 나도 그냥 그사람이 하는 행동 고대로 똑같이 대해주기로 다짐했다 ~ 이래야 인생살기 좀 편할거 같드라구요</p>
<p><strong>Q3. 운동을 하는가?</strong>
<strong>A3. 시도를 하긴 했다</strong> (9월~12월) 
비록 12월 중순부로 피티를 끊고 다니지는 않지만… 내년에는 허리통증 없애기에 전념할 예정 아좌작 !!!!!!!!!!</p>
<p>위의 목표 플러스 알파로 적어두었던게 있길래 하나하나 짧게씩이라도 기록해보려고 한다 
생각보다 적어둔게 많아서 힘들군요</p>
<blockquote>
<p><strong>2023년 ~ 나의 목표 ~</strong></p>
<ol>
<li>취업하면 너무 좋지만 압박감에 나를 혹사 시키면서 조급해 하지는 말며 취업하기 (?) </li>
</ol>
<p><strong>→ 4월 첫출근이면 내 생각보다는 빠르게 취업함</strong>
2. 맛있는 스시 나오는 오마카세 먹기. 초 밥 좋 아 
<strong>→ 못먹음</strong>
3. 교복 입고 에버랜드 가기, 에버랜드 안간지 15년도 넘었으니 갈 때 됐다 
<strong>→ 못입음. 못감</strong>
4. 겨울에 캐리비안베이 가기, 얼굴만 동동 뜨고 위에는 춥지만 아래는 따뜻한 몽롱한 기분 아주 궁금하다 
→ <strong>못감</strong>
5. 일본 가서 규카츠랑 사케동 먹기, 규카츠가 입에서 사르르 녹는다는데 진짜 먹고싶다 
→ <strong>못감. 못먹음</strong>
6. 제발 일주일에 2번 이상 운동하기. 제발 해주세요 
→ <strong>하다가 말았음</strong>
7. 클라이밍 한 달에 1번 이상 가보기. 클라이밍도 재밌던 데 나름의 취미로 삼고 싶어요 
→ <strong>한번도 안감</strong>
8. 욕하지 않기. 대신 웃을 때만은 허용해주세요 
→ <strong>안한 거 같긴함 아닌가? 화날때도 한거같긴함</strong>
9. 독서하는 습관 책 6권 이상 읽기. 절대 안할 것 같음 
→ <strong>북스터디 덕에 6권읽음</strong> 
10. 취미 찾기. 독서가 취미가 됐으면 
→ <strong>취미 못찾음. 독서는 아직 취미라고 하긴 너무 강제적임</strong>
11. 자취방 깔끔하게라도 꾸며보기. 지저분하지만 않게 살자 
→ <strong>지저분함</strong>
12. 해외 2번 이상 가기, 그곳이 어디든. 
→ <strong>1번도 못감</strong>
13. 여름에 강릉 가기. 강릉엔 항상 좋은 기억들이 많다 
→ <strong>못감</strong>
14. 호캉스 가보기. 호호 불면은 구멍이 뚫리는 커다란 솜사탕 
→ <strong>못감</strong></p>
<ul>
<li>1번, 9번 완벽달성</li>
<li>6번, 8번 애매모호</li>
<li>나머지 미달성</li>
</ul>
<p><strong>2023년, 내가 되고 싶은 ~ 나 ~</strong></p>
<ol>
<li>하고 싶은 게 생겼을 때 최대한 기회를 잡는 사람 
→ <strong>하반기에는 심적 여유가 있을 때는 나름 하고 싶었던 것들을 다 도전했음 (북스터디, 외부스터디, 글또) 하지만,, 회사에서 하는 활동 하나를 신청안한게 아직도 후회되는 게 있음</strong></li>
<li>올 한해도 내가 좋아하는 사람들과 행복하게 사는 사람 
→ <strong>적당히 만나고싶은 사람들만 만나면서 잘 살았음</strong></li>
<li>감사함을 그 때 그 때 표현할 줄 아는 사람 
→ <strong>고마운건 그때그때 잘 말함</strong></li>
<li>용기내서 내 마음을 말할 줄 아는 사람 
→ <strong>적당히 할말은 하고 산 듯</strong></li>
<li>부정적인 감정을 빠르게 털어내는 사람 
→ <strong>하루종일 감. 그래도 저녁에 자고 아침에 일어나면 풀려고 노력했음</strong></li>
<li>기분이 태도가 되지 않는 사람 
→ <strong>태도가 된 때도 있음</strong></li>
<li>다른 사람의 성공을 진심으로 응원해줄 수 있는 사람 
→ <strong>사람인지라 여전히 쉽지 않음 사바사 케바케 심함</strong></li>
<li>누군가의 꿈이 되는 삶을 사는 사람 
→ <strong>이건 모르겠음</strong></li>
<li>만나면 또 만나고 싶은 사람 
→ <strong>이것도 난 모름</strong></li>
<li>주변 사람을 잘 챙기는 사람 
→ <strong>잘 챙기지 못한 거 같음</strong></li>
</ol>
</blockquote>
<p>생각보다 자잘하게 적은 투두들은 이루지 못한 것들이 많은 거 같다</p>
<p>흠… 생각보다 내 일상을 채우는 재미있는 활동을 대부분 못한 거 같은데 2024년에는 여가활동도 조금 더 …? 해보면 풍요로운 한 해가 될 거 같다 !</p>
<h1 id="2023년-한-해-되돌아보기">2023년 한 해 되돌아보기</h1>
<h2 id="취업">취업</h2>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/48c22d04-d669-4968-b557-ccdc4b9fa4bd/image.png" alt="https://velog.velcdn.com/images/eunz_juu/post/48c22d04-d669-4968-b557-ccdc4b9fa4bd/image.png"></p>
<p>뭐니뭐니해도 2023년 가장 뿌듯하고 행복했던 일은 취업을 했다는 것이다. 작년 하반기부터 조금씩 준비해왔지만 본격 취준은 올해 1월 싸피에 입과하면서부터였다. 9-6시 싸피 수업을 듣고, 수업이 끝나면 도서관에 가서 코테 공부를 하고 돌아와 1시쯤 잠에 들면 7시 반쯤 일어나서 다시 싸피를 갔다. 이 생활을 2주 정도 하고 나니 도저히 이건 1년동안 버틸 수 없겠다 싶었다. 그래서 최대한 빨리 나가는 것을 목표로 잡고 아등바등하다가 3월 29일 싸피를 나가게 되었다. 내 생각보다 더 빠른 싸탈이라서 아직도 놀라운 것 같닥 ㅋ.ㅋ.ㅋ.</p>
<p>가고 싶었던 1순위 회사는 아니었지만, 현재 투입된 프로젝트에서 배울 것도 많고, 함께 하는 팀원들이 너무 좋고 재미있어서, 어느 정도 만족하며 다니고 있다. </p>
<p><strong>취업 준비할 때의 간절함을 잊지 말자</strong>라는 문장을 주기적으로 생각하고 출근한다…</p>
<p>그리고 신입사원 교육이랑 연수에서 액자랑 무드등 만들었는데 거기에 적은 문구는 아래와 같다</p>
<blockquote>
<p><strong>간절했던 마음을 잊지말자   (입사하자마자)
열정을 전파하자                   (입사한지 8개월 후)</strong></p>
</blockquote>
<p>이 두가지 문장을 마음속에 품고,,,,,열심히 회사를 다녀보도록 하쟈,,,,,,,,</p>
<p>아침에 일어나는 건 여전히 너무 힘든 것 같다. 아침마다 풀재택근무 회사가고 싶다는 생각을 하며 일어나곤 한다… 그치만 회사가도 항상 후회는 없이 맛있는 밥도 먹고 열심히 집중해서 일하다 와서 여전히 회사가는게 더 좋다구 생각은 한다.. 그치만 일어나기싫어</p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/673c944d-aff5-46ac-90f9-0fabec225ed1/image.png" alt="https://velog.velcdn.com/images/eunz_juu/post/673c944d-aff5-46ac-90f9-0fabec225ed1/image.png"></p>
<p>일단 회사에 가서 깨진 고정 관념들도 있고, 새로 깨달은 점이 몇가지 있어서 적어보려고 한다</p>
<h3 id="신기술-써보는-게-장땡이-아니다">신기술 써보는 게 장땡이 아니다.</h3>
<p>취준을 하면서 채용공고 필수/우대 사항을 보다보면, 새로운 기술적 개념 + 키워드들에 대해 공부를 해야 한다는 생각이 자주 들었다. 그 기술을 왜 사용해야 하는지도 모르고 냅다 덤벼들어 공부를 해왔던 것 같다.
그런데 회사에서 온보딩을 하며 다른 분이 받은 코멘트를 보니 &quot;<strong>회사에서는 &#39;딱히 이유는 없다&#39; 라는 말은 먹히지 않는다.</strong>&quot; 였다. 그 말이 확 와닿았는데, 결국 회사에서 어떠한 기술을 사용하여 구현했을 때는 그냥 신기술이니까~ 내가 한번 써보고 싶으니까~ 와 같은 이유는 받아들여지지 않는다는 것이다. 지금 내가 구현하는 코드에 해당 기술이 필요한 이유가 무엇이고, 기술을 도입했을 때 발생하는 사이드이펙트는 뭐고, 현재 조직원들이 그 기술을 받아들일 여유가 있고 실력을 갖추고 있는지 등등 고려해야할 사항들이 정말 많다. 그래서 무조건 신기술 신기술 !!!!!! 하던 생각이 옳지 않았다는 것을 <em>(개인적인 생각임)</em> 느꼈고, 여러 기술들을 써보고 왜 쓰는지에 대한 이해를 바탕으로 개발을 하는 것이 오히려 더 중요하다는 것을 깨달았다. <strong>회사에서 못쓴다고 불평불만할게 아니라, 정 궁금하면 사이드 프로젝트로 하면 된다!</strong></p>
<h3 id="도메인-지식이-너무나도-중요하다">도메인 지식이 너무나도 중요하다.</h3>
<p>그리고 나는 능력있는, 잘하는 개발자로서의 역량으로 구현능력만을 생각해왔던 것 같다. 취준생때는 다른 사람들의 이력서나 깃허브를 보면서 난 이 기술을 안써봤는데, 이 사람은 할 줄 아네.. 나도 공부해야하는데.. 어떡하지.. 이런식으로 사고가 흘러갔고 내가 부족한 기술적 부분만 보였다.</p>
<p>하지만 회사는 구현 능력 뿐만 아니라 도메인 지식이 정말x100 중요하다는 것을 느꼈다. 모빌리티 산업에 무지했던 나였기에 msrp, fsc 등등 자동차 산업에서 일반적으로 자주 쓰이는 용어들도 몰라서 코드를 보는데 이해가 안되는 부분이 너무 많았다. 그래서 사내 문서나 코드 주석을 참고해서 궁금한 단어를 다 검색해서 자료를 끌어 모아모아 해석해보기도 하고, 많이 물어보면서 도메인 지식을 나름대로 정리해왔다. 하지만 여전히 나는 이 산업에 대해 모르는 게 너무 많은 것 같고, 앞으로도 배워갈 게 많다는 생각이 든다..</p>
<p>그래서 2024년에는 현재 하는 프로젝트에서 배울 수 있는 걸 끌어 모아모아, 누가 물어봐도 이 프로젝트에 적용된 도메인 지식에 대해 대답할 수 있을 정도로 성장하고 싶다. 개발단계 뿐만 아니라 기획/검토 단계에서도 풀어지지 않고, 누구보다 꼼꼼하게 검토하고 문서화하면서 프로젝트에 기여할 수 있는 사람이 되고 싶다 !</p>
<h3 id="문서화의-중요성에-대해-알았다">문서화의 중요성에 대해 알았다.</h3>
<p>처음에는 이걸 왜 정리하고 있는거지.. 라는 생각이 들었고 뭐라도 개발하고 싶고 코드를 치고 싶다는 생각이 많이 많이 들었다</p>
<p>하지만 나는 회사에 속해있고 혼자 일하는 사람이 아니니까..  함께 일하기 위해서 사용되는 <strong>깔끔하고 구조화된 문서가 생산성을 크게 높여준다는 것을 알게 되었다.</strong> 같이 일하시는 분 중에 문서화를 증말 기깔나게 하시는 분이 있는데,, 매번 그 문서를 통해 추가 개발 요건에 대한 검토도 쉽게 할 수 있었고, 기획 검토 단계에서는 개발할 때 고려해야 할 부분들을 단번에 모아볼 수 있어서 참 편했다. 그분의 노하우.. (?) 어깨너머 배워봐야겠다. 그래서 나도 누군가에게 도움이 되는 문서, <strong>누가 봐도 알아보기 쉽고 이해하기 쉬운 문서를 작성</strong>하고 싶다 !!!</p>
<p>그래서 사실 지금까지 문서 작업을 하면서도 함께 문서를 작성하는 분과 의논하에, 어떻게 하면 더 이해하기 쉬울지, 다른 사람들이 따라할 때 편할까? 를 고민하면서 많이 수정해왔다 !</p>
<p>지금해왔던 것처럼 꼼꼼하게 체계적으로 문서를 만들어보자 <del>~ 개발안한다고 불평하지말고말야</del></p>
<h3 id="찾았다-내-롤모델">찾았다 내 롤모델.</h3>
<p>총 세분의 롤모델인데,, 자세히 쓰면 드러날 거 같기 때문에 슬렁슬렁 얼레벌레 대충대충 써보려곻 한다</p>
<p>커뮤니케이션 / 개발 / 회사 다니는 마인드 부분에서 배우고 싶은 점이 있는 분들이어서 배울 점들을 쏙쏙 골라 닮아보려고 한다.</p>
<p>1) 커뮤니케이션</p>
<ul>
<li>기획과 사업에서 들어오는 요구사항들을 쳐낼건 쳐내면서 효율적으로 의사소통하시는 거 같다..</li>
<li>우선순위에 따라 해야할 일 정리를 잘해주셔서 함께 일하기가 정말 편하고 수월하다.. </li>
<li>그리고 정말 설명을 잘해주신다.. 이 일을 왜 해야하는지에 대한 설명을 더해주셔서 목적의식을 갖고 일을 하게 된 계기가 된 것 같다</li>
<li>이 분이 리더인 조직에서 일하는 사람들은 정말 복받은 거라는 생각이 드는 분이다.. 그냥 함께 있으면 업무 외적으로도 즐겁고 편안한 분위기를 만들어주시는 분이다 </li>
</ul>
<p>2) 개발</p>
<ul>
<li>코드리뷰가 정말 꼼꼼하다.. 리뷰 뿐만 아니라 그냥 모든 업무가 다 꼼꼼하셔서 너무 일잘러다.. </li>
<li>일할 때 집중해서 하는 모습이 넘 멋지다..</li>
<li>코드도 깔끔하고 너무 잘짜신다.. 근데 문서도 잘쓰신다.. 근데 이슈대응도 빠르시다.. 너무 너무 부럽다</li>
<li>의견 제시를 하실 때 (의견 + 의견에 대한 타당한 근거) 를 함께 말씀해주시는 게 도움이 많이 된다</li>
<li>큰 그림을 잘 그리시는 것 같다.. 뭔가 검토할 때 멀리보고 확장성을 고려한 설계를 하시는 게 정말 멋지다..</li>
<li>내가 이분 연차가 되어도 이분처럼 될 수 있을까..? 라는 생각이 들 정도로 멋진 분이고 배울점이 너무 많은분이다..</li>
</ul>
<p>3) 회사 다니는 마인드</p>
<ul>
<li>회사 생활을 할 때 크게 뭔가 걱정하지 않고 좋은 게 좋은 거지의 느낌으로 다니시는 거 같아서.. 유리멘탈인 나로서는 그런 여유롭고 안정적인 마인드가 부럽다..</li>
</ul>
<blockquote>
<p>취업 분야 4L 회고</p>
</blockquote>
<ul>
<li><strong>Liked : 좋았던 점은 무엇인가?</strong><ul>
<li>꾸준히 열심히 해왔던 것을 바탕으로 기회가 왔을 때 잡을 수 있었다는 생각이 들어서, 뿌듯하고 행복했다.</li>
<li>다행히 개발하는 팀에 들어오게 되어서 성장할 수 있는 기회들이 많이 있을 것 같아 기대가 된다. <br> <br></li>
</ul>
</li>
<li><strong>Lacked : 아쉬웠던 점, 부족한 점은 무엇인가?</strong><ul>
<li>조금 더 적극적으로 질문하지 못했던 것이 아쉬웠다. 지금 상황에서 이걸 물어봐도 되나? 하는 의문들이 계속 생겨서 물어보지 못한 것들도 있었는데, 망설일 때가 가장 빠른 시기니까 바로 물어보고 궁금증을 해결해야 겠다. <br> <br></li>
</ul>
</li>
<li><strong>Learned : 배운 점은 무엇인가?</strong><ul>
<li>회사에서는 일을 주기만을 바라면 안된다. 일이 없을 때는 스스로 찾아서 하는 방법도 터득해야 한다. <br> <br></li>
</ul>
</li>
<li><strong>Longed for : 앞으로 바라는 것은 무엇인가?</strong><ul>
<li>회사 코드에서 아직도 잘 모르는 부분에 대해서 스스로 공부하는 시간을 가지고, 적극적인 질문으로 부족했던 도메인 지식을 채워가자</li>
<li>적극적으로 질문하는 태도를 가지자</li>
<li>시킨다고 아무생각 없이 일하지 말고, 이 일을 왜하는가? 에 대한 근본적인 질문에 대해 스스로 대답해보며, 일의 의미를 찾자</li>
</ul>
</li>
</ul>
<h2 id="운동">운동</h2>
<p>작년 말에 허리디스크 초기라는 진단을 받았다. 1-2월에 싸피를 하면서 통증이 심해져서 두 달 동안 병원을 매주 갔던 것 같다. 이제는 정말 살기 위해서라도 운동이라는 걸 해야될때가 온 것 같구나라는 생각이 들었다. ㅠ ㅠ </p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/025ced23-589e-493c-acd4-0a91aa55823c/image.png" alt="https://velog.velcdn.com/images/eunz_juu/post/025ced23-589e-493c-acd4-0a91aa55823c/image.png"></p>
<p>그래서 운동과는 정말 거리가 먼 나인데도 불구하고 큰 마음을 먹고 자취방 근처 헬스장을 알아보기 시작했다. 그렇게 PT 20회와 헬스장 이용권을 끊었고 130만원 지출을 하게 되었다. 나에게는 너무 큰 돈이었지만, 운동을 그렇게 하기 싫어하던 내가 일주일에 적어도 2번은 피티를 받으러가고 매번 갈때마다 조금씩 무게와 횟수가 늘어가는 걸 보면서 뿌듯했다 ㅎㅋㅎㅋㅎ 하지만 피티가 아닌 날에는 단 한번도 운동을 가지 않았기에 헬스장 이용권은 거의 버린 셈이었다,,, 그래도 25년 동안 운동안하다가 일주일에 2번이라도 간게 어디냐라고 생각하고 있다.. 20회를 다 끝내고 피티를 조금만 더 받고 개인운동을 하고 싶어서 피티를 재등록했는데, 최근 디스크가 더 악화되어 더이상 피티를 받을 수는 없는 상황이 되었다.</p>
<p>허리가 안좋은데 운동을 잘 모르고 해서 더 악화된건가? 하는 생각이 순간 들어서 속상한 마음도 있었지만 회사를 다니면서 앉아있는 시간이 훨씬 길어져서 그런거라고 생각하려고 한다. 하하 </p>
<h3 id="20만보-걷기-챌린지-장렬히-실패">20만보 걷기 챌린지. 장렬히 실패.</h3>
<p>10월, 11월에는 회사에서 20만보 걷기 챌린지 이벤트가 있었다. 20만보를 달성한 사람들 중 5명을 추첨하여 에어팟을 준다고 하여 걷기 챌린지하려고 애플 워치까지 샀다. 내가 생각해도 너무 충동적인 구매였는데, 아니나 다를까 한 4번 착용하고 안쓰고 있어서 당근에 팔아버리려고 한다. 아무튼 20만보 걷기를 우리팀+타팀 동기들이랑 같이 내기를 했는데, 마감 날짜를 착각해서 19만 3천보를 달성해버려서 에어팟에 응모조차 못했다. 그리고 11월에는 추운 날씨 이슈로 일주일만에 포기해버렸다. 그래도 회사 덕에 매일 8천보씩 걷고 양재시민의숲역부터 양재역까지 걸어서 퇴근하기도 하면서, 건강을 조금은 얻었을 거라고 생각한다.. </p>
<p>올한해는 건강을 최우선목표로 잡고 건강을 챙기려고 한다 !!!
허리아프니까 정말 앉아있기가 힘들고 그래서,,  자취방과 본가에 모션데스크를 사두려고 한다 내 허리건강 지켜
걷기 운동도 퇴근하는 시점을 활용해서 30분씩 걷고, 허리 치료가 어느정도 진행되면 피티 말고 헬스장을 자발적으로 가는 멋진 어른이 되어보려고 한다..</p>
<blockquote>
<p>운동 분야 4L 회고</p>
</blockquote>
<ul>
<li><strong>Liked : 좋았던 점은 무엇인가?</strong><ul>
<li>돈을 쏟아붓긴 했지만, 그래도 일주일에 2번씩은 운동을 한 것만으로도 뿌듯했다.<br><br></li>
</ul>
</li>
<li><strong>Lacked : 아쉬웠던 점, 부족한 점은 무엇인가?</strong><ul>
<li>피티 외에는 한번도 혼자 운동하러 헬스장을 가지 않은게 아쉽다. 헬스하려고 운동화도 샀는데 한번도 안 신고 신발장에 쳐박혀있다.<br><br></li>
</ul>
</li>
<li><strong>Learned : 배운 점은 무엇인가?</strong><ul>
<li>아무리 가까워도 헬스장은 혼자 가기 너무 어렵다. 빠른걸음으로 3분 정도 걸리는 거리였는데도 날씨가 추워지니까 피티조차 가기가 싫었다.<br><br></li>
</ul>
</li>
<li><strong>Longed for : 앞으로 바라는 것은 무엇인가?</strong><ul>
<li>일단 피티는 더이상 받지 않고, 허리디스크 치료에 집중을 하기로 했다.</li>
<li>병원에서 알려주는 개인 운동을 루틴화하기</li>
<li>주 5회정도는 30분씩이라도 걷기</li>
</ul>
</li>
</ul>
<h2 id="독서">독서</h2>
<p>7월에 친구랑 만나서 우리 책을 좀 읽어볼까? 라는 이야기를 하다가 시작되었던 북스터디를 6개월 동안 지속해왔다. 처음에는 한 주에 한 챕터씩 읽는 것을 목표로 하였는데, 그렇게 되면 일요일에 항상 벼락치기할게 뻔하다는 것에 둘 다 동의했다. 그래서 매일 1문장이라도 읽기 + 한 주에 한 챕터 읽기, 이 2가지 목표를 가지고 북스터디를 진행해왔다. 하루 안 읽으면 벌금 1000원, 일주일동안 한 챕터를  못 읽은 경우 벌금 5000원으로 진행했고, 12월까지 모인 벌금이 약 15만원 정도였다. 친구와 함께 책을 읽으며 정리한 <a href="https://github.com/star-books-coffee">깃허브 레포</a> 이다 !! 
2023년 회고를 하면서 2023년 버전, 2024년 버전으로 노션을 싹 정리했는데 되돌아보았을 때 너무너무 뿌듯한 기록이 되었다 </p>
<p>이 스터디를 안했으면 2023년 하반기는 아무것도 남지 않는다고 해도 과언이 아니다.. </p>
<p>나는 북스터디를 통해 6개월동안 총 6권의 책을 완독할 수 있었다.</p>
<ul>
<li>함께 자라기</li>
<li>육각형 개발자</li>
<li>오브젝트</li>
<li>자바 코딩의 기술</li>
<li>지대넓얕1</li>
<li>그림과 작동 원리로 쉽게 이해하는 AWS 구조와 서비스</li>
</ul>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/f536d1a7-c623-4914-be0b-a3e98296c48a/image.png" alt="https://velog.velcdn.com/images/eunz_juu/post/f536d1a7-c623-4914-be0b-a3e98296c48a/image.png"></p>
<p>토비의 스프링, 헤드퍼스트 디자인패턴, 자바 웹 프로그래밍 NEXT STEP, Docs for Developer 를 사두었는데, 그와중에 대규모 시스템 설계2 나왔다고 해서 그 책도 읽어보고 싶어졌다. 사놓은 거부터 읽자 제발</p>
<p>회사에서 지원해주는 도서구입비도 이용하고 사비로 사기도하면서 책을 모으는 재미에도 빠진 것 같다.. ㅎㅎ 이제 책을 사도 실물 책으로만 구매할 것 같다 ! 지금껏 온라인으로 산게 몇권 있는데 ,, 새로 사서 다시 한번 읽는 것도 좋을 것 같다는 생각이 든다.</p>
<p>2024년에는 자바 웹 프로그래밍 NEXT STEP 부터 다시 스터디를 시작할 건데,, 이건 쌩 이론이 아니라 구현과 동시에 가져가야 하는 책이라서 !!! 그래도 2024년에는 코드를 좀 칠 것 같다 기대가 된다 </p>
<blockquote>
<p>독서 분야 4L 회고</p>
</blockquote>
<ul>
<li><strong>Liked : 좋았던 점은 무엇인가?</strong><ul>
<li>초등학생 때 이후로 정말 오랜만에 <strong>자발적으로 책을 읽고 있는 것만으로 뿌듯했다</strong></li>
<li>기술서적 위주로 읽었는데 코드를 실전에 많이 적용해보지는 못했지만, <strong>코드를 볼 때 시야가 넓어진 것이 체감이 되어서 좋았다.</strong> 회사에서 리팩토링할 것들을 제안하라고 할 때, 책을 읽기 전에는 제안하지 못했을 내용들이 있어서 신기하고 뿌듯했다.</li>
<li><strong>꾸준함의 힘</strong>을 또 다시 느끼게 되었다. 책을 매일 매일 읽으면서 출퇴근길에도 시간을 허투루 쓰지 않고 자투리 시간을 활용하려는 내 모습을 볼 수 있어서 좋았다.</li>
<li><strong>누군가와 함께 책을 읽고 공부하고 있다는 것이 큰 힘이 되었다.</strong> 혼자 읽으려고 했으면 한 달도 못했을 것 같은데, 함께 스터디를 하는 사람이 있어서 책 읽기 싫으면 디코에서 함께 노래 틀어놓고 하기도 하면서 재미있게 책을 읽을 수 있었다.</li>
<li>내가 <strong>어떤 분야에 관심이 있고 더 배우고 싶어하는지 확실히 알게 되었다.</strong> 백엔드 개발자로 취업하긴 했지만 클라우드/인프라 쪽에도 관심이 있어서 공부해보고 싶다는 생각이 있었다. 그런데 AWS 구조와 서비스 책을 완독하고 나니 클라우드를 더이상 공부하고 싶다는 생각이 사라졌다. 자바나 OOP 책읽는게 나에게는 훨씬 재밌다는 걸 느껴서 이 분야에 집중하려고 한다. <br><br></li>
</ul>
</li>
<li><strong>Lacked : 아쉬웠던 점, 부족한 점은 무엇인가?</strong><ul>
<li>12월에는 조금 열정이 떨어져서 그런지 벌금도 많이 내기도 했고, 친구가 쉬자고 할 때 같이 쉬어버리는 경우가 많았는데 1월부터는 다시 마음을 다잡고 새 시작을 해보려고 한다.</li>
<li>책 내용을 그대로 요약하는 것도 물론 나중에 참고할 때 좋지만, 책 내용 플러스 알파로 찾아보면서 공부하는 시간을 가지지 못한 것이 아쉬웠다.</li>
<li>책을 읽으면서 전에 했던 프로젝트 코드를 리팩토링해보고 싶다는 생각을 자주 했는데, 행동으로 옮기지 못한 것이 아쉬웠다.<br><br></li>
</ul>
</li>
<li><strong>Learned : 배운 점은 무엇인가?</strong><ul>
<li>좋았던 점 중에서 3번이 배운 점인 것 같다. 꾸준하게 책 읽고 루틴화하는 방법을 배울 수 있었던 것 같다.<br><br></li>
</ul>
</li>
<li><strong>Longed for : 앞으로 바라는 것은 무엇인가?</strong><ul>
<li>일단 미리 사두기만 한 책들이 널려있는데, 지금까지 읽은 책보다 조금 더 난이도가 있을 거라고 예상된다. 그래서 지금까지 해왔던 것처럼 꾸준함의 힘을 믿고 지치지 않고 완독하는 것이 목표이다.</li>
</ul>
</li>
</ul>
<h2 id="2024년의-액션아이템">2024년의 액션아이템</h2>
<p>2023년을 돌아봤는데... 생각보다 많은 것들을 배우고 느낀 것 같다 이제 이걸 2024년에 적용하기만 하면 된다!!!!
내년의 마인드셋은 아래와 같다 !!</p>
<blockquote>
</blockquote>
<ul>
<li>건강이 없으면 공부고 뭐고 아무것도 못한다 → 허리 건강 최우선으로 관리하기 !!!!!!!!!!!!!!!!!!!!!!</li>
<li>1년 전의 다른 사람과 비교하지 말고, 나 자신과 비교하기</li>
<li>회사 타이틀을 떼고도 개발자 OOO 로 홀로 설 수 있는 실력 만들기</li>
<li>열정을 주변에 전파하는 사람이 되기</li>
<li>이직은 먼 훗날이겠지만, 내가 앞으로 나아갈 목적지가 어딘지 생각해보기</li>
<li>세상은 좁으니 어딜 가서도 최선을 다하는 모습을 보여주기 (그 사람 정말 열심히 하던데? 하는 사람 되기)</li>
<li>기술적 겸손함을 갖추기 - 다 안다고 착각하지마라 넌 아는게없다 깨달음의 비탈길을 올라가는 한 해가 되자 ~ 
<img src="https://velog.velcdn.com/images/eunz_juu/post/c304ced0-00c5-4b27-843f-28528ca2fa50/image.png" alt=""></li>
</ul>
<p>우선 크게 운동, 영어공부, 개발공부에 대해서는 해보고 싶은 것들을 투두리스트 형태로 정리해서 2024년 회고할 때 다시한번 얼마나 달성했는지 체크해보려고 한다
<em>굵은 글씨는 반드시 해야 하거나 이미 확정된 일정들이고, 얇은 글씨는 선택사항이다!</em></p>
<p>업무 관점에서는 내가 업무를 하면서 갖춰야 할 마인드셋 위주로 정리를 했다</p>
<h3 id="운동-1">운동</h3>
<p>일단 피티는 지금 당장은 못하지만, 디스크 치료와 개인 운동을 병행하면서 허리 아픈 것부터 조금 해결해보려고 한다.
젊으니까 좀만 관리해도 낫는다고 말씀하시니 많이 신경쓰면서 건강 관리를 해야겠다. 우하하</p>
<ul>
<li><strong>주 5회 30분씩은 걷기</strong><ul>
<li>퇴근시간 이용하기 ; 선릉역으로 퇴근 / 하차 서울대입구 → 낙성대까지 걸어오기</li>
<li>퇴근시간 이용하지 못할 경우 ; 알아서 걷기</li>
</ul>
</li>
<li><strong>허리 치료 끝내고  헬스장을 끊어서 최소 주 3회 운동하기</strong></li>
<li><strong>다리 스트레칭은 치료할 동안은 매일 하기</strong></li>
<li>라운드 숄더 운동 하기</li>
</ul>
<h3 id="영어-공부">영어 공부</h3>
<p>2024년에는 영어라는 키워드를 하나 더 해보려고 한다.
사실 올해도 말해보카라는 어플을 통해 조금씩이라도 영어 공부를 해왔는데, 내가 원하는 것은 영어 회화 공부이다.
회사에서 지원해주는 프로그램을 신청해서, 전화영어로 회화 공부를 하는 것이 목표인데,…………할지를 모르겠다 </p>
<ul>
<li><strong>말해보카 매일하기</strong></li>
<li><strong>매일 1장 영어 쓰기 습관 100일의 기적 Advanced 완독</strong></li>
<li>회화공부 &lt;&lt;&lt; 회사 어학 프로그램 확인 후 추후 계획 작성 필요</li>
</ul>
<h3 id="개발-공부">개발 공부</h3>
<p>자바, 스프링 위주의 책을 읽으면서 실무에서 적용가능한 개발 능력치를 갖추자
앞으로 읽으려고 사둔 책들이 대부분 분량이 많아서, 2024년에는 솔직히 2권…완독해도 잘했다고 생각한다</p>
<ul>
<li><strong>회사 스터디 (일주일에 3시간정도 투자)</strong></li>
<li><strong>msa 스터디 (일단 이론 책부터 시작)</strong></li>
<li>개발 관련 독서<ul>
<li><strong>자바 웹 프로그래밍 next step</strong></li>
<li><strong>docs for developer</strong></li>
<li>헤드퍼스트 디자인패턴 완독</li>
<li>토비의 스프링 1권 완독</li>
</ul>
</li>
<li>구현 능력 키우기<ul>
<li><strong>url 단축기 만들기</strong></li>
<li><strong>XXX프로젝트 리팩토링</strong></li>
<li><strong>테오의 프론트엔드 - 백엔드로 참여</strong></li>
<li>오픈소스 기여해보기</li>
<li>해커톤 참여</li>
<li>넘블 챌린지 참여</li>
<li>재미있는 사이드 플젝 아이디어가 있다면 참여 or 모아보기</li>
<li>next step 강의 들으면서 코딩</li>
</ul>
</li>
<li>직장인 동아리 가능한 경우 회사 일정 고려하여 지원해보기</li>
<li><strong>오픈소스 컨트리뷰트 프로그램 참여</strong></li>
<li><strong>글또 X 유데미강의 듣기 (~4월)</strong><ul>
<li><strong>【Java 멀티스레딩, 병행성 및 성능 최적화 - 전문가 되기</strong></li>
<li><strong>【한글자막】 Apache Kafka 시리즈 – 초보자를 위한 아파치 카프카 강의 v3</strong></li>
</ul>
</li>
</ul>
<h3 id="업무">업무</h3>
<p>내년 4월이면, 회사생활도 1년이 된다. 지금까지는 시키는 일이니까 하는, 조금은 수동적인 태도로 임했던 것 같다. 하지만 조금 더 적극적인 자세로 다른 사람이 하는 일에도 관심을 갖고 어떤 의미를 가진 일인지 이해하고 수행하는 태도를 가져볼 것이다. 그리고 남일이라고 생각하지 않고 같은 팀이니까, 다른 팀원분들께도 도움이 될 수 있을 정도로 도메인을 이해하는 능력치를 쌓는 것을 목표로.. !! 일잘러를 향해 한걸음…</p>
<ul>
<li>이슈발생 시 어떤 이슈인지 바로 알아내는 사람으로 성장하기</li>
<li>개발이 아니라 기획 검토 단계에도, 관통하는 질문으로 기여하기</li>
<li>도메인의 큰 흐름을 파악하고 있는 사람으로 성장하기 (누가 물어봐도 대답할 수 있을정도)</li>
<li>이번해에는 적극적으로 많은 티켓을 할당받아 개발적으로도 회사에 기여하기</li>
<li>하고 싶은 일이 있으면 적극적으로 의사표현하기</li>
<li>내 일 아니라고 생각하지 말고 적극적으로 임하기</li>
<li>내가 바쁜 와중에도 다른 사람을 도와줄 수 있는 사람이 되기</li>
<li>바쁠 때 예민하게 행동하지 않기 (특히 말투 조심하기)</li>
<li>공개 커뮤니케이션하기</li>
<li>모르면 질문을 좀 하기 (갈수록 더 못 물어보게 됨)</li>
<li>업무할 때 나만의 생각과 개념을 정확히 정립한 후에 일하는 습관들이기</li>
<li>이 일을 하는 목적이 무엇이고, 어떤 방향으로 흘러가는 것이 옳은지 끊임없이 생각하기</li>
<li>스터디를 통해 발표할 기회 잘 활용하기</li>
</ul>
<p>2023년도 수고한 나자신에게.. 2024년도 고생하렴.. 
2024년 목표를 한마디로 정해보고 회고를 마치겠다 !!!!!!!!!!!!!!!!</p>
<h3 id="후회없이-끊임없이-개발에-몰두한-2024년을-만들어가자-🤸♀️">후회없이 끊임없이 개발에 몰두한 2024년을 만들어가자 🤸‍♀️</h3>
<h3 id="정신-건강과-몸-건강을-모두-관리한-2024년을-만들어가자-👊">정신 건강과 몸 건강을 모두 관리한 2024년을 만들어가자 👊</h3>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/55cbe57d-9be1-408c-946e-498ad7060ba5/image.png" alt="https://velog.velcdn.com/images/eunz_juu/post/55cbe57d-9be1-408c-946e-498ad7060ba5/image.png"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[글또] 5개월간 도라희의 여정을 시작하며...]]></title>
            <link>https://velog.io/@eunz_juu/%EA%B8%80%EB%98%90-5%EA%B0%9C%EC%9B%94%EA%B0%84-%EB%8F%84%EB%9D%BC%ED%9D%AC%EC%9D%98-%EC%97%AC%EC%A0%95%EC%9D%84-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@eunz_juu/%EA%B8%80%EB%98%90-5%EA%B0%9C%EC%9B%94%EA%B0%84-%EB%8F%84%EB%9D%BC%ED%9D%AC%EC%9D%98-%EC%97%AC%EC%A0%95%EC%9D%84-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0</guid>
            <pubDate>Sun, 10 Dec 2023 12:53:02 GMT</pubDate>
            <description><![CDATA[<p align="center"><img src="https://velog.velcdn.com/images/eunz_juu/post/5bb9797a-eba0-463d-b5bc-ad402dc02c2d/image.png" width="50%">

<p>뭐라도 일을 벌려두지 않으면 아무것도 하지 않는 나를 위해... <br>이번에는 개발자 글쓰기 커뮤니티에 들어가는 선물을 주었다. 그 커뮤니티가 뭐냐면... 바로... [글또] (<a href="https://www.notion.so/zzsza/ac5b18a482fb4df497d4e8257ad4d516">https://www.notion.so/zzsza/ac5b18a482fb4df497d4e8257ad4d516</a>) 
5개월 간의 글또 생활을 시작하며, 글또를 통해 내가 이루고자 하는 작은 목표들을 적어보려고 한다. 
<em>(일단 적어놓으면 뭐라도 하겠지라는 생각으로... )</em></p>
<p>아래는 글또 OT 에서 운영자분께서 공유해주신 질문들이다.
꾸준히 글을 작성하기 위해서는** 단순히 이렇게 이렇게 해야겠다 라는 생각보다 나만의 <code>액션 아이템</code> 을 정하는 것**이 중요한 것 같다. 
따라서 아래의 질문들에 짧게 답을 하며 나만의 액션 아이템을 정해보겠다! </p>
<blockquote>
</blockquote>
<ul>
<li>글또가 끝난 <strong>5개월 후에 어떤 모습</strong>이길 바라나요?</li>
<li>그 모습을 달성하기 위해 <strong>무엇</strong>을 해야 할까요?</li>
<li>그걸 <strong>꾸준히</strong> 할 수 있는 방법은 무엇일까요?<blockquote>
</blockquote>
</li>
</ul>
<h3 id="5개월-후-나의-모습">5개월 후, 나의 모습</h3>
<ul>
<li>5개월 후의 나는 ⭐<strong>자발적으로 앉아서 블로그에 글을 쓰는 사람</strong>⭐ 이 되길 바란다.<br>
### 자발적으로 앉아서 글을 쓰려면 무엇을 해야할까?</li>
<li>자발적으로 글을 쓰기 위해서는 <strong>왜 글을 쓰는지에 대한 동기</strong>가 명확해야 한다.</li>
<li>따라서 글을 써야 하겠다는 동기를 꾸준하게 만들어 내야 한다.<br>
### 글을 쓸 동기를 꾸준히 만들어 내려면 어떻게 해야할까?</li>
<li><strong>새로운 경험을 많이 해야한다.</strong> </li>
<li>새로운 사람을 만나든, 새로운 경험을 하든, 현재 나의 생활에서는 겪지 못했던 &quot;무언가&quot; 를 통해 다른 사람들에게 공유하고 싶은 인사이트가 생기는 것 같다.<br>
### 그렇다면 글또에서 할 수 있는 새로운 경험이 무엇일까?</li>
<li><strong>같은 직군 사람들과 커피챗 하기</strong><ul>
<li>커피챗은 한달에... 2번...이상을 목표로 하겠다 <em>(이상 내향형 인간의 당차고 멋진 포부)</em></li>
</ul>
</li>
<li><strong>벽또 참여하기</strong><ul>
<li>벽또는 한달에 1번...? 클라이밍을 취미로 삼고 싶은데 팔힘이 없어서 너무 힘이 들어서 엄두가 안나긴 한다</li>
</ul>
</li>
<li><strong>관악또 참여하기</strong><ul>
<li>관악또는... 그래도 같은 지역이고 가까우니까 한달에 2번 이상....나가보겠다</li>
</ul>
</li>
<li><strong>단순한 지식 정리가 아닌, 나의 경험을 담은 트러블 슈팅 또는 기술 관련 글 작성하기</strong><ul>
<li>지금 프로젝트 진행 상 뭔가 새로운 기능을 많이 개발할 일이 없을 것 같긴 하지만, 기존 프로젝트 코드를 리팩토링한다거나 하는 경험 속에서 인사이트를 줄 만한 글을 작성할 수 있지 않을까 생각한다.</li>
</ul>
</li>
<li><strong>같은 조 분들의 글 출퇴근 시간에 읽기</strong><ul>
<li>글또에는 워낙 사람이 많다보니 관심가는 키워드가 있는 글만 다 읽더라도 많은 글이 있을 거라 예상된다. 일단은 같은 조에 배정된 분들이 작성해주시는 글부터 출퇴근길에 읽어보려고 한다.</li>
</ul>
</li>
</ul>
<br>
이상 글또에서 내가 할 수 있는 액션 아이템들을 (나름 자세히) 적어보았다.<br>
다음은 내가 개인적으로 글또를 하면서 얻고 싶은 부분을 적으려고 한다.<br><br>

<h3 id="1----글또-활동이-끝난-후에도-이어지는-인연-만들기">1.    글또 활동이 끝난 후에도 이어지는 인연 만들기</h3>
<p align="center"><img src="https://velog.velcdn.com/images/eunz_juu/post/8cfca06a-d83f-4aa6-b2c1-37ea4860d20d/image.png" width="50%"></p>

<p>나는 어떤 활동을 하든, <strong>활동이 끝나더라도 꾸준히 이어질 인연을 1명이라도 만드는 것을 목표로 한다</strong>.
그만큼 나는 새로운 경험을 통해 소중한 인연을 만드는 것을 중요하고 가치 있게 생각한다.
어떤 활동에서 만났느냐에 따라 사람들의 특징도 제각각이며, 나에게 미치는 영향들도 다양하므로, 많은 사람을 만나고 인연을 이어가는 것은 참 의미 있는 일 같다.</p>
<p>글또는 개발자들이 “글쓰기” 에 관심이 많은 개발자들이 같은 목표를 나누고 공유하기 위해 모인 커뮤니티이다.
글쓰기에 관심이 많은 만큼 자신의 생각을 머릿속로도, 글로서도 정리할 줄 아는 사람들이 많다는 생각이 든다. 
나보다 글 잘 쓰는 사람들이 정말 많은 것 같아서, 고수분들로부터 글을 짜임새 있게 작성하는 꿀팁들도 얻어가고 싶다.</p>
<p>새로운 사람 만나는 것이 조금은 낯설고 힘든 내향형 인간이지만... 최대한 많은 커피챗을 하도록 노력해보며 커리어와 관련된 이야기들을 많이 나눠보고 싶다.
커피챗을 통해 어떤 이야기를 할지, 내가 어떤 도움을 줄 수 있을지는 아직 잘 모르겠지만, 우선은 편한 마음으로 시도해봐야겠다. </p>
<h3 id="2-읽었을-때-재미있다고-느끼는-글을-써보기">2. 읽었을 때 재미있다고 느끼는 글을 써보기</h3>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/ae4f5813-9fba-4ddc-8873-1bf075db1946/image.png" alt=""></p>
<p>막연한 목표일 수 있고, 주관적일 수 있다. 
그렇지만 대다수 사람들이 읽을 때 재미있다는 느낌을 받는 글을 쓰고 싶다. 
지금까지 내가 읽으면서 재미있다고 느꼈던 글들의 특징을 정리해보자면, 아래와 같다</p>
<blockquote>
</blockquote>
<ol>
<li>제목이 재치있음</li>
<li>중간중간 웃긴 밈이나 짤들이 존재함</li>
<li>기승전결이 존재함</li>
<li>소제목을 달아 전개함으로써 짧은 호흡으로 글을 읽을 수 있음<blockquote>
</blockquote>
</li>
</ol>
<p>나 또한 이러한 특징을 잡아 글을 작성해보려고 한다. 
이렇게 작성하면 조금은 지루할 수 있는 기술적인 글도, 소소한 즐거움과 인사이트를 줄 수 있을 거라 기대한다. </p>
<p>또한 “재미” 만에 목매달면서, 글을 재미있게 꾸미는 것에 초점을 두지 않도록 주의해야겠다. 
정리하자면 나의 목표는 <strong>알맹이 있는 글을 쓰되, 잔잔한 미소가 지어질 수 있는 글을 쓰는 것</strong>이다 (?) 
적어보니 굉장히 어려운 목표인 듯하다.</p>
<br>
이렇게 글또를 시작하는 다짐글을 작성해보았는데, 액션 아이템을 구체적으로 잡으면 잡을 수록 더 실천하기에 편해질 것 같다는 생각이 들었다.<br>그리고 글을 쓰면서, 어떤 방향으로 글또 활동을 해나갈 수 있을 지 약간 감이 잡힌 것 같다!<br>
앞으로 5개월간 조금씩 변화해가는 나를 기록해보겠다 
(죽어가는 벨로그도 다시 살려내겠다...)<br><br>
<h3><p align ="center">아자아자 화이팅
<img src="https://velog.velcdn.com/images/eunz_juu/post/9c0f67cf-7b25-4f33-b28e-c71df35e1319/image.jpg" width="40%">
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트 코드 작성하기]]></title>
            <link>https://velog.io/@eunz_juu/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@eunz_juu/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 31 Dec 2022 06:31:00 GMT</pubDate>
            <description><![CDATA[<ul>
<li>좋은 단위 테스트란?
<img src="https://velog.velcdn.com/images/eunz_juu/post/0f776828-3c5f-4b18-b630-83726bd8b243/image.png" alt="">
<img src="https://velog.velcdn.com/images/eunz_juu/post/0e45f81a-5558-4174-a041-8f6b5dd321b4/image.png" alt=""></li>
</ul>
<ol>
<li>두가지 동작을 한번에 검증하는 테스트는 X</li>
</ol>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/929eee1d-e88f-4161-950b-3a4c400f080e/image.png" alt=""></p>
<ol start="2">
<li>1스트라이크로 판별한다() 라는 테스트 코드가 여러 개 발생하고, 중복된 코드가 발생</li>
</ol>
<p>@ParameterizedTest
@ValueSource</p>
<p>위의 어노테이션을 사용하면 한 테스트 메소드로 여러 케이스를 테스트할 수 있음</p>
<ol start="3">
<li><p>테스트코드에서 if문을 쓰지마라
제어할 수 없는 부분을 제거해라
ex) LocalDateTime 같은 경우</p>
</li>
<li><p>단일작업을 하는데 2개 메소드 호출은 잘못된 API 설계이다</p>
</li>
</ol>
<ul>
<li>테스트코드를 작성해보면서 설계의 부족한 점을 파악할 수 있음</li>
</ul>
<ol start="5">
<li><img src="https://velog.velcdn.com/images/eunz_juu/post/c5807da0-e6e6-4fe2-a734-5faea004c03a/image.png" alt=""></li>
</ol>
<p>-&gt; 리플렉션으로 PRIVATE 인 애를 외부로 꺼내서 테스트를 하게 되는 것임
-&gt; 너무 하고 싶으면 PRIVATE 이었던 부분을 클래스 분리해서 해라</p>
<ol start="6">
<li>랜덤함수를 사용하면 언제는 테스트 코드가 성공하고, 언제는 실패할 수 있음</li>
</ol>
<p>7.
<img src="https://velog.velcdn.com/images/eunz_juu/post/e5450a62-a64d-4d09-a2b2-0f7ec0d18178/image.png" alt=""></p>
<ul>
<li>태스트 코드 파악하려고 @BeforeEach 까지 갓다와야 함</li>
<li><blockquote>
<p>좋은단위테스트는 그 테스트 하나만으로도 무슨 테스트인지 알아야 함</p>
</blockquote>
</li>
<li><blockquote>
<p>데이터베이스를 세팅한다거나 개발환경에 대한 세팅을 할 때 비포이치를 사용하는 게 좋음 (데이터 세팅이 아니라)</p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/11c8b7e5-736d-48d6-9cc9-0dd10ce902f8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/6834531e-0588-4329-97ff-c9231297ce80/image.png" alt="">
<img src="https://velog.velcdn.com/images/eunz_juu/post/c7e8a82c-4629-43df-8329-1d3369ced07e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/eunz_juu/post/a14206a6-3f27-4a18-8b69-a09da1c8f4bd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유스콘22]]></title>
            <link>https://velog.io/@eunz_juu/%EC%9C%A0%EC%8A%A4%EC%BD%9822</link>
            <guid>https://velog.io/@eunz_juu/%EC%9C%A0%EC%8A%A4%EC%BD%9822</guid>
            <pubDate>Sat, 31 Dec 2022 06:00:42 GMT</pubDate>
            <description><![CDATA[<p>객체지향 : 변경을 캡슐화한 객체들이 메시지를 통해 협력하는 프로그래밍</p>
<ol>
<li>메시지</li>
</ol>
<ul>
<li>객체들이 <strong>협력</strong>하기 위해 사용할 수 있는 유일한 <strong>의사소통 수단</strong></li>
<li>수신자는 메시지를 처리할 <strong>책임</strong>을 다하기 위해, 메시지를 처리할 방법인 메소드를 선택한다 </li>
</ul>
<p>협력</p>
<ul>
<li>무엇인가를 <strong>요청</strong></li>
<li><strong>메시지</strong> 전송이 유일한 커뮤니케이션 수단</li>
</ul>
<p>책임</p>
<ul>
<li>협력에서 수행하는 행동
ex) product 는 재고를 감소시키는 책임을 함</li>
</ul>
<p>메시지 
condition.isSatisfiedBy(screening);
 수신자      오퍼레이션명    인자</p>
<ul>
<li>GRASP</li>
<li><em>책임할당*</em>을 위한 소프트웨어 패턴</li>
</ul>
<p>책임을 수행하는데 필요한 정보를 갖고 잇는 객체에게 책임을 지게 한다
-&gt; iNFORMATION EXPERT 
-&gt; 높은 <strong>응집도</strong>, 낮은 <strong>결합도</strong>
-&gt; 유지 보수 쉬움</p>
<p>응집도 : 변경 발생 시 모듈 내부에서 발생하는 변경의 정도
<img src="https://velog.velcdn.com/images/eunz_juu/post/d8ae0374-c48c-4ee3-9bfb-13df15a6a339/image.png" alt=""></p>
<ul>
<li>높은 응집도 유지해라</li>
<li>복잡성 관리할 수 있는 수준으로 유지해라</li>
</ul>
<ul>
<li><p>결합도</p>
<ul>
<li><strong>다른 모듈</strong>에 대해 <strong>얼마나 많은 지식</strong>을 가졌는지 나타내는 척도
<img src="https://velog.velcdn.com/images/eunz_juu/post/be46421e-228d-4267-be9b-1d99eee935c6/image.png" alt=""></li>
</ul>
</li>
<li><p>낮은 결합도를 유지해라</p>
</li>
<li><p>의존성 낮추고, 변화의 영향을 줄이고, 재사용성을 증가시킨다</p>
</li>
</ul>
<p>** 오퍼레이션명
<img src="https://velog.velcdn.com/images/eunz_juu/post/f57ba9ab-2a28-43ba-b02e-6abb03361296/image.png" alt=""></p>
<ul>
<li>협력과 관련된 의도만을 포함하도록 메시지 형성</li>
<li>이름은 송신자 입장에서 정해야 함</li>
<li>내부에서 어떤 일을 하는지 알도록 정하면 안됨.</li>
</ul>
<p>외부에서 객체에게 명령하듯이 메시지를 전송해야 한다</p>
<p>메시지 정리</p>
<ul>
<li>직접적인 데이터 공유 X, 메시지로 객체의 자율성 보장</li>
<li>메시지 만들 때, <strong>협력/책임/역할</strong>을 고려</li>
</ul>
<ol start="2">
<li>캡슐화</li>
</ol>
<ul>
<li>변경될 수 잇는 어떤 것이라도 감추는 것!!!!</li>
<li>내부 구현 변경으로, 외부 객체가 영향받으면 캡슐화를 위반한 것</li>
<li>변화와 불안정성이 다른 요소에 나쁜 영향 미치지않도록 방지</li>
</ul>
<ol>
<li>데이터 캡슐화 =&gt; 데이터 은닉도 캡슐화의 일부긴 한데 이게 전부는 아님</li>
</ol>
<ul>
<li>private 변수</li>
<li>public 메소드로 변수 값 변경</li>
</ul>
<ol start="2">
<li>메소드 캡슐화</li>
</ol>
<ol start="3">
<li>객체 캡슐화 -&gt; 컴포지션 (합성)</li>
</ol>
<p>-&gt; 컴파일 타임에만 고정</p>
<ol start="4">
<li>서브타입 캡슐화 -&gt; 다형성</li>
</ol>
<ul>
<li>overloading</li>
<li>dynamic binding (동적바인딩)</li>
<li><blockquote>
<p>실행될 메소드를 런타임에 결정하는 방식</p>
</blockquote>
</li>
<li><blockquote>
<p>변하는 것이 있다면 타입 분리하고, 변화하는 것을 각 타입의 책임으로 할당하여 변화에 유연하게 대처하자!</p>
</blockquote>
</li>
</ul>
<p>역할 : 교체할 수 있는 책임의 집합
기프트카드 + 포인트 -&gt; 머니라는 역할 부여</p>
<p>3.상속</p>
<ul>
<li>DRY 원칙 : 코드 안에 중복 존재해서는 안된다</li>
<li>코드 재사용이 목적이면 안되고, <strong>타입 계층을 구현</strong>하는 게 목표여야 함</li>
<li><blockquote>
</blockquote>
</li>
<li>상속은 클래스간의 결합도를 높임</li>
</ul>
<ol start="4">
<li>추상화</li>
</ol>
<ul>
<li>추상적인 존재에 의존해야 결합도를 낮출 수 잇음</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>