<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jeongyeon_kim.log</title>
        <link>https://velog.io/</link>
        <description>Backend Developer👩🏻‍💻</description>
        <lastBuildDate>Wed, 23 Jul 2025 07:23:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jeongyeon_kim.log</title>
            <url>https://velog.velcdn.com/images/jeongyeon_kim/profile/bab055e5-ffac-4960-9089-a814741a72a3/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jeongyeon_kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jeongyeon_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초] 2장. 개략적인 규모 추정]]></title>
            <link>https://velog.io/@jeongyeon_kim/%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-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/@jeongyeon_kim/%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-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>Wed, 23 Jul 2025 07:23:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개략적인 규모 추정(back-of-the-envelope estimation)은 보편적으로 통용되는 성능 수치상에서 사고 실험(thought experiments)을 행하여 추정치를 계산하는 행위로서, 어떤 설계가 요구사항에 부합할 것인지 보기 위한 것
-구글 senior fellow 제프 딘(Jeff Dean)</p>
</blockquote>
<h2 id="2의-제곱수">2의 제곱수</h2>
<table>
<thead>
<tr>
<th>2의 x제곱</th>
<th>근사치</th>
<th>이름</th>
<th>축약형</th>
</tr>
</thead>
<tbody><tr>
<td>10</td>
<td>1천(thousand)</td>
<td>1킬로바이트(Kilobyte)</td>
<td>1KB</td>
</tr>
<tr>
<td>20</td>
<td>1백만(million)</td>
<td>1메가바이트(Megabyte)</td>
<td>1MB</td>
</tr>
<tr>
<td>30</td>
<td>10억(billion)</td>
<td>1기가바이트(Gigabyte)</td>
<td>1GB</td>
</tr>
<tr>
<td>40</td>
<td>1조(trillion)</td>
<td>1테라바이트(Terabyte)</td>
<td>1TB</td>
</tr>
<tr>
<td>50</td>
<td>1000조(quadrillion)</td>
<td>1페타바이트(Petabyte)</td>
<td>1PB</td>
</tr>
</tbody></table>
<h2 id="모든-프로그래머가-알아야-하는-응답지연-값">모든 프로그래머가 알아야 하는 응답지연 값</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/2379ac85-5747-4946-8a1a-5bc1bcfe50ce/image.png" alt=""></p>
<ul>
<li>메모리는 빠르지만 디스크는 아직도 느리다</li>
<li>디스크 탐색은 가능한 한 피하라</li>
<li>단순한 압축 알고리즘은 빠르다</li>
<li>데이터를 인터넷으로 전송하기 전에 가능하면 압축하라</li>
<li>데이터 센터는 보통 여러 지역에 분산되어 있고, 센터들 간에 데이터를 주고받는 데는 시간이 걸린다</li>
</ul>
<h2 id="가용성에-관계된-수치들">가용성에 관계된 수치들</h2>
<p><strong>고가용성(high availability)</strong>: 시스템이 오랜 시간 동안 지속적으로 중단 없이 운영될 수 있는 능력</p>
<p><strong>SLA(Service Lavel Agreement)</strong>: 서비스 사업자와 고객 간에 맺어진 합의로, 서비스 사업자가 제공하는 서비스의 가용시간(uptime)이 기술되어 있다.</p>
<table>
<thead>
<tr>
<th>가용률</th>
<th>하루당 장애시간</th>
<th>주당 장애시간</th>
<th>개월당 장애시간</th>
<th>연간 장애시간</th>
</tr>
</thead>
<tbody><tr>
<td>99%</td>
<td>14.40분</td>
<td>1.68시간</td>
<td>7.31시간</td>
<td>3.65일</td>
</tr>
<tr>
<td>99.9%</td>
<td>1.44분</td>
<td>10.08분</td>
<td>43.83분</td>
<td>8.77시간</td>
</tr>
<tr>
<td>99.99%</td>
<td>8.64초</td>
<td>1.01분</td>
<td>4.38분</td>
<td>52.60분</td>
</tr>
<tr>
<td>99.999%</td>
<td>864.00밀리초</td>
<td>6.05초</td>
<td>26.30초</td>
<td>5.26분</td>
</tr>
<tr>
<td>99.9999%</td>
<td>86.40밀리초</td>
<td>604.80밀리초</td>
<td>2.63초</td>
<td>31.56초</td>
</tr>
</tbody></table>
<h2 id="팁">팁</h2>
<p>개략적인 규모 추정과 관계된 면접에서 가장 중요한 것은 문제 해결 능력</p>
<ul>
<li>근사치를 활용한 계산: 면접장에서 정확한 계산을 하기 어려우므로 적절한 근사치를 활용하여 시간 절약</li>
<li>가정들은 적어두기</li>
<li>단위 꼭 붙이기</li>
<li>많이 출제되는 개략적 규모 추정 문제는 QPS, 최대 QPS, 저장소 요구량, 캐시 요구량, 서버 수 등 추정하는 것</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[가상 면접 사례로 배우는 대규모 시스템 설계 기초] 1장. 사용자 수에 따른 규모 확장성]]></title>
            <link>https://velog.io/@jeongyeon_kim/%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%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/@jeongyeon_kim/%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%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>Wed, 16 Jul 2025 14:19:58 GMT</pubDate>
            <description><![CDATA[<h2 id="단일-서버">단일 서버</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/e128cc17-8538-4c14-b403-80387bd96df4/image.png" alt=""></p>
<ol>
<li>사용자는 도메인 이름(api.mysite.com)을 이용해 웹사이트에 접속한다.
웹사이트에 접속하기 위해 도메인 이름을 DNS에 질의하여 IP 주소로 변환한다.</li>
<li>DNS 조회 결과로 IP 주소를 반환한다.</li>
<li>IP 주소로 HTTP 요청이 전달된다.</li>
<li>요청을 받은 웹 서버는 HTML이나 JSON을 응답한다.</li>
</ol>
<h2 id="데이터베이스">데이터베이스</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/ef90e10b-ccca-40e4-92f3-34f3ece9e7e3/image.png" alt=""></p>
<p>웹/모바일 트래픽 처리 서버(웹 계층)와 데이터베이스 서버(데이터 계층) 분리를 하여 독립적으로 확장 가능</p>
<p><strong>관계형 데이터베이스(RDBMS)</strong></p>
<ul>
<li>MySQL, Oracle, PostgreSQL</li>
<li>자료를 테이블, 열, 칼럼으로 표현</li>
<li>여러 테이블에 있는 데이터를 조인(join)할 수 있음</li>
</ul>
<p><strong>비관계형 데이터베이스(NoSQL)</strong></p>
<ul>
<li>CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB</li>
<li>키-값 저장소(key-value store), 그래프 저장소(graph store), 칼럼 저장소(column store), 문서 저장소(document store)</li>
<li>조인 연산 지원하지 않음</li>
<li>다음과 같은 경우에 사용하는 것이 좋음<ul>
<li>아주 낮은 응답 지연시간(latency) 요구하는 경우</li>
<li>다루는 데이터가 비정형 데이터인 경우</li>
<li>데이터(JSON, YAML, XML 등)를 직렬화 혹은 역직렬화 할 수 있기만 하면 되는 경우</li>
<li>아주 많은 양의 데이터를 저장할 필요가 있는 경우</li>
</ul>
</li>
</ul>
<h2 id="수직적-규모-확장-vs-수평적-규모-확장">수직적 규모 확장 vs 수평적 규모 확장</h2>
<p><strong>수직적 규모 확장(Scale Up)</strong></p>
<ul>
<li>고사양 자원(더 좋은 CPU, 더 많은 RAM 등)을 추가하는 행위</li>
<li>서버로 유입되는 트래픽 양이 적을 때 좋은 선택</li>
<li>하드웨어의 한계가 있음</li>
<li>자동복구(failover) 방안이나 다중화(redundancy) 방안을 제시하지 못해 서버에 장애가 발생하면 웹사이트/앱이 완전히 중단됨</li>
</ul>
<p><strong>수평적 규모 확장(Scale Out)</strong></p>
<ul>
<li>더 많은 서버를 추가하여 성능을 개선하는 행위</li>
<li>대규모 애플리케이션 지원하는데 유리</li>
</ul>
<h3 id="로드밸런서">로드밸런서</h3>
<p>로드밸런서는 부하 분산 집합(load balancing set)에 속한 웹 서버들에게 트래픽을 고르게 분산하는 역할을 한다.</p>
<p>사용자는 로드밸런서의 공개 IP 주소로 접속하고, 로드밸런서와 웹서버 간의 통신에는 사설 IP 주소가 이용된다.
사설 IP 주소는 같은 네트워크에 속한 서버 사이의 통신에만 쓰일 수 있는 IP 주소로, 인터넷을 통해서는 접속할 수 없다.</p>
<p>부하 분산 집합에 웹 서버를 추가하면 장애 자동복구(failover) 문제가 해소되고, 웹 계층의 가용성(availability)은 향상된다.</p>
<h3 id="데이터베이스-다중화">데이터베이스 다중화</h3>
<p>주(master) - 부(slave) 관계를 설정하고, 데이터의 원본은 master에, 사본은 slave에 저장한다.</p>
<p>쓰기 연산은 master에서만 지원한다. slave는 master로 부터 사본을 전달 받으며, 읽기 연산만 지원한다.
대부분의 애플리케이션은 읽기 연산의 비중이 쓰기 연산의 비중보다 높기 때문에 통상적으로 slave의 수가 master의 수보다 많다.</p>
<p><strong>데이터베이스 다중화의 장점</strong></p>
<ul>
<li>더 나은 성능: 쓰기 연산은 master, 읽기 연산은 slave로 전달되기 때문에 병렬로 처리될 수 있는 query의 수가 늘어나 성능이 향상된다.</li>
<li>안정성(reliability): 데이터를 지역적으로 떨어진 여러 장소에 다중화시켜 놓으면 자연 재해등의 이유로 데이터베이스 서버의 일부가 파괴되어도 데이터가 보존된다.</li>
<li>가용성(availability): 데이터를 여러 지역에 복제해 둠으로써, 하나의 데이터베이스 서버에 장애가 발생해도 다른 서버에서 데이터를 가져와 계속 서비스할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/04e6814d-caee-4148-ada1-c565f0f92196/image.png" alt=""></p>
<h2 id="캐시">캐시</h2>
<p><strong>캐시 계층</strong>
데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠르다.
별도의 캐시 계층을 둠으로써 성능이 개선되고, 데이터베이스의 부하를 줄일 수 있고, 캐시 계층의 규모를 독립적으로 확장시키는 것도 가능하다.</p>
<p>캐시할 데이터의 종류, 크기, 액세스 패턴에 맞는 캐시 전략을 선택할 수 있다.</p>
<p><strong>캐시 사용 시 유의할 점</strong></p>
<ul>
<li>데이터 갱신은 자주 일어나지 않지만 참조는 빈번하게 일어나는 경우 캐시 사용에 유리</li>
<li>캐시는 데이터를 휘발성 메모리에 두므로 영속적으로 보관할 데이터를 캐시에 두는 것은 바람직하지 않음</li>
<li>적절한 만료(expire) 정책을 설정하는 것이 좋음</li>
<li>데이터 저장소의 원본과 캐시 내의 사본이 같은지 여부인 일관성(consistency) 고려 필요</li>
<li>캐시 서버를 한 대만 두는 경우 단일 장애 지점(Single Point of Failure, SPOF)이 되어버릴 가능성이 있기 떄문에 여러 지역에 걸쳐 캐시 서버 분산시키는 것이 좋음</li>
<li>캐시 메머리 과할당(overprovision)을 통해 캐시 메모리가 부족해지는 문제 해결</li>
<li>적절한 캐시 데이터 방출(eviction) 정책 필요</li>
</ul>
<h2 id="콘텐츠-전송-네트워크cdn">콘텐츠 전송 네트워크(CDN)</h2>
<p>CDN은 정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크이다.
이미지, 비디오, CSS, JavaScript 파일 등을 캐시할 수 있다.</p>
<ol>
<li>사용자 A가 CDN 서비스 사업자가 제공한 이미지 URL을 통해 이미지에 접근한다.</li>
<li>CDN 서버의 캐시에 해당 이미지가 없는 경우, 서버는 원본 서버에 파일을 요청하여 가져온다.</li>
<li>원본 서버가 파일을 CDN 서비스에 반환한다. 이때 응답 헤더에는 캐시될 수 있는 기간인 TTL이 들어있다.</li>
<li>CDN 서버는 파일을 캐시하고 사용자 A에게 반환한다. 이미지는 TTL이 끝날 때까지 캐시된다.</li>
<li>사용자 B가 같은 이미지에 대한 액세스 요청을 CDN 서버에 전송한다.</li>
<li>만료되지 않은 이미지에 대한 요청은 캐시를 통해 처리된다.</li>
</ol>
<p><strong>CDN 사용 시 고려해야 할 사항</strong></p>
<ul>
<li>비용: CDN으로 들어가고 나가는 데이터 전송 양에 따라 요금을 내게 되므로, 자주 사용되지 않는 콘텐츠를 캐싱하는 것은 이득이 크지 않음</li>
<li>적절한 만료 시한 설정: 너무 길지도, 짧지도 않은 TTL 설정 필요</li>
<li>CDN 장애에 대한 대처 방안: CDN 자체가 죽었을 경우, 해당 문제를 감지하여 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트 구성 필요</li>
<li>콘텐츠 무효화 방법<ul>
<li>CDN 서비스 사업자가 제공하는 API 이용</li>
<li>콘텐츠의 다른 버전을 서비스하도록 오브젝트 버저닝 이용</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/af10a699-4c5c-427c-93d0-2eb7cc15630c/image.png" alt=""></p>
<h2 id="무상태stateless-웹-계층">무상태(stateless) 웹 계층</h2>
<p>상태 정보에 의존적인 아키텍처는 서버에 사용자의 상태 정보가 보관되어 있기 때문에 같은 클라이언트로부터의 요청을 항상 같은 서버로 전송해야 하는 문제점이 발생한다.
대부분의 로드밸런서가 이를 지원하기 위해 고정 세션(sticky session) 기능을 제공하고 있지만, 이는 로드밸런서에 부담을 준다.</p>
<p>무상태 아키텍처는 상태 정보를 공유 저장소에 저장하여 필요한 경우 가져와서 사용한다.
상태 정보는 웹 서버로부터 물리적으로 분리되어 있어 안정적이며, 규모 확장이 쉽다.</p>
<p>상태 정보가 저장되는 공유 저장소르 관계형 데이터베이스일 수도 있고, Memcached/Redis 같은 캐시 시스템일 수도 있고, NoSQL일 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/0ed997ea-0a8c-49d4-bc42-44b7b212f01a/image.png" alt=""></p>
<h2 id="데이터-센터">데이터 센터</h2>
<p>지리적 라우팅(geoDNS-routing, geo-routing): 장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내됨
geoDNS: 사용자의 위치에 따라 도메인 이름을 어떤 IP 주소로 변환할지 결정할 수 있도록 해주는 DNS 서비스</p>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/2229c9ff-689c-457b-b816-cc77ba3d7130/image.png" alt=""></p>
<p>DC1(US-East), DC2(US-West)로 트래픽이 나뉘어 전달된다고 할 때,
DC2(US-West)에서 장애가 발생하면 모든 트래픽이 DC1(US-East)로 전송된다.</p>
<p><strong>다중 데이터센터 아키텍처를 만들기 위한 기술적 난제</strong></p>
<ul>
<li>트래픽 우회: 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 함. GeoDNS는 사용자에게서 가장 가까운 데이터 센터로 트래픽을 보냄</li>
<li>데이터 동기화(synchronization): 데이터를 여러 데이터 센처에 걸쳐 다중화하는 것 필요</li>
<li>테스트와 배포(deployment):자동화된 배포 도구는 모든 데이터 센터에 동일한 서비스가 설치되도록 하는데 중요한 역할</li>
</ul>
<h2 id="메시지-큐">메시지 큐</h2>
<p>메시지 큐: 메시지의 무손실(durability, 메시지 큐에 보관된 메시지는 소비자가 꺼낼 때까지 안전하게 보관됨)을 보장하는, 비동기 통신(asynchronous communication)을 지원하는 컴포넌트</p>
<p>메시지 큐를 이용하면 서버 간 느슨한 결합(loosely coupled)이 되어 규모 확장성이 보장되어야 하는 안정적 애플리케이션 구성에 유리하다.
또한 생산자와 소비자는 각기 독립적으로 확장 가능하다.</p>
<h2 id="로그-메트릭-그리고-자동화">로그, 메트릭 그리고 자동화</h2>
<ul>
<li><p>로그: 로그를 단일 서비스로 모아주는 도구를 활용해 편리하게 에러 로그 모니터링</p>
</li>
<li><p>메트릭: 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수도 있고, 시스템의 현재 상태를 손쉽게 파악 가능.</p>
<ul>
<li>호스트 단위 메트릭: CPU, 메모리, 디스크 I/O에 관한 메트릭</li>
<li>종합(aggregated) 메트릭: 데이터베이스 계층의 성능, 캐시 계층의 성능</li>
<li>핵심 비즈니스 메트릭: 일별 능동 사용자(DAU), 수익(revenue), 재방문(retention)</li>
</ul>
</li>
<li><p>자동화: 시스템이 크고 복잡해지면 생산성을 높이기 위해 자동화 도구 이용. 지속적 통합(Continuous Integration) 도구를 통해 빌드, 테스트, 배포 등의 절차 자동화 가능</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/17e0dc72-8987-426c-8a9c-ba30101b4a75/image.png" alt=""></p>
<h2 id="데이터베이스의-규모-확장">데이터베이스의 규모 확장</h2>
<p><strong>수직적 확장(Scale Up)</strong></p>
<ul>
<li><p>기존 서버에 더 많은, 고성능의 자원(CPU, RAM, 디스크 등)을 증설하는 방법</p>
</li>
<li><p>수직적 확장의 단점</p>
<ul>
<li><p>데이터베이스 서버 하드웨어에는 한계가 있으므로 무한대로 자원을 증설할 수 없다.</p>
</li>
<li><p>SPOF로 인한 위험성이 크다.</p>
</li>
<li><p>고성능 서버로 갈 수록 비용이 많이 든다.</p>
</li>
</ul>
</li>
</ul>
<p><strong>수평적 확장(Scale Out)</strong></p>
<ul>
<li><p>샤딩(sharding)이라고도 부름</p>
</li>
<li><p>더 많은 서버를 추가함으로써 성능 향상</p>
</li>
<li><p>샤딩: 데이터베이스를 샤드(shard)라고 부르는 작은 단위로 분할하는 기술</p>
</li>
<li><p>모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복 없음</p>
</li>
<li><p>데이터를 고르게 분할할 수 있는 적절한 샤딩 키(sharding key)를 정해 데이터 조회나 변경 처리의 효율을 높임</p>
</li>
<li><p>샤딩 도입시 풀어야할 문제</p>
<ul>
<li>데이터의 재 샤딩(resharding): 데이터가 너무 많아져서 하나의 샤드로 감당하기 어렵거나, 샤드 소진 현상이 발생하면 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치 해야함</li>
<li>유명인사(celebrity) 문제: 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제</li>
<li>조인과 비정규화: 하나의 데이터베이스를 여러 샤드로 쪼개면, 여러 샤드에 걸친 데이터를 조인하기 힘들어짐. 데이터베이스 비정규ㄹ화를 통해 하나의 테이블에서 질의가 수행될 수 있도록 하여 해결</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/d5a63bc8-1db7-4692-883f-774d995f4215/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 WIL] 4주차 회고]]></title>
            <link>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 20 Apr 2025 13:47:55 GMT</pubDate>
            <description><![CDATA[<h2 id="1-내가-구현한-흐름은-어떻게-구성되었고-어떤-기준으로-구조를-나눴나요">1. 내가 구현한 흐름은 어떻게 구성되었고, 어떤 기준으로 구조를 나눴나요?</h2>
<blockquote>
<p>Controller -&gt; Facade -&gt; Service -&gt; Repository &lt;- RepositoryImpl</p>
</blockquote>
<p>Layered Architecture + Clean Architecture 관점에서 접근하여 단방향 의존성을 지키며 구현했다.</p>
<h2 id="2-통합-테스트를-어떤-방식으로-구성했나요-테스트를-통해-확인하고-싶었던-건-무엇이었나요">2. 통합 테스트를 어떤 방식으로 구성했나요? 테스트를 통해 확인하고 싶었던 건 무엇이었나요?</h2>
<p>Controller, Facade, Service에 대해서 통합 테스트를 진행하였다.
이전 주차에 작성해둔 단위 테스트를 바탕으로 비즈니스 흐름 상 각 단위가 잘 조립되어 작동하는지에 대해 집중하며 통합테스트를 구현하였다.</p>
<p>시간 부족 이슈로 성공하는 케이스에 대해서만 작성한 것이 아쉬움이 남는다.
5주차를 진행하면서 예외 케이스에 대해서 추가로 작성해보는 것도 좋을 것 같다.</p>
<h2 id="3-db-성능이나-동시성-문제를-어떻게-분석했고-어떤-해결-방안을-고민했나요">3. DB 성능이나 동시성 문제를 어떻게 분석했고, 어떤 해결 방안을 고민했나요?</h2>
<h4 id="예상했던-느린-조회-기능">예상했던 느린 조회 기능</h4>
<ul>
<li>인기 상품 조회</li>
<li>상품 목록 조회</li>
<li>사용자 보유 쿠폰 조회 -&gt; user_id 인덱스 처리</li>
<li>주문 시 재고 조회 -&gt; PK로 조회(이미 인덱스)</li>
<li>결제 시 잔액 조회 -&gt; user_id 인덱스 처리</li>
<li>결제 시 주문 정보 조회 -&gt; PK로 조회(이미 인덱스)</li>
</ul>
<p>인기 상품 조회의 경우 인덱스를 걸기 전에 full scan을 하고 있었다.
(product_id, created_at, sales)로 복합 인덱스를 걸고 성능을 측정한 결과 성능 개선 가능성은 확보되었으나, 실행시간이 줄어들지는 않았다.
조회된 결과를 캐시 테이블에 저장하는 방법을 통해 추가로 성능 최적화를 진행할 수 있을 것이다.</p>
<p>상품 목록 조회의 경우 full scan을 하며 인덱스를 걸 수 없기 때문에
추후 검색 조건을 추가하거나 페이징을 추가해 성능을 향상시킬 수 있을 것이다.</p>
<p>사유에 대해 좀 더 디깅을 해서 보고서를 작성했으면 좋았을 것 같다는 아쉬움이 있다.</p>
<h2 id="4-이번-과정을-통해-느낀-점은-무엇인가요">4. 이번 과정을 통해 느낀 점은 무엇인가요?</h2>
<p>DB 성능을 고민하면서 DB에 대한 개념과 경험이 부족하다고 느꼈다.
DB 성능 최적화 과정을 경험한 적이 전무하다보니 시작을 어떻게 해야할지부터 어려웠다.
성능에 대한 고민을 하면서 최적화할 수 있는 방법을 디깅해봐야겠다.</p>
<p>이번주차는 3주차 피드백을 바탕으로 리팩토링 하는데 시간을 많이 쓰면서 4주차 과제에 힘을 좀 덜 들였는데,
5주차부터는 시간 분배를 잘해서 해당 주차 과제에 힘을 써야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 WIL] 3주차 회고]]></title>
            <link>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 13 Apr 2025 12:43:31 GMT</pubDate>
            <description><![CDATA[<h2 id="항해-3주차는-힘들었다">항해 3주차는 힘들었다.</h2>
<p>지난주차에 설계한 것을 바탕으로 Clean + Layered Architecture를 적용하여 구현을 하였다.
그런데..
하나 Pass, 하나 Fail을 받게 되었다. 
도메인이 아닌 데이터 중심적인.. 테이블 그 잡채의 구현을 했던 것이다!!
Fail 받은 내용 바탕으로 리팩토링 해야지..
4주차는 모두 Pass 받을거야~!!!!!</p>
<h2 id="clean--layered-architecture가-뭔데">Clean + Layered Architecture가 뭔데..?</h2>
<p>지금까지 개발하면서 Layered Architecture 말고 다른 방법을 써서 개발해본 적이 없다.
그래서 이번 항해 이커머스 플젝을 진행하면서 클린 아키텍처를 제대로 도입해보기로 했다.
그런데 쉽지만은 않았다.</p>
<h3 id="클린-아키텍처-facade">클린 아키텍처? Facade?</h3>
<p>클린 아키텍처 적용은 정말 낯설었다.
일단 interface - application - domain - infrastructure 계층 구조도 익숙치 않았다.
게다가 Repository 구현체 없이 코드를 작성하라는 과제에 맞추어, JPA도 완전히 배제하고 코드를 작성하다보니 시간도 많이 걸리고, 어려웠다.
익숙하지 않았다.</p>
<h3 id="도메인-엔티티">도메인? 엔티티?</h3>
<p>DDD는 말만 들어봤지 실제로 해본 적도 없고, 누가 한 것을 본 적도 없다.
그리고 일단 &#39;도메인 = 엔티티&#39; 라고 생각하고 있었다. JPA를 쓰면서 늘 데이터 중심적이 사고를 해왔으니까.</p>
<p>이번 3주차를 진행하기 전에 도메인과 엔티티에 대해서 많이 찾아봤다.
도메인과 엔티티가 같다고 생각했었던 나에게 큰 전환점이 되었다.</p>
<p>공부를 했지만 실제 구현에서 도메인을 제대로 구현하기란 쉽지 않았다.
내 나름대로 객체지향적으로, 책임을 분리하여서 구현했는데 아니었던 것이다.</p>
<h2 id="3주차를-통해-느낀점">3주차를 통해 느낀점</h2>
<p>이번주차에 Fail을 받으면서 많은 것을 느꼈다.
처음엔 Fail을 받은 것이 마냥 아쉽게만 느껴졌었는데, 내각 항해를 하는 이유는 배움과 성장을 위한 것이라는 생각을 가지고 다시 Fail을 바라보니 다르게 느껴졌다.
오히려 Fail을 받아서 더 배운 것이 있다고 생각이 들었다.</p>
<p>아직 객체지향적으로, 도메인 주도적인 설계를 하는 것이 어렵다.
도메인에 대한 고민과 공부는 지속되어야 할 것 같다.</p>
<p>이번 과제를 하면서 이틀은 밤을 새다시피 했는데, 다음주차 부터는 시간 관리에 좀 더 신경을 써야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 WIL] 2주차 회고]]></title>
            <link>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 07 Apr 2025 06:11:12 GMT</pubDate>
            <description><![CDATA[<h2 id="e-commerce-설계">E-Commerce 설계</h2>
<p>2주차는 이커머스/콘서트 예매 중 하나의 시나리오를 정하고 이를 설계하는 것이다.
우리 팀은 이커머스로 정했고, 학습메이트의 조언에 따라 팀원 모두가 같이 설계를 진행하였다.</p>
<p>설계 과정은 쉬운듯 어려웠다.
팀원이 모두 같이 진행하다보니 내가 미처 생각하지 못했던 부분을 다른 팀원이 의견을 내면서 설계하는 시야가 점점 넓어졌던 것 같다.</p>
<p>나는 프로젝트를 진행하는게 오랜만이라 처음 배우는 것 같은 느낌이 들었다.
내가 설계를 맞게 하고 있는건지, 혹은 이런 상황에 이런 설계가 맞는 것인지 확신이 들지 않았다.</p>
<h2 id="kpt">KPT</h2>
<h3 id="keep">Keep</h3>
<p>항해 과정에 적응해가면서 퇴근 후 시간을 의미없이 흘려보내지 않고, 항해 과제를 진행한 것!!</p>
<h3 id="problem">Problem</h3>
<p>여전히 설계에 대한 많은 의심이 남아있다. 구현 하면서 설계를 바꾸게 될 상황도 오게 될 것 같은데 대응을 잘 해야겠다.</p>
<h3 id="try">Try</h3>
<p>아키텍처에 관련된 공부해보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 WIL] 1주차 회고]]></title>
            <link>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jeongyeon_kim/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-WIL-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 29 Mar 2025 05:08:12 GMT</pubDate>
            <description><![CDATA[<h2 id="항해-플러스를-시작하게-된-이유">항해 플러스를 시작하게 된 이유</h2>
<p>항해 플러스를 시작하기 전에는 두려움이 많았다.
회사를 다니고 있지만 전형적인 운영 업무만 하다보니 개발은 물론 고급 기술 또한 사용할 기회가 없었다.
사실 이것도 핑계이다. 업무 핑계를 대지 않고 스스로 공부해보면 기회는 얼마든지 있었는데 내가 하지 않았다.</p>
<p>나는 Comfort zone에 있는 것에 굉장한 안정감을 느끼고 벗어나고 싶어하지 않았던 것 같다.
대기업에 다니고 있고 월급이 계속해서 나온다는 것 때문인지 무언가에 열정을 느끼지도 않았고, 도전하려 하지도 않았다.</p>
<p>직장인이 되고 난 후에 내 시간이 부족해지는 것이 싫어서 출퇴근을 빨리 하기로 했다.
그런데 지금 생각해보면 내가 원해서 만든 내 시간을 의미없이 허투루 보냈다.
그렇게 시간이 쌓여서 벌써 물경력 2년차가 되었다.</p>
<p>이제는 이 모든 상황이 더이상 지속되면 안된다고 생각해 항해를 도전하게 되었다.</p>
<h2 id="1주차를-보내고">1주차를 보내고</h2>
<h3 id="어-나-퇴근하고-시간-많네">어? 나 퇴근하고 시간 많네</h3>
<p>과제를 하며 1주차를 보내고 나니 퇴근 후의 시간은 많았다는 것을 가장 먼저 느꼈다.
퇴근 하고 저녁을 먹고 쉬다보면 하루가 금방 끝난다고 생각했는데, 그 시간에 과제를 하니 하루가 길게 느껴졌다.</p>
<h3 id="재미있어">재미있어!!!</h3>
<p>회사에서 fade out 예정이고 안정적인 시스템을 운영 업무 하다보니 사실 코드 짤 일이 거의 없다.
1주차를 진행하면서 다시 &#39;개발&#39;을 하면서 새로운 것을 공부하고 그걸 코드로 구현하는 것에 재미를 느꼈다.</p>
<h3 id="j-같은-생활하기">J 같은 생활하기</h3>
<p>아직은 일주일 계획이 완전히 잡힌 것 같진 않다.
2주차 때는 실제 나의 스케줄을 기록해보고 항해를 하는 동안 고정된 스케줄을 만들자.</p>
<h3 id="tdd는-이런거구나">TDD는 이런거구나</h3>
<p>허재 코치님의 멘토링, TDD에 대해 공부한 것, 과제 진행을 통해 TDD가 뭔지 감(?)을 잡은 것 같다.(<del>아닐 수도..</del>)</p>
<h3 id="아직-동시성에-대한-개념이-부족해">아직 동시성에 대한 개념이 부족해</h3>
<p>동시성 처리에 대한 개념이 부족하다.
앞으로 남은 과제에서 동시성에 대해 다룰 일이 계속 있을 것 같은데 추가로 공부하자!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Testable Code]]></title>
            <link>https://velog.io/@jeongyeon_kim/Testable-Code</link>
            <guid>https://velog.io/@jeongyeon_kim/Testable-Code</guid>
            <pubDate>Sun, 09 Feb 2025 03:05:25 GMT</pubDate>
            <description><![CDATA[<h1 id="testable-code">Testable Code</h1>
<h2 id="1-무엇을-테스트할-것인가">1. 무엇을 테스트할 것인가</h2>
<h3 id="1-1-구현부가-아닌-설계를-테스트-해야한다">1-1. 구현부가 아닌 설계를 테스트 해야한다</h3>
<blockquote>
<p>테스트를 위해 원본의 구현과 설계를 고치는 것이 맞는가?</p>
</blockquote>
<ul>
<li><strong>테스트를 위해 구현과 설계는 변경될 수 있다.</strong></li>
<li>테스트 코드는 보조 수단이 아닌 <strong>같은 레벨</strong>로 봐야 한다.</li>
<li><strong>좋은 디자인으로 구현된 코드는 테스트하기 쉽다.</strong></li>
<li><strong>테스트는 구현 설계 Smell을 맡게 해주는 좋은 수단</strong></li>
</ul>
<h3 id="1-2-테스트가-가능한-것-불가능한-것">1-2. 테스트가 가능한 것, 불가능한 것</h3>
<ul>
<li>Non-Testable: 제어할 수 없는 영역 ⇒ <strong>멱등한 결과를 보장할 수 없음</strong><ul>
<li><code>Random</code>, <code>Shuffle</code>, <code>LocalDateTime.now()</code></li>
<li>외부 세계(HTTP, 외부 저장소)</li>
</ul>
</li>
<li>Testable: <strong>항상 성공할 수 있는 것, 항상 동일한 결과가 나올 수 있는 것</strong></li>
</ul>
<h2 id="2-어떻게-테스트할-것인가">2. 어떻게 테스트할 것인가</h2>
<h3 id="2-1-테스트가-불가능한-영역을-boundary-layer로-올려서-테스트">2-1. 테스트가 불가능한 영역을 Boundary Layer로 올려서 테스트</h3>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/8f9b6869-6d3c-4e96-a197-f7efaea7ae99/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/c0d21d78-2143-45de-8570-96fa2b6eb2b4/image.png" alt=""></p>
<ul>
<li>배달팁을 계산하는 로직에서 isValid의 <code>LocalDateTime.now()</code> 는 제어할 수 없는 영역이기에 테스트가 어려움</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/494a877f-421c-43fe-9f6b-254956d55514/image.png" alt=""></p>
<ul>
<li><code>LocalDateTime.now()</code>를 <strong>Boundary Layer</strong>로 끌어올려서 테스트가 가능하도록 변경시킴</li>
</ul>
<blockquote>
<p>어디까지가 Boundary Layer인가?
⇒ <strong>한 모듈로서의 의미를 지니는 가장 바깥쪽</strong></p>
</blockquote>
<h3 id="2-2-java-spring-framework">2-2. Java, Spring Framework</h3>
<blockquote>
<p><code>@SpringBootTest</code></p>
</blockquote>
<ul>
<li><p>통합 테스트를 지원하는 스프링 부트 테스트 어노테이션</p>
</li>
<li><p>모든 빈들을 스캔하고 애플리케이션 Context를 생성하여 테스트 진행</p>
</li>
<li><p>Spring Context는 느리다 ⇒ 빠른 피드백을 받을 수 없다</p>
</li>
<li><p>Spring Context의 오용은 언어의 본질을 망각하게 할 수 있다</p>
</li>
<li><p><strong>Context, Framework에 의존적이지 않은 테스트를 작성하는 것이 중요!</strong></p>
</li>
</ul>
<h3 id="2-3-test-double">2-3. Test Double</h3>
<ul>
<li>Test Double이란?<ul>
<li><strong>테스트 중인 시스템의 일부분이 완전히 준비되지 않았거나 테스트하기 어려운 상황에서 그 대안으로 사용될 수 있는 &#39;가짜&#39; 컴포넌트</strong></li>
</ul>
</li>
<li>Test Double의 종류<ul>
<li>Dummy: 실제로 사용되진 않지만 파라미터 리스트를 채우기 위한 객체</li>
<li>Fake: 실제 객체의 간단한 버전. 가벼운 데이터베이스 서버나 간단한 로직을 가진 컴포넌트로 작동</li>
<li>Stub: 테스트 중에 호출되면 미리 준비된 응답을 제공. 특정 메서들 호출에 의한 반환값을 설정하거나 외부 서비스, 컴포넌트를 대체하는데 사용</li>
<li>Spy: Stub과 유사하지만 호출되었을 때의 정보 기록. 테스트에서 어떤 메서드가 어떻게, 몇 번 호출되었는지 확인 가능</li>
<li>Mock: 예상된 호출 명세를 정의하며 테스트에서 이 명세가 충족되지 않으면 테스트 실패</li>
</ul>
</li>
<li>대표적인 Test Double에는 <strong>Mockito</strong>가 있음</li>
<li>무엇을 Test Double로 처리해야 할까?<ul>
<li><strong>Test Double의 남용은 구현 테스트로 유도할 수 있음</strong></li>
<li>Boundary Layer로 끌어 올려진 Non-Testable 코드에 대해 Test Double 처리</li>
</ul>
</li>
</ul>
<h3 id="2-4-embedded">2-4. Embedded</h3>
<ul>
<li><p>Embedded는 제어할 수 없는 영역을 제어가능하도록 만들 수 있음</p>
</li>
<li><p>테스트와 Embedded 시스템은 <strong>동일한 라이프 사이클</strong>을 갖도록 구성해야  함</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>테스트 정확도</td>
<td>Local &gt; Embedded</td>
</tr>
<tr>
<td>테스트 피드백 속도</td>
<td><strong>Local &lt; Embedded</strong></td>
</tr>
<tr>
<td>테스트 안정성</td>
<td><strong>Local &lt; Embedded</strong></td>
</tr>
</tbody></table>
</li>
</ul>
<h3 id="2-5-endpoint-test">2-5. EndPoint Test</h3>
<ul>
<li>MockMvc, REST Assured, WebTestClient</li>
<li>엔드 포인트 테스트는 요청과 응답 스펙 검증만으로 제한하는게 좋음</li>
</ul>
<h3 id="2-6-spring-cloud-contract">2-6. Spring Cloud Contract</h3>
<ul>
<li>MSA 환경에서 E2E 테스트를 위해서 만들어진 stub 기반 test 도구</li>
<li>CDC(Consumer-Driven Contract)를 잘 이뤄질 수 있도록 Contract 공유 메커니즘을 제공</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/f9240079-d00d-4790-8b59-bc0194b38bc3/image.png" alt=""></p>
<hr>
<h2 id="참고">참고</h2>
<p><a href="https://www.youtube.com/watch?v=YdtknE_yPk4">https://www.youtube.com/watch?v=YdtknE_yPk4</a></p>
<p><a href="https://jwchung.github.io/testing-oh-my">https://jwchung.github.io/testing-oh-my</a></p>
<p><a href="https://jojoldu.tistory.com/674">https://jojoldu.tistory.com/674</a></p>
<p><a href="https://jojoldu.tistory.com/676?category=1036934">https://jojoldu.tistory.com/676?category=1036934</a></p>
<p><a href="https://jojoldu.tistory.com/680?category=1036934">https://jojoldu.tistory.com/680?category=1036934</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD의 개념, Why TDD?]]></title>
            <link>https://velog.io/@jeongyeon_kim/TDD%EC%9D%98-%EA%B0%9C%EB%85%90-Why-TDD</link>
            <guid>https://velog.io/@jeongyeon_kim/TDD%EC%9D%98-%EA%B0%9C%EB%85%90-Why-TDD</guid>
            <pubDate>Sun, 09 Feb 2025 03:00:41 GMT</pubDate>
            <description><![CDATA[<h1 id="tdd란">TDD란?</h1>
<p>TDD란 Test Driven Development의 약자로, ‘<strong>테스트 주도 개발’</strong>이라고 한다.</p>
<p>짧은 주기의 반복 테스트를 이용한 소프트웨어 방법론이다.</p>
<p>애자일 방법론 중 하나인 <strong>eXtream Programming(XP)</strong>의 ‘<strong>Test-First</strong>’ 개념에 기반을 둔 설계를 중요시 한다.</p>
<blockquote>
<p><strong>eXtream Programming(XP)</strong>
: 고객의 요구 사항에 유연하게 대처하기 위해 고객의 참여와 개발 과정의 반복을 극대화하여 개발 생산성을 향상하는 방법</p>
</blockquote>
<h1 id="tdd-개발-주기">TDD 개발 주기</h1>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/6091fe6f-681e-42a2-a1d8-310f89251bb4/image.png" alt=""></p>
<h3 id="red-green-refactor">Red-Green-Refactor</h3>
<ul>
<li>Red: 실패하는 테스트 코드 작성</li>
<li>Green: 테스트가 통과하는 코드 작성</li>
<li>Refactor: 중복 코드 제거, 일반화 등 리팩토링 수행</li>
</ul>
<h1 id="일반적인-개발-방식-vs-tdd-개발-방식">일반적인 개발 방식 vs. TDD 개발 방식</h1>
<h3 id="일반적인-개발-방식">일반적인 개발 방식</h3>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/ee127751-7c4c-4a12-9df5-fcdb1bd815d0/image.png" alt=""></p>
<ul>
<li>요구사항 분석 → 설계 → 개발 → 테스트 → 배포</li>
<li>고객의 요구사항은 변경될 가능성이 있어 초기에 완벽한 설계 불가능</li>
<li>추가적인 요구사항이나 잠재적인 버그 등으로 인해 재설계를 진행하면서 불필요한 코드 발생</li>
<li>이는 코드의 재사용성을 떨어트리고, 유지보수를 어렵게 만듦(소스코드 품질 저하)</li>
<li>작은 수정에도 모든 기능을 테스트하게 되면서 자체 테스트 비용 증가</li>
</ul>
<h3 id="tdd-개발-방식">TDD 개발 방식</h3>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/08bb2cf1-c379-4b36-b2ca-cf777dbdffd5/image.png" alt=""><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/04d7efbc-cff4-4e27-9ebf-aa4ed70b1042/image.png" alt=""></p>
<ul>
<li>테스트 코드 작성 → 개발 → 리팩토링 사이클 반복</li>
<li>설계 단계에서 프로그래밍의 목적을 반드시 정의하고, 무엇을 테스트할지 미리 정의해야 함</li>
<li>테스트 도중 발생하는 예외 사항(추가 요구사항, 버그 등)을 테스트 케이스에 추가하고 설계 개선</li>
<li>위 반복 단계가 진행되면서 자연스럽게 버그가 줄고, 소스코드는 간결해짐(소스코드 품질 향상)</li>
<li>지속적인 테스트 코드 작성으로 추가적인 요구사항이 생겨도 재설계 시간이 절약됨</li>
</ul>
<blockquote>
<p><strong>JUnit(Java)</strong>
대표적인 TDD Tool로 ‘Java에서 독립된 단위테스트를 지원해주는 프레임워크’이다.
어노테이션 기반으로 테스트를 지원하며, 단정문(Assert)으로 테스트 케이스의 기대값에 대해 수행 결과를 확인한다.
c.f.) xUnit 프레임워크: JUnit(Java), CUnit(C), PyUnit(Python) 등</p>
</blockquote>
<h1 id="tdd의-장단점">TDD의 장단점</h1>
<h2 id="tdd의-장점">TDD의 장점</h2>
<h3 id="1-디버깅-시간-단축">1. 디버깅 시간 단축</h3>
<p>단위 테스트의 장점이다.</p>
<p>단위 테스트 코드가 없다면 특정 버그가 어디서 발생했는지 찾기 위해 모든 레이어를 디버깅해야 하지만, TDD의 경우 자동화된 단위 테스트를 통해 버그를 쉽게 찾을 수 있다.</p>
<h3 id="2-코드의-재사용성-증가">2. 코드의 재사용성 증가</h3>
<p>Red 단계에서 버그와 같은 예외 사항이 미리 고려되고 테스트가 통과된 코드만 개발 단계에서 사용된다.</p>
<p>불필요하거나 중복이 발생한 코드는 정리되고, 재사용성이 높고 유지보수가 용이한 코드만 남는다.</p>
<h3 id="3-재설계-시간-단축">3. 재설계 시간 단축</h3>
<p>테스트 코드를 미리 작성하기 때문에 프로그래밍의 목적을 분명하게 할 수 있다.</p>
<p>테스트 코드를 작성하면서 다양한 예외 사항을 생각해볼 수 있기 때문에 개발을 진행하면서 설계가 변경되는 일을 방지할 수 있다.</p>
<h3 id="4-빠른-피드백">4. 빠른 피드백</h3>
<p>인수 테스트는 거의 완성된 코드를 가지고 사용자의 관점에서 사용할 수 있는 수준인지를 확인한다. 이 과정에서 문제를 발견해도 정확한 원인 파악이 어렵다.</p>
<p>TDD는 기능 단위로 테스트를 진행하기 때문에 코드가 완성되어 프로그래머의 손을 떠나기 전에 피드백을 받는 것이 가능하다.</p>
<h3 id="5-불안정성을-제거하여-생산성-향상">5. 불안정성을 제거하여 생산성 향상</h3>
<p>켄트 벡은 TDD는 불안함을 지루함으로 바꾸는 마법의 돌이라고 말한 적이 있다.</p>
<p>코드가 지닌 불안정성과 불확실성을 지속적으로 해결하여 생산성을 높인다.</p>
<h3 id="6-추가-구현-용이">6. 추가 구현 용이</h3>
<p>기존 코드에 기능을 추가할 때 가장 우려되는 점은 해당 기능이 기존 코드에 어떤 영향을 미칠지 알지 못한다는 것이다.</p>
<p>하지만 TDD의 경우 자동화된 단위 테스트를 통해 테스트 시간을 단축할 수 있다.</p>
<h3 id="7-문서-대체-기능">7. 문서 대체 기능</h3>
<p>테스트를 작성할 때 어떤 의도를 가지고 테스트 코드를 작성하였는지, 그리고 어떤 결과 값을 기대하는지를 작성하기 때문에 코드를 작성한 사람의 의도를 파악할 수 있다.</p>
<h2 id="tdd의-단점">TDD의 단점</h2>
<h3 id="1-개발-시간-증가">1. 개발 시간 증가</h3>
<p>단기적으로 봤을 때 코드 개발 시간이 증가한다.</p>
<p>개발자에게 테스트 코드 작성이 익숙하지 않을 수도 있고, 기존에 비즈니스 코드만 작성하고 넘어갔을 때에 비해 테스트 코드를 작성하는데 들이는 시간이 많다.</p>
<h3 id="2-구조에-얽매임">2. 구조에 얽매임</h3>
<p>테스트는 테스트일 뿐이고 실제 코드가 더 중요한데, TDD 원칙 때문에 쉽게 넘어가기 못하는 경우가 발생한다.</p>
<h1 id="bdd-ddd">BDD, DDD</h1>
<h2 id="bdd">BDD</h2>
<p>BDD는 Behevior Driven Development의 약자로 ‘<strong>행동 주도 개발</strong>’이다.</p>
<p>TDD에서 파생된 개발 방법론으로 사용자 또는 비즈니스 요구사항을 <strong>행위와 시나리오를 중심</strong>으로 한다.</p>
<p>개발자와 비개발자 모두 이해하기 쉬운 자연어로 표현하여 의사소통을 원활히 하고, 요구사항을 명확하게 전달하고 구현한다.</p>
<p><strong>Given-When-Then</strong> 형태로 표현하여 주어진 상황에서 어떤 동작을 하고 기대하는 결과가 나오는지를 명시한다.</p>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/f3966030-47f3-4d22-bc54-add22d51891f/image.png" alt=""></p>
<h3 id="given-when-then">Given-When-Then</h3>
<ul>
<li>Given(주어진 상황): 초기 상태나 전제 조건 기술</li>
<li>When(행동): 수행할 동작이나 이벤트 기술</li>
<li>Then(결과): 예상되는 결과나 상태 기술</li>
</ul>
<h2 id="ddd">DDD</h2>
<p>DDD는 Domain Driven Design의 약자로 ‘<strong>도메인 주도 설계</strong>’로, 기존 데이터 중심의 접근법에서 벗어나 <strong>도메인 패턴을 중심에 두고 설계</strong>하는 방식이다.</p>
<p><strong>보편적인(ubiquitous) 언어</strong>를 사용한다. 도메인 전문가와 커뮤니케이션 문제를 없애기 위해 동일한 표현과 단어로 구성된 단일 언어체계를 구축하여 사용한다.</p>
<p><strong>소프트웨어 엔티티와 도메인 컨셉을 가장 가까이 일치시킨다.</strong> 도메인 모델부터 코드까지 함께 움직이는 모델을 지향한다.</p>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/0f5c1cd1-6649-4d06-92c6-4c0a2b2072cb/image.png" alt=""></p>
<hr>
<h2 id="참고">참고</h2>
<p><a href="https://tidyfirst.substack.com/p/tdd-isnt-design">https://tidyfirst.substack.com/p/tdd-isnt-design</a></p>
<p><a href="https://f-lab.kr/insight/importance-of-tdd-and-ddd-in-modern-development">https://f-lab.kr/insight/importance-of-tdd-and-ddd-in-modern-development</a></p>
<p><a href="https://dhh.dk/2014/tdd-is-dead-long-live-testing.html">https://dhh.dk/2014/tdd-is-dead-long-live-testing.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[만들면서 배우는 클린 아키텍처] 2. 의존성 역전하기]]></title>
            <link>https://velog.io/@jeongyeon_kim/%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-2.-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jeongyeon_kim/%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-2.-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 21 Dec 2024 14:28:50 GMT</pubDate>
            <description><![CDATA[<h2 id="1-단일-책임-원칙single-responsibility-prinsiple-srp">1. 단일 책임 원칙(Single Responsibility Prinsiple, SRP)</h2>
<blockquote>
<p>컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.</p>
</blockquote>
<p>컴포넌트를 변경할 이유가 하나 -&gt; 컴포넌트는 한가지 일만 함</p>
<p>컴포넌트를 변경할 이유가 하나뿐이면 다른 이유로 소프트웨어를 변경하더라도 이 컴포넌트에 대해 신경쓰지 않아도 됨.</p>
<h2 id="2-의존성-역전-원칙dependency-inversion-principle-dip">2. 의존성 역전 원칙(Dependency Inversion Principle, DIP)</h2>
<blockquote>
<p>코드 상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다.</p>
</blockquote>
<p>양쪽 코드를 모두 제어할 수 있을 때만 의존성 역전 가능</p>
<p>(서드파티 라이브러리에 의존성이 있으면 라이브러리를 제어할 수 없기 때문에 의존성 역전 불가)</p>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/c3800848-ce2a-4fa0-b6b6-d336a62d550b/image.png" alt=""></p>
<ol>
<li><p>엔티티는 도메인 객체를 표현하고 도메인 코드가 이 엔티티들의 상태를 변경하기 때문에 엔티티를 도메인 계층으로 올림</p>
</li>
<li><p>리포지토리가 도메인 계층의 엔티티에 의존하기 때문에 순환 의존성(circular dependency) 생김</p>
</li>
<li><p>도메인 계층에 리포지토리 인터페이스를 만들고, 실제 리포지토리는 영속성 계층에서 구현</p>
</li>
</ol>
<h2 id="3-클린-아키텍처">3. 클린 아키텍처</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/7e8872df-c673-413e-8989-e1d5a483c32b/image.png" alt=""></p>
<blockquote>
<p><strong>계층 간의 모든 의존성이 안쪽으로 향해야 한다.</strong></p>
</blockquote>
<p>도메인 계층이 특정 프레임워크에 특화된 코드를 가질 수 없어 비즈니스 로직에만 집중할 수 있음.</p>
<p>도메인 계층이 외부 계층과 철저히 분리되어야 하므로 엔티티에 대한 모델을 각 계층에서 유지보수 해야함.</p>
<p>도메인 계층은 영속성 계층을 모르기 때문에 도메인 계층에서 사용한 엔티티를 영속성 계층에서 함께 사용 불가.
따라서 각 계층에서 엔티티를 만들어 변환하는 과정 필요.</p>
<h2 id="4-헥사고날-아키텍처">4. 헥사고날 아키텍처</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/de370557-e131-4215-aa33-00c415d11016/image.png" alt=""></p>
<p>모든 의존성은 코어를 향함.</p>
<p>헥사고날 아키텍처를 계층으로 구성하면</p>
<ul>
<li>가장 바깥쪽 계층은 애플리케이션과 다른 시스템 간의 번역을 담당하는 어댑터</li>
<li>포트와 유스케이스 구현체가 애플리케이션 계층 구성</li>
<li>도메인 엔티티가 가장 마지막 계층</li>
</ul>
<p>포트와 어댑터(port-and-adapters) 아키텍처라고도 불림.</p>
<p>코어와 어댑터 간 통신하기 위해 어플리케이션 코어가 포트 제공.</p>
<h4 id="애플리케이션을-주도하는-어댑터driving-adapter">애플리케이션을 주도하는 어댑터(driving adapter)</h4>
<p>포트가 코어에 있는 유스케이스 클래스 중 하나에 의해 구현되고 어댑터에 의해 호출됨</p>
<h4 id="애플리케이션에-주도되는-어댑터driven-adapter">애플리케이션에 주도되는 어댑터(driven adapter)</h4>
<p>포트가 어댑터에 의해 구현되고 코어에 의해 호출됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[만들면서 배우는 클린 아키텍처] 1. 계층형 아키텍처의 문제는 무엇일까?]]></title>
            <link>https://velog.io/@jeongyeon_kim/%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-1.-%EA%B3%84%EC%B8%B5%ED%98%95-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EB%AC%B8%EC%A0%9C%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@jeongyeon_kim/%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-1.-%EA%B3%84%EC%B8%B5%ED%98%95-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EB%AC%B8%EC%A0%9C%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Sat, 21 Dec 2024 14:04:24 GMT</pubDate>
            <description><![CDATA[<h2 id="1-계층형-아키텍처layered-architecture란">1. 계층형 아키텍처(Layered Architecture)란?</h2>
<blockquote>
<p>소프트웨어 설계 패턴 중 하나로, 시스템을 여러 개의 계층으로 분리하여 각 계층이 특정 역할을 수행하도록 하는 방식</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/9dc90ff4-f57a-426c-9a02-e345c331a948/image.png" alt=""></p>
<p><strong>특정한 계층에서는 같은 계층에 있는 컴포넌트나 아래에 있는 계층에만 접근 가능</strong></p>
<h3 id="presentation-layer">Presentation Layer</h3>
<p>사용자의 응답 및 요청 처리하는 계층</p>
<h3 id="application-layer">Application Layer</h3>
<p>비즈니스 로직을 수행하는 계층</p>
<h3 id="domain-layer">Domain Layer</h3>
<p>데이터베이스에 요청을 전달(접근)하는 계층.</p>
<h3 id="database-layer">Database Layer</h3>
<p>데이터베이스가 운영되는 계층</p>
<h2 id="2-계층형-아키텍처의-장점">2. 계층형 아키텍처의 장점</h2>
<p>계층별로 낮은 결합도를 가지고 계층 내에서 높은 응집도를 가지면 유지보수 용이 및 확장성 향상</p>
<h2 id="3-계층형-아키텍처의-단점">3. 계층형 아키텍처의 단점</h2>
<h3 id="데이터베이스-주도-설계-유도">데이터베이스 주도 설계 유도</h3>
<p>ORM(Object-Relational Mapping)이 계층형 아키텍처와 결합하면 데이터베이스 주도 설계를 할 가능성이 높아짐.
ORM에 의해 관리되는 엔티티는 영속성 계층에 있고 도메인 계층에서 엔티티에 접근하면서 강한 결합이 생김.
이로 인해 도메인 계층에서 도메인 로직 뿐만 아니라 영속성 로직(즉시로딩/지연로딩, 트랜잭셔느 캐시 플러시 등)까지 수행하게 됨.</p>
<h3 id="지름길을-택하기-쉬워짐">지름길을 택하기 쉬워짐</h3>
<p>마감 기한에 쫓기거나 누군가 한번 지름길을 택하게 되면 이후에는 지름길을 택하는 경우가 많아짐.
(지름길: 상위 계층 컴포넌트에 접근해야 하는 경우 해당 컴포넌트를 하위 계층으로 내려버리는 행위 등)</p>
<h3 id="테스트하기-어려워짐">테스트하기 어려워짐</h3>
<p>웹 계층에서 도메인 계층을 거치지 않고 바로 영속성 계층에 접근하는 경우 테스트하기 어려워짐.</p>
<ol>
<li>필드 하나를 조작하더라도 도메인 로직이 웹 계층에 구현되게 되어 책임이 섞이고 핵심 도메인 로직이 퍼져나할 확률이 높아짐.</li>
<li>웹 계층 테스트에서 도메인 계층과 영속성 계층을 모두 mocking 해야하는 문제 발생.</li>
</ol>
<h3 id="유스케이스를-숨김">유스케이스를 숨김</h3>
<p>도메인 로직이 퍼져나가게 되고, 서비스가 넓어지게 되는 문제가 발생하면 추가 유스케이스가 들어갈 위치를 찾기 어려워짐.</p>
<h3 id="동시-작업이-어려워짐">동시 작업이 어려워짐</h3>
<p>모든 것이 영속성 계층 위에서 만들어지기 때문에 여러 개발자가 동시에 작업하기 어려움.
서비스가 넓으면 같은 서비스를 여러명이 동시에 작업하면서 병합 충돌(merge conflict) 발생 가능성 높음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[책 리뷰 - 단위 테스트] 1. 단위 테스트의 목표]]></title>
            <link>https://velog.io/@jeongyeon_kim/%EC%B1%85-%EB%A6%AC%EB%B7%B0-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-1.-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%9D%98-%EB%AA%A9%ED%91%9C</link>
            <guid>https://velog.io/@jeongyeon_kim/%EC%B1%85-%EB%A6%AC%EB%B7%B0-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-1.-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%9D%98-%EB%AA%A9%ED%91%9C</guid>
            <pubDate>Sat, 22 Jun 2024 15:20:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>단위 테스트는 최소한의 노력으로 최대한의 이득을 내야 한다.</p>
</blockquote>
<h2 id="1-단위-테스트-현황">1. 단위 테스트 현황</h2>
<p>단위 테스트가 중요하고, 필수적이라는 인식은 자리 잡았으나, 
좋은 단위 테스트를 작성하는 것은 어떤 것인지에 대한 의미는 혼란스러운 상태</p>
<h2 id="2-단위-테스트의-목표">2. 단위 테스트의 목표</h2>
<blockquote>
<p>단위테스트의 목표는 소프트웨어 프로젝트의 <strong>지속 가능한 성장</strong>을 가능하게 하는 것.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/6cca8829-0825-4a65-93f5-d4e4f0771e9f/image.png" width="50%" height="50%"></img>
테스트가 부재하면 처음에는 빠른 시작이 가능하나 이후 시간이 지날 수록 개발 속도가 느려진다.
<code>소프트웨어 엔트로피(software entropy): 개발 속도가 빠르게 감소하는 현상</code>
단위 테스트는 <strong>지속성</strong>과 <strong>확장성</strong>이 핵심</p>
<h3 id="21-좋은-테스트와-좋지-않은-테스트를-가르는-요인">2.1 좋은 테스트와 좋지 않은 테스트를 가르는 요인</h3>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/47d3f41b-1c65-42a3-b236-d4be9cf25099/image.png" width="50%" height="50%"></img></p>
<p>테스트의 가치와 유지 비용을 모두 고려해야 한다.</p>
<ul>
<li>기반 코드를 리팩토링 할 때 테스트 코드도 리팩토링</li>
<li>각 코드 변경 시 테스트 실행</li>
<li>테스트가 잘못된 경고를 발생시킬 경우 처리</li>
<li>기반 코드가 어떻게 동작하는지 이해하려고 할 때는 테스트를 읽는 데 시간 투자</li>
</ul>
<h5 id="제품-코드-vs-테스트-코드">제품 코드 vs. 테스트 코드</h5>
<ul>
<li>코드는 자산이 아니라 <strong>책임</strong></li>
<li>코드가 많아 진다 = 잠재적인 버그에 노출될 확률 높다 = 프로젝트 유지비가 증가한다</li>
<li>테스트 코드도 코드이기에 다른 코드와 마찬가지로 지속적인 리팩토링 필요</li>
</ul>
<h2 id="3-테스트-스위트-품질-측정을-위한-커버리지-지표">3. 테스트 스위트 품질 측정을 위한 커버리지 지표</h2>
<blockquote>
<p>커버리지는 <strong>좋은 부정 지표</strong>이지만 <strong>나쁜 긍정 지표</strong></p>
</blockquote>
<h3 id="31-코드-커버리지-지표에-대한-이해">3.1 코드 커버리지 지표에 대한 이해</h3>
<p><code>코드 커버리지(테스트 커버리지) = 실행 코드 라인 수 / 전체 라인 수</code></p>
<ul>
<li>하나 이상의 테스트로 실행된 코드 라인 수와 제품 코드 베이스의 전체 라인 수의 비율<pre><code class="language-java">public boolean isStringLong(String input) {
  if (input.length &gt; 5)
      return true;
  return false;
}
</code></pre>
</li>
</ul>
<p>public void Test() {
    boolean result = isStringLong(&quot;abc&quot;);
    Assertions.assertThat(result).isFalse();
}</p>
<pre><code>이 경우 코드 커버리지는 4/5 = 0.8 = 80%
```java
public boolean isStringLong(String input) {
    return input.length &gt; 5;
}

public void test() {
    boolean result = isStringLong(&quot;abc&quot;);
    Assertions.assertThat(result).isFalse();
}</code></pre><p>위 코드를 리팩토링하여 코드 커버리지를 100%로 만들었다.
하지만 이는 메서드 내의 코드만 바꿨을 뿐 검증하는 결과의 개수는 동일하다.
코드 커버리지는 라인 수만 처리하기 때문에 코드가 작을 수록 유리하다.</p>
<h3 id="32-분기-커버리지-지표에-대한-이해">3.2 분기 커버리지 지표에 대한 이해</h3>
<p><code>분기 커버리지 = 통과 분기 / 전체 분기 수</code></p>
<ul>
<li>테스트 스위트 내 하나 이상의 테스트가 통과하는 제어 구조의 수<pre><code class="language-java">public boolean isStringLong(String input) {
  return input.length &gt; 5;
}
</code></pre>
</li>
</ul>
<p>public void test() {
    boolean result = isStringLong(&quot;abc&quot;);
    Assertions.assertThat(result).isFalse();
}</p>
<pre><code>이 경우 분기 커버리지는 1/2 = 50%이다.
코드를 길게 작성하던 짧게 작성하던 분기의 개수는 동일하기에 분기 커버리지는 동일하다.

### 3.3 커버리지 지표에 대한 문제점
#### 테스트 대상 시스템의 모든 가능한 결과를 검증한다고 보장할 수 없다.
```java
public class Example {
    private boolean wasLastStringLong;

    public boolean isStringLong(String input) {
        boolean result = input.length &gt; 5;
        wasLastStringLong = result;
        return result;
    }

    public void test() {
        boolean result = isStringLong(&quot;abc&quot;);
        Assertions.assertThat(result).isFalse();
    }
}</code></pre><p>wasLastStringLong에 result 값을 쓰는 암묵적인 결과를 검증하지 않아도
코드 커버리지 100%와 분기 커버리지 50%의 결과를 보인다.</p>
<pre><code class="language-java">public void test() {
    boolean result1 = isStringLong(&quot;abc&quot;);
    boolean result2 = isStringLong(&quot;abcdef&quot;);
}</code></pre>
<p>test() 메서드를 위와 같이 바꾸면,
코드 커버리지 100%, 분기 커버리지 100%를 나타낸다.
그러나 이 메서드는 아무것도 검증하지 않기 때문에 쓸모가 없는 테스트 코드이다.</p>
<h4 id="외부-라이브러리의-코드-경로를-고려할-수-있는-커버리지-지표는-없다">외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.</h4>
<pre><code class="language-java">public int parse(String input) {
    return Integer.parseInt(input);
}

public void test() {
    int result = parse(&quot;5&quot;);
    Assertions.assertThat(result).isEqualTo(5);
}</code></pre>
<p>분기 커버리지는 100%
그러나 Integer.parseInt()가 수행하는 코드 경로는 고려되지 않는다.</p>
<h3 id="34-특정-커버리지-숫자를-목표로-하기">3.4 특정 커버리지 숫자를 목표로 하기</h3>
<p>커버리지 지표는 지표 그 자체로 보는 것이지, 목표로 여겨서는 안된다.</p>
<h2 id="4-무엇이-성공적인-테스트-스위트를-만드는가">4. 무엇이 성공적인 테스트 스위트를 만드는가?</h2>
<h3 id="41-개발-주기에-통합되어-있다">4.1 개발 주기에 통합되어 있다.</h3>
<p>모든 테스트는 개발 주기에 통합 되어야 한다.
이상적으로는 코드가 변경될 때마다 작은 테스트 코드라도 실행해야 한다.</p>
<h3 id="42-코드베이스에서-가장-중요한-부분만을-대상으로-한다">4.2 코드베이스에서 가장 중요한 부분만을 대상으로 한다.</h3>
<p>비즈니스 로직(도메인 모델)을 테스트 하는 것에 집중하여 노력을 기울여야 한다.</p>
<h3 id="43-최소-유지비로-최대-가치를-끌어낸다">4.3 최소 유지비로 최대 가치를 끌어낸다.</h3>
<p>가치 있는 테스트(+ 가치가 낮은 테스트)를 식별한다.
가치 있는 테스트를 작성한다.</p>
<hr>
<h2 id="📚-내-생각">📚 내 생각</h2>
<p>단순히 단위 테스트 코드를 작성해야 하고, 단위 테스트를 작성하는 것이 좋다는 것만 알고 있었지
그것이 왜 필요한지, 단위 테스트는 무엇을 검증하는 것인지, 좋은 단위 테스트란 무엇인지 등
단위 테스트의 목표에 대해 생각해보지는 못했었다.</p>
<p>1장의 내용을 통해 단위 테스트의 목표가 무엇인지 알 수 있었고,
지금까지 내가 작성해왔던 단위 테스트, 혹은 통합 테스트가 좋은 테스트 코드였는가에 대한 생각을 해보는 계기가 되었다.</p>
<p>이 책을 읽고 단위 테스트에 대한 이해를 통해 
단위 테스트를 작성할 때 애플리케이션의 지속 가능한 성장을 염두에 두고,
최소한의 유지비로 최대한의 가치를 끌어내는 좋은 테스트 코드를 작성하며
나와 애플리케이션이 모두 성장하는 테스트 코드를 작성해야겠다는 생각을 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]복제]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%B3%B5%EC%A0%9C</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%B3%B5%EC%A0%9C</guid>
            <pubDate>Mon, 04 Mar 2024 09:04:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>가용성</p>
</blockquote>
<ul>
<li>서비스의 안정성을 측정하는 데 사용되는 지표</li>
<li>Availability = Available for Use Time / Total Time</li>
<li>일정 기간 동안 서비스를 정상적으로 사용할 수 있는 시간 비율</li>
<li>고가용성 시스템: 가용성이 높은 시스템</li>
</ul>
<h3 id="고가용성-확보를-위한-기능">고가용성 확보를 위한 기능</h3>
<ul>
<li>복제: 마스터 노드의 데이터를 복제본 노드로 실시간 복사하는 기능</li>
<li>자동 페일오버: 마스터 노드에서 발생한 장애를 감지해 레디스로 들어오는 클라이언트 연결을 자동을 ㅗ복제본 노드로 리다이렉션 하는 기능</li>
</ul>
<h1 id="1-레디스에서의-복제-구조">1. 레디스에서의 복제 구조</h1>
<hr>
<ul>
<li>운영 중인 서비스에서 복제본 노드를 추가하는 이유<ul>
<li>서비스를 안정적으로 운영하기 위해서는 마스터 DB가 다운됐을 때 대신 사용할 여분의 복제본 필요</li>
<li>마스터 노드에 접근하는 서비스가 많을 때, 일부 트래픽이 복제본을 바라보게 해 부하 분산</li>
<li>백업을 복제본에서 수행해 백업 작업이 서비스에 미치는 영향도 최소화</li>
</ul>
</li>
<li>레디스는 복제본 노드가 기본으로 읽기 전용으로 동작. 모든 데이터 입력은 마스터 노드에서 이루어짐<h2 id="복제-구조-구성하기">복제 구조 구성하기</h2>
<pre><code>REPLICAOF &lt;master-ip&gt; &lt;master-port&gt;</code></pre></li>
<li>한 개의 복제 그룹에서는 항상 한 개의 마스터 노드만 존재</li>
<li>복제본 노드에 새로운 복제본 추가 가능<h2 id="패스워드-설정">패스워드 설정</h2>
</li>
<li>requirepass 옵션을 이용해 패스워드 설정</li>
<li>복제본 노드는 masterpass 옵션에 마스터의 requirepass에 설정된 패스워드 값 입력</li>
<li>하나의 복제 그룹에 속한 마스터와 복제본 노드는 같은 패스워드로 설정하는 것이 일반적(다른 패스워드도 가능)<h1 id="2-복제-메커니즘">2. 복제 메커니즘</h1>
</li>
</ul>
<hr>
<ul>
<li><p>버전 7 이전</p>
<ul>
<li>repl-diskless-sync 옵션 기본값 no(디스크 사용 방식)</li>
<li>디스크 사용하는 방식에서의 복제 연결 메커니즘<ol>
<li>REPLICAOF 커맨드로 복제 연결 시도</li>
<li>마스터 노드에서는 fork로 자식 프로세스를 새로 만든 뒤 RDB 스냅샷 생성</li>
<li>2번 과정 동안 마스터 노드에서 수행된 모든 데이터셋 변경 작업은 레디스 프로토콜(RESP) 형태로 마스터의 복제 버퍼에 저장</li>
<li>RDB 파일이 생성 완료되면 파일은 복제본 노드로 복사</li>
<li>복제본에 저장됐던 모든 내용을 삭제한 뒤 RDB 파일을 이용해 데이터 로딩</li>
<li>복제 과정 동안 버퍼링됐던 복제 버퍼의 데이터를 복제본으로 전달해 수행시킴
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/c0fcc355-75a6-405b-aee6-f86cef0de2bd/image.png" alt=""></li>
</ol>
</li>
<li>복제 속도는 디스크 I/O 처리량에 영향 받음</li>
<li>RDB 파일이 생성되는 도중 다른 노드에서 복제 연결 요청이 들어오면 큐에 저장하고 기존 RDB 저장이 완료된 후 여러 복제본이 한 번에 복제 연결 시작</li>
</ul>
</li>
<li><p>버전 7 이후</p>
<ul>
<li>repl-diskless-sync 옵션 기본값 yes(디스크 사용하지 않는 방식)</li>
<li>디스크 사용하지 않는 방식에서의 복제 연결 메커니즘<ol>
<li>REPLICAOF 커맨드로 복제 연결 시도</li>
<li>마스터 노드는 소켓 통신을 이용해 복제본 노드에 바로 연결하며, RDB 파일은 생성됨과 동시에 점진적으로 복제본의 소켓에 전송됨</li>
<li>2번 과정 동안 마스터 노드에서 수행된 모든 데이터셋 변경 작업은 레디스 프로토콜(RESP) 형태로 마스터의 복제 버퍼에 저장</li>
<li>소켓에서 읽어온 RDB 파일을 복제본의 디스크에 저장</li>
<li>복제본에 저장된 모든 데이터를 삭제한 뒤 RDB 파일 내용을 메모리에 로딩</li>
<li>복제 버퍼의 데이터를 복제본으로 전달해 수행시킴
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/98003f78-ffad-49cd-a4d4-d483324555ec/image.png" alt=""></li>
</ol>
</li>
<li>복제본 노드는 마스터에서 가져온 데이터를 모두 삭제하는 과정을 거치는데, 소켓 통신으로 받아온 RDB 데이터가 정상적인지 미리 확인할 수 없어 삭제 전 자신의 디스크에 데이터를 저장하는 과정을 선행해 데이터의 안정성 확보</li>
<li>디스크의 I/O가 느리고 네트워크가 빠른 경우 디스크를 사용하지 않는 복제 방식을 사용하는 것이 더 빠르게 복제 연결을 완료할 수 있는 방법</li>
<li>하나의 복제본으로 복제 연결이 시작된 경우 복제 과정이 끝나기 전까지 다른 복제본과의 연결 수행 불가. 다른 복제본들은 하나의 복제 연결이 끝날 때까지 큐에서 대기</li>
<li>repl-diskless-sync-delay 설정을 통해 설정 시간 만큼 기다린 뒤 복제 연결 시작</li>
<li>네트워크가 유실되어 재동기화를 요청할 경우 마스터에는 한 번에 여러 개의 복제본에서 복제 연결이 들어오는 것이 일반적이기 때문에 repl-diskless-sync-delay 옵션 활성화 하는 것이 좋음<h2 id="비동기-방식으로-동작하는-복제-연결">비동기 방식으로 동작하는 복제 연결</h2>
</li>
</ul>
</li>
<li><p>정상적으로 복제 연결이 된 상태에서 마스터에서 복제본으로의 데이터 전달은 비동기 방식으로 동작</p>
</li>
<li><p>클라이언트는 데이터 입력할 때 마다 복제본에 데이터가 정확하게 전달 되었는지 확인하지 않기 때문에 복제 구조를 사용해도 짧은 지연 시간과 높은 성능 가짐</p>
<h2 id="복제-id">복제 ID</h2>
</li>
<li><p>모든 레디스 인스턴스는 복제 ID(랜덤 스트링) 가짐</p>
</li>
<li><p>복제 ID는 오프셋과 쌍으로 존재</p>
</li>
<li><p>레디스 내부의 데이터가 수정되는 모든 커맨드를 수행할 때마다 오프셋 증가</p>
</li>
<li><p><strong>INFO REPLICATION</strong>: 복제 연결 상태 확인</p>
</li>
<li><p>INFO REPLICATION을 통해 확인한 복제본의 replication id는 마스터의 replication id이고, 오프셋은 복제본에서 마지막으로 수행된 마스터의 오프셋</p>
</li>
<li><p>replication id와 오프셋이 같을 때 두 노드는 정확히 일치된 상태</p>
<h2 id="부분-재동기화">부분 재동기화</h2>
</li>
<li><p>레디스는 부분 재동기화 기능을 사용해 안정적으로 복제 연결 유지</p>
</li>
<li><p>마스터는 커넥션 유실을 대비해 백로그 버퍼라는 메모리 공간에 복제본에 전달한 커맨드 데이터 저장</p>
</li>
<li><p><strong>PSYNC</strong>: 복제본이 자신의 replication id와 오프셋을 마스터에 전달해 복제 재연결 시도
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/9512fa4d-2a5e-4e69-8d2d-f451863daf30/image.png" alt=""></p>
</li>
<li><p>복제본이 보낸 repliation id가 현재의 마스터와 일치하지 않다면 전체 재동기화 시도</p>
</li>
<li><p>복제본은 언제든지 마스터로 승격될 가능성을 갖고 있기 때문에 복제본에 직접 연결된 복제 연결이 따로 없더라도 백로그 버퍼를 해제하지 않음</p>
<h2 id="secondary-복제-id">Secondary 복제 ID</h2>
</li>
<li><p>한 개의 복제본 그룹 내의 모든 레디스 노드는 동일한 복제 ID 가짐</p>
</li>
<li><p>마스터 노드와의 복제가 끊어짐과 동시에 복제본은 새로운 복제 ID 가짐</p>
</li>
<li><p>기존의 복제 ID는 master_replid2에 저장
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/0a285d79-5db7-4fcd-98ab-326a692f7525/image.png" alt=""></p>
</li>
<li><p>노드 C가 B에 복제 연결이 될 때, 두 노드의 master_replid2가 같기 때문에 C 노드는 B 노드에 부분 재동기화 시도</p>
<blockquote>
<p>레디스가 2개의 복제 ID를 갖는 이유는 마스터로 승격되는 복제본 때문이며, 같은 복제 그룹 내에서 페일오버 이후 승격된 새로운 마스터에 연결된 복제본은 전체 재동기화를 수행할 필요가 없을 수 있음</p>
</blockquote>
<h2 id="읽기-전용-모드로-동작하는-복제본-노드">읽기 전용 모드로 동작하는 복제본 노드</h2>
</li>
<li><p>replica-read-only</p>
</li>
<li><p>복제본에 직접 데이터를 쓸 수 있다하더라도 복제본에 쓰는 내용은 로컬에만 유지되며 다른 복제본으로 전파되지 않음</p>
</li>
<li><p>서브 복제보는 항상 최상위 마스터가 중간 복제본으로 보낸 것과 동일한 복제 프로토콜을 전달 받음</p>
<h2 id="유효하지-않은-복제본-데이터">유효하지 않은 복제본 데이터</h2>
</li>
<li><p>유효하지 않은 데이터: 복제본의 데이터와 마스터의 데이터가 정확하게 일치하지 않는 경우의 데이터</p>
<ul>
<li>복제본이 마스터와 연결이 끊어진 상태</li>
<li>복제 연결이 시작된 뒤 아직 완료되지 않았을 경우</li>
</ul>
</li>
<li><p>replica-serve-stale-data: 복제본의 데이터가 유효하지 않다고 판단될 때 복제본의 동작 방식 제어</p>
<ul>
<li>yes: 유효하지 않다고 판단될 때에도 클라이언트로부터 들어오는 모든 읽기 요청에 데이터 반환</li>
<li>no: INFO, CONFIG, PING 등의 일부 기본 커맨드를 제외한 모든 커맨드에 대해 SYNC with master in progress 오류 반환<h2 id="백업을-사용하지-않는-경우에서의-데이터-복제">백업을 사용하지 않는 경우에서의 데이터 복제</h2>
</li>
</ul>
</li>
<li><p>레디스에서 복제를 사용하는 경우 마스터와 복제본에서 백업 기능을 사용하는 것이 좋음</p>
</li>
<li><p>백업 기능을 사용하지 않으려면 재부팅 후 레디스가 자동으로 재시작되지 않도록 설정하는 것 권장
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/4029a310-5d8c-49c9-b08a-f46246987a1e/image.png" alt=""></p>
</li>
</ul>
<ol>
<li>백업 기능 사용하지 않는 마스터와 복제본 노드</li>
<li>마스터가 장애로 인해 종료되었지만 레디스 프로세스를 자동 재시작하는 시스템에 의해 노드 재부팅. 메모리 노드 초기화</li>
<li>복제본 노드에는 데이터가 존재하지만, 마스터 노드로의 복제 연결 시도</li>
<li>마스터에서 복제본으로 빈 데이터셋 전달</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]레디스 데이터 백업 방법]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%B1%EC%97%85-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%B1%EC%97%85-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 04 Mar 2024 05:43:18 GMT</pubDate>
            <description><![CDATA[<h1 id="1-레디스에서-데이터를-영구-저장하기">1. 레디스에서 데이터를 영구 저장하기</h1>
<hr>
<p>레디스에서 모든 데이터는 메모리에서 관리된다. 메모리에 있던 모든 데이터는 손실될 가능성이 있다.
따라서 레디스를 영구 저장소와 같은 용도로 사용한다면 디스크에 데이터를 주기적으로 백업하는 것이 필요하다.</p>
<blockquote>
<p>복제와 백업의 차이</p>
</blockquote>
<ul>
<li>복제: 가용성을 위한 것</li>
<li>백업: 장애 상황에서 데이터의 복구를 위한 것</li>
</ul>
<blockquote>
<p>레디스 백업 방식</p>
</blockquote>
<ul>
<li>AOF(Append Only File): 레디스 인스턴스가 처리한 모든 쓰기 작업을 차례대로 기록. 복원 시에는 파일을 다시 읽어가며 데이터 세트 재구성</li>
<li>RDB(Redis DataBase): 일정 시점에 메모리에 저장된 데이터 전체를 저장(snapshot 방식)</li>
</ul>
<h3 id="aof">AOF</h3>
<ul>
<li>레디스 프로토콜(RESP) 형태로 저장됨</li>
<li>레디스에서 실행된 모든 쓰기 작업이 기록됨</li>
<li>RDB 파일보다 크기가 크고 주기적으로 압축해 재작성해야 하지만, 원하는 시점으로 복구 가능</li>
</ul>
<h3 id="rdb">RDB</h3>
<ul>
<li>바이너리 형태로 저장</li>
<li>저장되는 시점의 메모리 데이터가 그대로 저장됨</li>
<li>시점 단위로 여러 백업본을 저장할 수 있고, AOF 파일보다 복원이 빠르다는 장점이 있지만 특정 시점으로의 복구는 불가능</li>
</ul>
<blockquote>
<p>레디스에서 데이터를 복원할 수 있는 시점은 서버가 재시작될 때뿐!!
레디스 인스턴스 실행 도중에 데이터 파일을 익어올 수 있는 방법은 없음</p>
</blockquote>
<h1 id="2-rdb-방식의-데이터-백업">2. RDB 방식의 데이터 백업</h1>
<hr>
<h2 id="특정-조건에-자동으로-rdb-파일-생성">특정 조건에 자동으로 RDB 파일 생성</h2>
<pre><code>save &lt;기간(초)&gt; &lt;기간 내 변경된 키의 개수&gt;
dbfilename &lt;RDB 파일 이름&gt;
dir &lt;RDB 파일이 저장될 경로&gt;</code></pre><ul>
<li>레디스 설정 파일에서 <strong>save</strong> 옵션을 사용해 원하는 조건에 RDB 파일을 저장하도록 설정</li>
<li>일정한 기간(초) 동안 변경된 키의 개수가 조건에 맞을 때 레디스 서버는 자동으로 RDB 파일 저장</li>
<li>RBD 파일을 저장하고 싶지 않다면 save &quot;&quot;로 설정해 옵션 비활성화</li>
<li><strong>CONFIG SET</strong>: 실행 중인 레디스 인스턴스에서 설정 변경</li>
<li><strong>CONFIG REWRITE</strong>: 설정 파일 재작성</li>
<li>레디스 인스턴스가 실행 중인 상태에서 설정 파일을 변경하는 것은 실행 중인 레디스 인스턴스에는 반영되지 않음<ul>
<li>CONFIG SET(설정 변경) -&gt; CONFIG REWRITE(설정 파일 재작성) 과정을 거쳐야 함<h2 id="수동으로-rbd-파일-생성">수동으로 RBD 파일 생성</h2>
</li>
</ul>
</li>
<li><strong>SAVE</strong>: 동기 방식으로 파일 저장</li>
<li><strong>BGSAVE</strong>: fork를 호출해 자식 프로세스를 생성하며 생성된 자식 프로세스가 백그라운드에서 RDB 파일을 생성한 뒤 종료<ul>
<li>이미 백그라운드로 데이터가 저장되고 있을 때 이 커맨드를 수행하면 에러 반환</li>
<li><strong>SCHEDULE</strong> 옵션: 이미 파일이 백그라운드에서 저장 중일 때 이 커맨드를 입력받은 레디스는 일단 OK 반환한 뒤, 기존에 진행 중이던 백업이 완료됐을 때 다시 BGSAVE 실행</li>
</ul>
</li>
<li><strong>LASTSAVE</strong>: RDB 파일이 정상적으로 저장됐는지 확인, 마지막으로 RDB 파일이 저장된 시점을 유닉스 타임스탬프로 반환<h2 id="복제를-사용할-경우-자동으로-rdb-파일-생성">복제를 사용할 경우 자동으로 RDB 파일 생성</h2>
</li>
<li><strong>REPLICAOF</strong>: 복제본에서 이 커맨드를 이용해 복제를 요청하면 마스터 노드에서는 RDB 파일을 새로 생성해 복제본에 전달</li>
<li>복제 연결을 처음 시작했을 때뿐만 아니라 이미 복제 연결이 되어 있는 상태에서도 상황에 따라 마스터에서는 언제든지 RDB 파일을 재생성할 수 있음<h1 id="3-aof-방식의-데이터-백업">3. AOF 방식의 데이터 백업</h1>
</li>
</ul>
<hr>
<p>AOF는 레디스 인스턴스에서 수행된 모든 쓰기 작업의 로그를 차례대로 기록</p>
<pre><code>appendonly yes    // AOF 파일에 주기적으로 데이터 저장
appendfilename &quot;appendonly.aof&quot;
appenddirname &quot;appendonlydir&quot;</code></pre><ul>
<li>AOF 파일에는 메모리상의 데이터가 변경되는 커맨드만 기록됨</li>
<li>항상 AOF 파일이 사용자가 실행한 커맨드를 그대로 저장하는 것은 아님<ul>
<li>BRPOP -&gt; RPOP</li>
<li>INCRBYFLOAT -&gt; 증분 후의 값을 직접 SET</li>
</ul>
</li>
<li>AOF는 실행되는 커맨드가 파일 뒤쪽에 계속 추가되는 방식으로 동작하기 때문에 인스턴스가 실행되는 시간에 비례해서 파일 크기 계속 증가<h2 id="aof-파일을-재구성하는-방법">AOF 파일을 재구성하는 방법</h2>
</li>
<li>AOF 파일을 이용한 백업 기능을 안정적으로 사용하려면 점점 커지는 파일을 주기적으로 압축시키는 재구성(rewrite) 작업 필요</li>
<li>재구성은 레디스 메모리에 있는 데이터를 읽어와서 새로운 파일로 저장하는 형태로 동작</li>
<li>aof-use-rdb-preamble yes -&gt; RDB 파일 형태로 저장</li>
<li>AOF 파일을 재구성할 때에도 fork를 이용해 자식 프로세스를 생성하며, 이 자식 프로세스가 AOF 파일을 재구성해 저장</li>
<li>버전 7 이전<ul>
<li>AOF는 하나의 파일로 관리</li>
<li>RDB 파일이 저장되는 동안 데이터가 변경된 동일한 로그가 AOF와 인메모리 버퍼에 이중으로 저장됨</li>
<li>하나의 AOF 파일 내에 바이너리 형태와 RESP의 텍스트 형태의 데이터가 함께 저장돼 수동으로 AOF 파일을 처리할 때 관리 복잡</li>
</ul>
</li>
<li>버전 7 이후 <ul>
<li>AOF는 기본이 되는 바이너리 형태의 RDB 파일, 증가하는 RESP의 텍스트 형태의 AOF 파일로 나누어 데이터 관리</li>
<li>현재 레디스가 바라보고 있는 파일이 어떤 것인지 나타내는 매니페스트 파일</li>
</ul>
</li>
<li>AOF 파일 재구성 과정은 모두 순차 입출력만 사용하기 때문에 디스크에 접근하는 모든 과정이 효율적<ul>
<li>파일 내에서 직접 데이터를 검색할 필요가 없기 때문에 랜덤 입출력을 고려할 이유 없음<h2 id="자동-aof-재구성">자동 AOF 재구성</h2>
</li>
</ul>
</li>
<li>auto-aof-rewrite-percentage: AOF 파일을 다시 쓰기 위한 시점을 정하기 위한 옵션</li>
<li>auto-aof-rewrite-min-size: 재구성된 이후의 AOF 파일의 최소 크기 지정 옵션</li>
<li>마지막으로 작성된 AOF 파일 크기를 기준으로 재구성하되, 적어도 AOF 파일이 특정 크기 이상일 때에만 재구성을 하도록 지정해 비효율적인 작업을 최소화<h2 id="수동-aof-재구성">수동 AOF 재구성</h2>
</li>
<li><strong>BGREWRITEAOF</strong>: 원하는 시점에 직접 AOF 파일 재구성. 자동으로 재구성할 때와 동일하게 동작<h2 id="aof-타임스탬프">AOF 타임스탬프</h2>
</li>
<li>aof-timestamp-enabled 옵션을 활성화 시키면 AOF 데이터가 저장될 때 타임스탬프도 함께 저장됨</li>
<li>redis-check-aof 프로그램을 사용해 데이터 복구<h2 id="aof-파일-복원">AOF 파일 복원</h2>
</li>
<li>redis-check-aof 프로그램은 AOF 파일이 손생됐을 때도 사용 가능</li>
<li>의도치 않은 서버의 장애 발생 시 AOF 파일 작성 도중 레디스가 중지됐을 가능성 존재<h2 id="aof-파일의-안전성">AOF 파일의 안전성</h2>
</li>
<li>파일 저장 내구성 제어 옵션<ul>
<li>APPENDFSYNC no: AOF 데이터를 저장할 때 WRITE 시스템 콜 호출. 데이터는 커널 영역에 데이터가 잘 저장되는지만 확인하기 때문에 쓰기 성능 가장 빠름</li>
<li>APPENDFSYNC always: AOF 데이터를 저장할 때 항상 WRITE와 FSYNC 시스템 콜 함께 호출. 매번 쓰고자 하는 데이터가 파일에 정확하게 저장되는 것을 기다리기 때문에 쓰기 성능 가장 느림</li>
<li>APPENDFSYNC everysec: 데이터를 저장할 때 WRITE 시스템 콜을 호출하며, 1초에 한번씩 FSYNC 시스템 콜을 호출. 성능은 no 옵션을 사용했을 때와 거의 비슷<h1 id="4-백업을-사용할-때-주의할-점">4. 백업을 사용할 때 주의할 점</h1>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>인스턴스의 maxmemory 값은 실제 서버 메모리보다 여유를 갖고 설정하는 것이 좋음</li>
<li>레디스는 Copy-On-Write(COW) 방식을 이용해 메모리상의 데이터를 하나 더 복사하는 방법을 이용해 백업을 진행하면서도 클라이언트의 요청 사항을 받아 메모리의 데이터를 읽고 수정하는 작업 진행</li>
<li>maxmemory 값을 너무 크게 설정한 경우(최악의 경우 기존 메모리 용량의 2배 사용), 레디스의 copy-on-write 동작으로 인해 OS 메모리가 가득차 OOM(Out Of Memory) 문제로 서버가 다운될 수 있음</li>
<li>RDB 스냅샷을 저장하는 도중엔 AOF의 재구성 기능을 사용할 수 없고, AOF 재구성이 진행될 때에는 BGSAVE를 실행할 수 없음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]레디스를 메시지 브로커로 사용하기]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4%EB%A5%BC-%EB%A9%94%EC%8B%9C%EC%A7%80-%EB%B8%8C%EB%A1%9C%EC%BB%A4%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4%EB%A5%BC-%EB%A9%94%EC%8B%9C%EC%A7%80-%EB%B8%8C%EB%A1%9C%EC%BB%A4%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 29 Feb 2024 07:58:25 GMT</pubDate>
            <description><![CDATA[<ul>
<li>모듈이 서로 느슨하고 적절하게 연결시킨 구조 선호 ➡️ 메시지 브로커 필요</li>
<li>모듈 간의 통신에서는 되도록 비동기 통신(async) 사용 권장</li>
<li>메시지 브로커의 종류<ul>
<li>메시징 큐</li>
<li>이벤트 스트림</li>
</ul>
</li>
</ul>
<h1 id="1-메시징-큐와-이벤트-스트림">1. 메시징 큐와 이벤트 스트림</h1>
<hr>
<blockquote>
<p>메시징 큐</p>
</blockquote>
<ul>
<li>생산자(producer): 데이터를 생성</li>
<li>소비자(consumer): 데이터를 수신</li>
</ul>
<blockquote>
<p>이벤트 스트림</p>
</blockquote>
<ul>
<li>발행자(publisher): 데이터 생성</li>
<li>수신자(subscriber): 데이터 조회</li>
</ul>
<h2 id="메시징-큐-vs-이벤트-스트림">메시징 큐 vs. 이벤트 스트림</h2>
<ol>
<li>방향성<ul>
<li>메시징 쿠의 생산자는 소비자의 큐로 데이터를 직접 푸시하기 때문에 2개의 서비스에 같은 메시지를 보낼 때 2번 푸시해야 함</li>
<li>이벤트 스트림에서 발행자는 스트림의 특정 저장소에 하나의 메시지를 보낼 수 있고, 메시지를 읽어가고자 하는 수신자들은 스트림에서 같은 메시지를 풀(pull)해 갈 수 있기 때문에 메시지를 복제해서 저장하지 않아도 됨</li>
</ul>
</li>
<li>데이터의 영속성<ul>
<li>메시징 큐에서는 소비자가 데이터를 읽어갈 때 큐에서 데이터 삭제</li>
<li>이벤트 스트림에서 구독자가 읽어간 데이터는 바로 삭제되지 않고, 저장소의 설정에 따라 특정 기간 동안 저장됨</li>
</ul>
</li>
</ol>
<blockquote>
<p>메시징 큐는 일대일 상황에서 한 서비스가 다른 서비스에게 동작을 지시할 때 유용
 스트림은 다대다 상황에서 유리함</p>
</blockquote>
<h2 id="레디스를-메시지-브로커로-사용하기">레디스를 메시지 브로커로 사용하기</h2>
<ul>
<li>레디스의 pub/sub 기능을 이용해 메시지 브로커 구현</li>
<li>레디스 pub/sub<ul>
<li>모든 데이터는 한 번 채널 전체에 전파된 뒤 삭제(일회성)</li>
<li>메시지가 잘 전달됐는지 등의 정보 보장 X(fire-and-forget 패턴에 사용됨)</li>
<li>c.f) fire-and-forget 패턴: 비동기 프로그래밍에서 사용되는 디자인 패턴으로, 어떤 작업을 실행하고 그 결과에 대한 응답을 기다리지 않고 바로 다음 코드를 실행하는 것</li>
</ul>
</li>
<li>레디스의 list와 stream을 이용해 각각 메시징 큐와 이벤트 스트림으로 사용하기 알맞음</li>
</ul>
<h1 id="2-레디스의-pubsub">2. 레디스의 pub/sub</h1>
<hr>
<ul>
<li>레디스의 pub/sub은 매우 가볍기 때문에 최소한의 메시지 전달 기능만 제공<ul>
<li>발행자는 메시지를 채널로 보낼 수 있을 뿐, 어떤 구독자가 메시지를 읽어가는지, 정상적으로 모든 구독자에게 메시지가 전달됐는지 확인할 수 없음</li>
<li>구독자는 메시지를 받을 수 있지만 해당 메시지가 언제 어떤 발행자에 의해 생성되었는지 등 메타데이터는 알 수 없음</li>
<li>한 번 전파된 데이터는 레디스에 저장 X ➡️ 정합성이 중요한 데이터를 전달하기에는 적합하지 않음</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>PUBLISH</td>
<td>데이터 전파(발행자)</td>
</tr>
<tr>
<td>SUBSCRIBE</td>
<td>특정 채널 구독(구독자)</td>
</tr>
</tbody></table>
<h2 id="클러스터-구조에서의-pubsub">클러스터 구조에서의 pub/sub</h2>
<ul>
<li>클러스터: 레디스가 자체적으로 제공하는 데이터 분산 형태의 구조</li>
<li>메시지를 발행하면 해당 메시지는 클러스터에 속한 모든 노드에 자동으로 전달</li>
<li>클러스터는 주로 대규모 서비스에서 데이터를 분산해서 저장하고 처리하기 위해 도입</li>
<li>레디스 클러스터 내에서 pub/sub을 사용할 때 메시지가 모든 레디스 노드에 복제되는 방식은 클러스터 환경의 핵심 목표와는 부합하지 않음
➡️ 불필요한 리소스 사용, 네트워크 부하<h2 id="sharded-pubsub">sharded pub/sub</h2>
</li>
<li>각 채널은 슬롯에 매핑</li>
<li>클러스터에서 키가 슬롯에 할당되는 것과 동일한 방식으로 채널 할당, 같은 슬롯을 가지고 있는 노드 간에만 pub/sub 메시지 전파</li>
<li>클러스터 구조에서 pub/sub 되는 메시지는 모든 노드로 전파되지 않기 때문에 불필요한 복제를 줄여 자원 절약 가능<h1 id="3-레디스의-list를-메시징-큐로-사용하기">3. 레디스의 list를 메시징 큐로 사용하기</h1>
</li>
</ul>
<hr>
<h2 id="list의-ex-기능">list의 EX 기능</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>RPUSHX</td>
<td>데이터를 저장하고자 하는 list가 이미 존재할 때에만 아이템 추가</td>
</tr>
</tbody></table>
<ul>
<li>SNS 타임라인의 경우 이미 캐시된(이미 키가 존재하는) 타임라인에만 데이터를 추가할 수 있음(자주 사용하지 않는 사람의 타임라인 캐시 데이터를 관리할 필요가 없음) </li>
<li>사용자의 캐시가 이미 존재하는지 유무를 애플리케이션에서 확인하는 불필요한 확인 과정이 없어 성능 향상 가능<h2 id="list의-블로킹-기능">list의 블로킹 기능</h2>
</li>
<li>이벤트 기반(event-driven) 구조: 이벤트 루프를 돌며 신규로 처리할 이벤트가 있는지 체크, 새로운 이벤트가 없을 경우 정해진 시간(polling interval) 동안 대기한 뒤 다시 이벤트 큐에 데이터가 있는지 확인하는 과정 반복(polling)</li>
<li>단점: 폴링 프로세스가 진행되는 동안 애플리케이션과 큐의 리소스가 불필요하게 소모, 폴링 인터벌 동안 대기한 뒤 다시 확인하는 과정을 거치기 때문에 이벤트를 즉시 처리할 수 없음</li>
<li><strong>BRPOP, BLPOP</strong>: list에 데이터가 있으면 즉시 반환, 만약 데이터가 없을 경우 기다려서 들어온 값을 반환 or 클라이언트가 설정한 타임아웃 시간 만큼 대기한 후 nil 반환</li>
</ul>
<h2 id="list를-이용한-원형-큐">list를 이용한 원형 큐</h2>
<ul>
<li>특정 아이템을 반복 접근해야 하는 클라이언트, 혹은 여러 개의 클라이언트가 병렬적으로 같은 아이템에 접근 해야 하는 클라이언트의 경우 원형 큐(circular queue)를 이용해 아이템 처리</li>
<li><strong>RPOPPUSH</strong><h1 id="4-stream">4. Stream</h1>
</li>
</ul>
<hr>
<h2 id="레디스의-stream과-아파치-카프카">레디스의 Stream과 아파치 카프카</h2>
<ul>
<li>Stream: 대용량, 대규모의 메시징 데이터를 빠르게 처리할 수 있도록 설계됨, 데이터를 계속해서 추가하는 방식으로 저장되는(append-only) 자료 구조</li>
<li>stream 활용<ul>
<li>백엔드 개발자들은 대량의 데이터를 효율적으로 처리하는 플랫폼으로 활용</li>
<li>데이터 엔지니어들은 여러 생산자가 생성한 데이터를 다양한 소비자가 처리할 수 있게 지원하는 데이터 저장소 및 중간 큐잉 시스템으로 활용</li>
</ul>
</li>
</ul>
<h2 id="스트림이란">스트림이란?</h2>
<ul>
<li>연속적인 데이터의 흐름, 일정한 데이터 조각의 연속<h2 id="데이터의-저장">데이터의 저장</h2>
<h3 id="메시지의-저장과-식별">메시지의 저장과 식별</h3>
</li>
<li>카프카<ul>
<li>토픽: 각각의 분리된 스트림, 같은 데이터를 관리하는 하나의 그룹</li>
<li>각 메시지는 0부터 시작해 증가하는 시퀀스 넘버로 식별</li>
<li>시퀀스 넘저는 토픽 내의 파티션 안에서만 유니크하게 증가하기 때문에 토픽이 1개 이상의 파티션을 갖는다면 메시지는 하나의 토픽 내에서 유니크하게 식별되지 않음</li>
</ul>
</li>
<li>레디스 stream<ul>
<li>각 메시지는 시간과 관련된 유니크한 ID를 가지며, 이 값은 중복되지 않음<h3 id="스트림-생성과-데이터-입력">스트림 생성과 데이터 입력</h3>
</li>
</ul>
</li>
<li>카프카<ul>
<li>각 스트림은 토픽으로 관리됨</li>
<li>생성자는 데이터를 토픽에 푸시, 소비자는 토픽에서 데이터 읽음</li>
<li>토픽 생성 후 프로듀서를 이용해 메시지 보냄</li>
</ul>
</li>
<li>레디스 stream<ul>
<li>따로 stream을 생성하는 과정 필요 X</li>
<li><strong>XADD</strong> 커맨드 이용</li>
<li>데이터는 hash 자료 구조처럼 필드-값 쌍으로 저장되므로 각 메시지마다 유동적인 데이터 저장 가능<h3 id="데이터의-조회">데이터의 조회</h3>
</li>
</ul>
</li>
<li>카프카<ul>
<li>소비자는 특정 토픽을 실시간으로 리스닝하며, 새롭게 토픽에 저장되는 메시지를 받을 수 있음</li>
</ul>
</li>
<li>레디스 stream<ul>
<li>실시간으로 처리되는 데이터 리스닝(<strong>XREAD</strong>)</li>
<li>ID를 이용해 필요한 데이터 검색(<strong>XRANGE, XREVRANGE</strong>)<h3 id="소비자와-소비자-그룹">소비자와 소비자 그룹</h3>
</li>
</ul>
</li>
<li>팬아웃(fan-out): 같은 데이터를 여러 소비자에게 전달하는 것</li>
<li>같은 데이터를 여러 소비자가 나눠서 가져가기 위해서는?<ul>
<li>같은 역할을 하는 여러 개의 소비자를 이용해 메시지를 병렬 처리함으로써 서비스의 처리 성능을 높일 수 있음</li>
</ul>
</li>
<li>레디스 stream<ul>
<li>데이터가 저장될 때마다 고유한 ID(시간)를 부여받아 순서대로 저장됨</li>
<li>소비자에게 데이터가 전달될 때 순서 항상 보장(시간순)</li>
</ul>
</li>
<li>카프카<ul>
<li>유니크 키는 파티션 내에서만 보장되기 때문에 소비자가 여러 파티션에서 토픽을 읽어갈 때에는 데이터의 순서를 보장할 수 없음</li>
<li>데이터의 정렬이 보장되지 않기 때문에 메시지 순서 보장을 위해 소비자 그룹 사용<h4 id="소비자-그룹">소비자 그룹</h4>
</li>
</ul>
</li>
<li>카프카<ul>
<li>소비자 그룹에 여러 소비자 추가 가능</li>
<li>소비자는 토픽 내의 파티션과 일대일로 연결됨</li>
<li>파티션을 이용해 소비자의 부하 분산 관리</li>
</ul>
</li>
<li>레디스 stream<ul>
<li>레디스 stream은 메시지가 전달되는 순서가 보장되기 때문에 카프카의 소비자 그룹과는 약간 다름</li>
<li>소비자 그룹 내의 한 소비자는 다른 소비자가 아직 읽지 않은 데이터만을 읽어감</li>
<li><strong>XGROUP</strong>: 소비자 그룹 생성</li>
<li><strong>XREADGROUP</strong>: 소비자 그룹 이용해 데이터 읽음, 마스터에서만 호출 가능</li>
<li>stream의 상태를 나타내는 개념으로 간주</li>
<li>stream과 소비자 그룹은 독립적으로 동작 가능</li>
<li>하나의 소비자 그룹에서 여러 개의 stream 리스닝 가능</li>
<li>파티션이라는 분할 없이도 소비자 그룹이라는 개념을 이용해 여러 소비자에게 데이터 분산 가능<h3 id="ack와-보류-리스트">ACK와 보류 리스트</h3>
</li>
<li>레디스 stream<ul>
<li>각 소비자별로 읽어간 메시지에 대한 리스트를 새로 생성하며, 마지막으로 읽어간 데이터의 ID로 last_delivered_id 값 업데이트(중복 전달 방지)</li>
<li>보류 리스트를 이용해 소비자가 처리한 데이터 파악</li>
<li>데이터가 처리됐다는 뜻의 ACK를 보내면 보류 리스트에서 해당 메시지 삭제</li>
</ul>
</li>
<li>카프카<ul>
<li>파티션별 오프셋 관리</li>
<li>__consumer_offsets: 소비자가 지정된 토픽의 특정 파티션의 메시지를 읽으면 소비자 그룹, 토픽, 파티션 내용이 통합되어 저장됨</li>
<li>오프셋은 소비자가 다음으로 읽어야 할 위치(마지막으로 읽은 위치 X)<h4 id="레디스-stream의-메시지-보증-전략">레디스 stream의 메시지 보증 전략</h4>
</li>
</ul>
</li>
<li>at most once: 메시지를 최소 한 번 보내는 것, 메시지를 받자마자 실제 처리하기 전에 먼저 ACK 보냄</li>
<li>at least once: 받은 메시지를 모두 처리한 뒤 ACK, 실제로 메시지가 처리됐지만 ACK를 전송하기 전에 소비자가 종료되는 상황 발생 가능</li>
<li>exactly once: 모든 메시지가 무조건 한 번씩 전송되는 것 보장, 이미 처리된 메시지인지 아닌지를 확인하는 과정 필요<h3 id="메시지의-재할당">메시지의 재할당</h3>
</li>
</ul>
</li>
<li><strong>XCLAIM</strong>: 메시지의 소유권을 다른 소비자에게 할당, 최소 대기 시간 지정</li>
<li>메시지가 보류 상태로 머무른 시간이 최소 대기 시간을 초과한 경우에만 소유권을 변경할 수 있도록 해서 같은 메시지가 2개의 다른 소비자에게 중복으로 할당되는 것 방지<h3 id="메시지의-자동-재할당">메시지의 자동 재할당</h3>
</li>
<li>XAUTOCLAIM<ul>
<li>소비자가 직접 보류했던 메시지 중 하나를 자동으로 가져와서 처리</li>
<li>할당 대기 중인 다음 메시지의 ID를 반환하는 방식으로 동작하기 때문에 반복적 호출 가능</li>
<li>지정한 소비자 그룹에서 최소 대기 시간을 만족하는 보류 중인 메시지가 있다면 지정한 소비자에게 소유권을 재할당하는 방식으로 동작<h3 id="메시지의-수동-재할당">메시지의 수동 재할당</h3>
</li>
</ul>
</li>
<li>stream 내 각 메시지는 counter 값 가짐</li>
<li>counter는 XREADGROUP을 이용해 소비자에게 할당하거나 XCLAIM을 이용해 재할당할 경우 1씩 증가</li>
<li>counter가 특정 값에 도달하면 이 메시지를 특수한 다른 stream으로 보내, 관리자가 추후에 처리할 수 있도록 함 ➡️ dead letter<h3 id="stream-상태-확인">stream 상태 확인</h3>
</li>
<li><strong>XINFO</strong><ul>
<li><strong>XINFO consumer</strong>: 특정 소비자 그룹에 속한 소비자의 정보</li>
<li><strong>XINFO GROUPS</strong>: stream에 속한 전체 소비자 그룹 list</li>
<li><strong>XINFO STREAM</strong>: stream 자체의 정보</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]레디스를 캐시로 사용하기]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4%EB%A5%BC-%EC%BA%90%EC%8B%9C%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4%EB%A5%BC-%EC%BA%90%EC%8B%9C%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 02 Jan 2024 08:58:08 GMT</pubDate>
            <description><![CDATA[<h1 id="레디스와-캐시">레디스와 캐시</h1>
<hr>
<h2 id="캐시란">캐시란?</h2>
<blockquote>
<p>캐시란 데이터의 원본보다 더 빠르고 효율적으로 액세스할 수 있는 임시 데이터 저장소</p>
</blockquote>
<p>애플리케이션이 다음 조건을 만족시킨다면 캐시를 도입했을 때 성능을 효과적으로 개선할 수 있음</p>
<ul>
<li>원본 데이터 저장소에서 원하는 데이터를 찾기 위해 검색하는 시간이 오래 걸리거나, 매번 계산을 통해 데이터를 가져와야 함</li>
<li>캐시에서 데이터를 가져오는 것이 원본 데이터 저장소 데이터를 요청하는 것보다 빨라야 함</li>
<li>캐시에 저장된 데이터는 잘 변하지 않는 데이터</li>
<li>캐시에 저장된 데이터는 자주 검색되는 데이터</li>
</ul>
<br/>

<p>위 조건을 만족하는 이상적인 캐시는 애플리케이션이 직면하게 되는 많은 문제점 해결 가능</p>
<ul>
<li>원본 데이터 저장소 데이터를 가지고 오는 시간을 단축시키기 때문에 애플리케이션의 응답 속도 줄일 수 있음</li>
<li>캐시는 데이터의 복제본을 저장하는 저장소이기 때문에 원본 데이터 저장소에서 데이터를 읽는 커넥션을 줄일 수 있음</li>
<li>캐시를 적절하게 배치함으로써 애플리케이션의 확장 가능</li>
<li>원본 데이터 저장소에서 데이터를 가져올 때 CPU와 메모리 등의 리소스를 많이 사용했다면 캐시를 사용함으로 애플리케이션 자체의 리소스를 줄일 수 있음<ul>
<li>같은 값을 도출하기 위해 같은 계산을 할 필요가 없으므로 리소스 최적화</li>
</ul>
</li>
<li>중요한 데이터를 캐시에 올려두고 사용할 때 원본 데이터 저장소에 장애가 발생해 접근할 수 없은 상황이 발생하더라도 캐시에서 데이터를 가지고 올 수 있기 때문에 장애 시간을 줄일 수 있음</li>
</ul>
<h2 id="캐시로서의-레디스">캐시로서의 레디스</h2>
<p><strong>1. 사용이 간단</strong></p>
<ul>
<li>키-값 형태로 저장하므로 데이터 저장 및 반환 간단</li>
<li>자체적으로 다양한 자료 구조를 제공해 애플리케이션에서 사용하던 자료 구조 변환 없이 바로 저장</li>
</ul>
<p><strong>2. 모든 데이터를 메모리에 저장하는 인메모리 데이터 저장소이기 때문에 데이터를 검색하고 반환하는 것이 빠름</strong></p>
<ul>
<li>평균 읽기 및 쓰기 작업 속도가 1ms 미만, 초당 수백만 건의 작업 가능</li>
</ul>
<p><strong>3. 자체적으로 고가용성 기능을 가지고 있는 솔루션</strong></p>
<ul>
<li>일부 캐싱 전략에서는 캐시에 접근할 수 없게 되면 서비스의 장애로 이어짐 ➡️ 캐시 저장소도 일반적인 데이터 저장소와 같이 안정적으로 운영될 수 있는 조건을 갖추는 것이 좋음</li>
<li>레디스의 센티널 또는 클러스터 기능을 사용하면 마스터 노드의 장애를 자동으로 감지해 페일오버를 발생시키기 때문에 운영자의 개입 없이 캐시는 정상으로 유지될 수 있어 가용성 높음</li>
</ul>
<p><strong>4. 클러스터를 사용하면 캐시의 스케일 아웃 쉽게 처리 가능</strong></p>
<ul>
<li>자체 샤딩 솔루션인 클러스터를 사용하면 수평 확장이 간단해짐</li>
</ul>
<h2 id="캐싱-전략">캐싱 전략</h2>
<h3 id="읽기-전략---look-aside">읽기 전략 - look aside</h3>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/e67dbbb4-3457-4dd4-8e15-b81d035c65e3/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/3a1af7fc-17a1-40d9-ae10-43027848581b/image.png" alt=""></p>
<ul>
<li>애플리케이션에서 데이터를 읽어갈 때 주로 사용하는 전략</li>
<li><strong>캐시 히트(cache hit)</strong>: 애플리케이션은 찾고자 하는 데이터가 먼저 캐시에 있는지를 확인한 뒤, 캐시에 데이터가 있으면 캐시에서 데이터를 읽어옴</li>
<li><strong>캐시 미스(cache miss)</strong>: 찾고자 하는 데이터가 캐시에 없음</li>
<li>레디스에 문제가 생겨 접근을 할 수 없는 상황이 발생하더라도 바로 서비스 장애로 이어지지 않고 데이터베이스에서 데이터를 가지고 올 수 있음</li>
<li>기존에 애플리케이션에서 레디스를 통해 데이터를 가져오는 연결이 매우 많았다면 모든 커넥션에 한꺼번에 원본 데이터베이스로 몰려 많은 부하 발생 ➡️ 원본 데이터베이스의 응답이 느려지거나 리소스를 많이 차지하는 이슈</li>
<li><strong>lazy loading</strong>: 찾고자 하는 데이터가 레디스에 없을 때에만 레디스에 데이터가 저장됨</li>
<li>기존 사용 중인 서비스에 처음 레디스를 투입하거나 데이터베이스에만 새로운 데이터를 저장하는 경우 매번 캐시 미스가 일어나 성능 저하 ➡️ 캐시 워밍을 통해 해결<ul>
<li><strong>캐시 워밍(cache warming)</strong>: 미리 데이터베이스에서 캐시로 데이터를 밀어넣어주는 작업
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/5ea73d13-58a0-4665-9d6a-0f08e4afc0b1/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="쓰기-전략과-캐시의-일관성">쓰기 전략과 캐시의 일관성</h3>
<p>캐시에 원본 데이터와 동일한 값을 갖도록 유지하는 것이 필수적!
캐시 불일치(cache inconsitency): 데이터가 변경될 때 원본 데이터베이스에만 업데이트 되어 캐시에는 변경된 값이 반영되지 않아 데이터 간 불일치가 발생한 것</p>
<p><strong>1. write through</strong>
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/045ce168-31dc-4133-8b48-1ea5794d5548/image.png" alt=""></p>
<ul>
<li>데이터베이스에 업데이트할 때마다 매번 캐시에도 데이터를 함께 업데이트 시키는 방식</li>
<li>캐시는 항상 최신 데이터를 가지고 있을 수 있다는 장점</li>
<li>데이터는 매번 2개의 저장소에 저장되어야 하기 때문에 데이터를 쓸 때마다 시간이 많이 소요될 수 있다는 단점</li>
<li>다시 사용되지 않을 데이터가 매번 업데이트 될 수 있기 때문에 데이터를 저장할 때 만료 시간을 사용하는 것이 좋음</li>
</ul>
<p><strong>2. cache invalidation</strong>
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/6cdb427f-2d51-4a2e-89b2-4e200bc52668/image.png" alt=""></p>
<ul>
<li>데이터베이스에 값을 업데이트할 때마다 캐시에서는 데이터를 삭제하는 전략</li>
<li>저장소에서 특정 데이털르 삭제하는 것이 새로운 데이터를 저장하는 것보다 훨씬 리소스를 적게 사용</li>
</ul>
<p><strong>3. write behind(write back)</strong>
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/fe983809-e783-479c-9ff1-5a85fb7a38cd/image.png" alt=""></p>
<ul>
<li>먼저 데이터를 빠르게 접근할 수 있는 캐시에 업데이트한 뒤, 이후에는 건수나 특정 시간 간격 등에 따라 비동기적으로 데이터베이스에 업데이트</li>
<li>저장되는 데이터가 실시간으로 정확한 데이터가 아니어도 되는 경우에 사용</li>
</ul>
<h1 id="캐시에서의-데이터-흐름">캐시에서의 데이터 흐름</h1>
<blockquote>
<p>캐시는 가득 차지 않게 일정 양의 데이터를 유지해야 하며 계속해서 새로운 데이터가 저장되고 기존 데이터는 삭제될 수 있도록 관리해야 함 ➡️ 적절한 시간의 TTL을 지정하는 것이 좋음</p>
</blockquote>
<hr>
<h2 id="만료-시간">만료 시간</h2>
<ul>
<li>초(second) 단위로 표현</li>
<li>키에 만료 시간을 설정하면 데이터의 수명을 관리하고 메모리 공간을 효율적으로 사용하는 데 도움을 줌</li>
<li>만료 시간 설정<ul>
<li>EXPIRE 커맨드: 만료 시간 설정(초 단위로 동작)</li>
<li>SET 커맨드의 EX 옵션: 데이터의 저장과 동시에 만료 시간을 설정</li>
<li>TTL 커맨드: 만료 시간 확인(초 단위로 동작)</li>
<li>PTTL, PEXPIRE 커맨드는 밀리세컨드 단위로 동작</li>
<li>INCR 커맨드로 데이터를 조작하거나 RENAME을 이용해 키의 이름을 바꾸더라도 설정된 만료 시간은 변경되지 않음</li>
<li>기존 키에 새로운 값을 저장해 키를 덮어 쓸 때에는 이전에 설정한 만료 시간은 유지되지 않고 사라짐</li>
</ul>
</li>
</ul>
<blockquote>
<p>레디스에서 키가 만료되었다고 해도 바로 메모리에서 삭제되는 것은 아님.
만료된 키를 곧바로 삭제하지 않기 때문에 키를 삭제하는 데 들어가는 리소스를 줄일 수 있지만, 그 만큼 메모리를 더 사용할 가능성 존재.</p>
</blockquote>
<ul>
<li>passive 방식<ul>
<li>클라이언트가 키에 접근하고자 할 때 키가 만료되었다면 메모리에서 수동적으로 삭제</li>
</ul>
</li>
<li>active 방식<ul>
<li>TTL 값이 있는 키 중 20개를 랜덤하게 뽑아낸 뒤, 만료된 키를 모두 메모리에서 삭제</li>
<li>만약 25% 이상의 키가 삭제되었다면 다시 20개의 키를 랜덤하게 뽑은 뒤 확인하고, 아니라면 뽑아놓은 20개의 키 집합에서 다시 확인</li>
</ul>
</li>
</ul>
<h2 id="메모리-관리와-maxmemory-policy-설정">메모리 관리와 maxmemory-policy 설정</h2>
<h3 id="noeviction">Noeviction</h3>
<ul>
<li>기본값</li>
<li>레디스에 데이터가 가득 차더라도 임의로 데이터를 삭제하지 않고 더 이상 레디스에 데이터를 저장할 수 없다는 에러 반환</li>
<li>캐시에 데이터를 저장하지 못해 에러가 발생할 경우 관리자가 데이터를 직접 지워야 함</li>
<li>데이터의 관리를 캐시에 맡기지 않고, 애플리케이션 측에서 관리<h3 id="lruleast-recently-used-eviction">LRU(Least-Recently Used) eviction</h3>
</li>
<li>레디스에 데이터가 가득 찼을 때 가장 최근에 사용되지 않은 데이터부터 삭제하는 정책</li>
<li>최근에 액세스 되지 않은 데이터는 나중에도 액세스 될 가능성이 낮을 것이라는 가정을 전제</li>
<li>근사 알고리즘 이용</li>
<li><strong>volatile-lru</strong><ul>
<li>만료 시간이 설정되어 있는 키에 한해서 LRU 방식으로 키를 삭제</li>
<li>만약 레디스 내부에 저장된 키에 모두 만료 시간이 지정되어 있지 않다면 noeviction 상황과 동일</li>
</ul>
</li>
<li><strong>allkeys-lru</strong><ul>
<li>모든 키에 대해 LRU 알고리즘을 이용해 데이터를 삭제하기 때문에 메모리가 꽉 차있을 때 장애가 발생할 상황 방지<h3 id="lfuleast-frequently-used-eviction">LFU(Least-Frequently Used) eviction</h3>
</li>
</ul>
</li>
<li>레디스에 데이터가 가득 찼을 때 가장 자주 사용되지 않은 데이터부터 삭제하는 정책</li>
<li>자주 사용되지 않은 데이터는 나중에도 액세스될 가능성이 낮을 것이라는 가정 전제</li>
<li>근사 알고리즘 이용</li>
<li><strong>volatile-lfu</strong><ul>
<li>만료 시간이 설정되어 있는 키에 한해서 LFU 방식으로 키를 삭제</li>
</ul>
</li>
<li><strong>allkeys-lfu</strong><ul>
<li>모든 키에 대해 LFU 알고리즘을 이용해 데이터 삭제<h3 id="random-eviction">RANDOM eviction</h3>
</li>
</ul>
</li>
<li>레디스에 저장된 키 중 하나를 임의로 골라내 삭제</li>
<li>삭제될 키 값을 계산하지 않아도 된다는 점에서 부하를 줄여줌</li>
<li>랜덤으로 데이터를 삭제하기 때문에 나중에 사용될 수도 있는 데이터를 삭제할 가능성이 높아짐 ➡️ 데이터 저장소에서 다시 데이터를 가지고 와서 캐시에 넣어주는 작업 불필요</li>
<li>굳이 레디스의 부하를 줄이기 위한다는 이유로 사용하는 것을 권장하지 않음</li>
<li><strong>volatile-random</strong>: 만료 시간이 설정되어 있는 키에 한해 랜덤하게 키 삭제</li>
<li><strong>allkeys-random</strong>: 모든 키에 대해 랜덤하게 키 삭제<h3 id="volatile-ttl">volatile-ttl</h3>
</li>
<li>만료 시간이 가장 작은 키 삭제</li>
<li>근사 알고리즘 이용</li>
</ul>
<h2 id="캐시-스탬피드-현상">캐시 스탬피드 현상</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/329c2bc2-fd3a-413f-b261-e9192b4e7948/image.png" alt=""></p>
<blockquote>
<p>캐시 스탬피드(cache stampede): 캐시 공간은 한정되어 있으므로 저장된 데이터에 만료 시간을 설정하는데, 해당 데이터에 계속 읽기 요청이 들어오고 있을 때 캐시 만료시간이 닥치면 순간적으로 데이터베이스에 그 읽기 요청이 집중되고 그게 다시 레디스에 중복된 쓰기 요청으로 몰리게 됨</p>
</blockquote>
<ul>
<li>중복 읽기(duplicate read): 여러 애플리케이션에서 바라보던 키가 만료되어 삭제된다면 이 서버들은 한꺼번에 데이터베이스에 가서 데이터를 읽어옴</li>
<li>중복 쓰기(duplicate write): 중복 읽기 이후 각 애플리케이션에서 읽어온 데이터를 레디스에 쓰게 되면 여러 번 반복해서 쓰게 됨</li>
</ul>
<p>한번 캐시 스탬피드 현상이 발생하면 결과적으로 더 많은 데이터가 이 현상의 영향을 받게 됨 ➡️ 계단식 실패(cascading failure)</p>
<h3 id="계단식-실패를-줄이기-위한-방법">계단식 실패를 줄이기 위한 방법</h3>
<p><strong>1. 적절한 만료 시간 설정</strong></p>
<ul>
<li>여러 애플리케이션에서 한꺼번에 접근해야 하는 데이터이며, 반복적으로 사용되어야 하는 데이터라면 저장 시점부터 만료 시간을 충분히 길게 설정</li>
</ul>
<p><strong>2. 선 계산</strong></p>
<ul>
<li>키가 실제로 만료되기 전에 이 값을 미리 갱신해준다면 여러 애플리케이션에서 한꺼번에 데이터베이스에 접근해 데이터를 읽어오는 과정을 줄여 불필요한 프로세스 줄임</li>
</ul>
<p><strong>3. PER 알고리즘</strong></p>
<ul>
<li>PER(Probabilistic Early Recomputation) 알고리즘을 이용하면 캐시 값이 만료되기 전에 언제 데이터베이스에 접근해서 값을 읽어오면 되는지 최적으로 계산할 수 있음</li>
<li><code>currentTime - ( timeToCompute * beta * log(rand()) ) &gt; expiry</code><ul>
<li>currentTime: 현재 남은 만료 시간</li>
<li>timeToCompute: 캐시된 값을 다시 계산하는 데 걸리는 시간</li>
<li>beta: 기본적으로 1.0 보다 큰 값으로 설정 가능</li>
<li>rand(): 0과 1 사이의 랜덤 값을 반환하는 함수</li>
<li>expiry: 키를 재설정할 때 새로 넣어줄 만료 시간</li>
<li>currentTime에서 timeToCompute x beta x log(rand())를 빼서 얻은 값이 expiry 보다 크면 조건은 거짓(False)</li>
<li>currentTime에서 timeToCompute x beta x log(rand())를 빼서 얻은 값이 expiry 보다 작으면 조건은 참(True)<ul>
<li>데이터를 다시 계산하기 위해 데이터베이스로 이동</li>
<li>만료 시간이 가까워질수록 currentTime과 expiry 사이의 차이가 작아지며, rand() 함수가 반환한 무작위 값에 의존하기 때문에 조건이 참이 될 확률 높아짐</li>
</ul>
</li>
<li>데이터를 가져오는 과정에서 GET 대신 이 함수를 사용하는 것은 캐시 스탬피드 현상을 줄이고 성능을 최적화하는데 도움</li>
</ul>
</li>
</ul>
<h1 id="세션-스토어로서의-레디스">세션 스토어로서의 레디스</h1>
<hr>
<h2 id="세션이란">세션이란?</h2>
<blockquote>
<p>세션(session): 서비스를 사용하는 클라이언트의 상태 정보</p>
</blockquote>
<h2 id="세션-스토어가-필요한-이유">세션 스토어가 필요한 이유</h2>
<ul>
<li><strong>sticky session</strong>: 특정 웹 서버에 유저가 몰려 트래픽이 집중되는 상황이 발생하더라도 유저는 다른 서버를 사용할 수 없어, 결국 트래픽을 분산시킬 수 없는 상황
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/c0462dd8-c46d-490f-a762-1da7ce60f9e8/image.png" alt=""></li>
<li><strong>all-to-all 방법</strong>: 유저의 세션 정보를 모든 웹 서버에 복제해서 저장하는 방법<ul>
<li>유저를 여러 웹 서버에 분산시킬 수 있지만, 유저의 세션 데이터를 여러 서버로 복사되어 저장되기 때문에 불필요한 저장 공간 차지</li>
<li>하나의 유저는 한 번에 하나의 웹 서버에만 접속하기 때문에 다른 웹 서버에 저장된 유저의 세션 정보는 무의미</li>
<li>데이터를 복제하는 과정에서 불필요한 네트워크 트래픽 다수 발생
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/0d07df87-0c4e-43eb-9eb7-77bfce941389/image.png" alt=""></li>
</ul>
</li>
<li><strong>데이터베이스를 세션 스토어로 이용</strong><ul>
<li>세션 스토어의 응답 속도가 느려지면 클라이언트의 응답 속도 저하로 이어짐
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/66ebf6ed-e620-4a42-9c20-f115385dee81/image.png" alt=""></li>
</ul>
</li>
<li><strong>레디스를 세션 스토어로 이용</strong><ul>
<li>레디스를 세션 스토어로 사용하고 서버, 데이터베이스와 분리시켜 놓은 뒤 여러 서버에서 세션 스토어를 바라보도록 구성</li>
<li>유저는 세션 스토어에 구애받지 않고 어떤 웹 서버에 연결되더라도 동일한 세션 데이터 조회 가능 ➡️ 트래픽 효율적 분산, 데이터의 일관성 고려할 필요X</li>
<li>레디스는RDBMS보다 훨씬 빠르고 접근하기 간편하므로 데이터를 가볍게 저장할 수 있음</li>
<li>레디스의 hash 자료 구조는 세션 데이터 저장에 알맞음
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/17aa5be3-d3a2-406d-8475-4b8e7337b6bb/image.png" alt=""><h2 id="캐시와-세션의-차이">캐시와 세션의 차이</h2>
</li>
</ul>
</li>
<li>캐시에 저장된 데이터는 여러 애플리케이션에서 함께 사용</li>
<li>세션 스토어에 저장된 데이터는 여러 사용자 간 공유되지 않으며, 특정 사용자 ID에 한해 유효</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/68d6d2dc-49ed-42e4-96b0-93ae57318d02/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/a8791fb7-7b6a-4083-abb5-240912c3e05c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]레디스 자료 구조 활용 사례]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4-%EC%9E%90%EB%A3%8C-%EA%B5%AC%EC%A1%B0-%ED%99%9C%EC%9A%A9-%EC%82%AC%EB%A1%80</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4-%EC%9E%90%EB%A3%8C-%EA%B5%AC%EC%A1%B0-%ED%99%9C%EC%9A%A9-%EC%82%AC%EB%A1%80</guid>
            <pubDate>Tue, 02 Jan 2024 02:22:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>레디스 자료 구조에 내장된 함수를 이용해 원하는 기능을 사용하면 데이터를 애플리케이션의 메모리 영역으로 가져간 뒤 가공하는 데에 걸리는 시간을 줄일 수 있기 때문에 애플리케이션에서는 매우 짧은 대기 시간으로 엄청난 양의 작업을 처리할 수 있음</p>
</blockquote>
<h1 id="레디스-자료-구조-활용-사례">레디스 자료 구조 활용 사례</h1>
<hr>
<h2 id="sorted-set을-이용한-실시간-리더보드">sorted set을 이용한 실시간 리더보드</h2>
<ul>
<li>리더보드는 사용자의 스코어를 기반으로 데이터를 정렬하는 서비스이기 때문에 사용자의 증가에 따라 가공 데이터 증가</li>
<li>리더보드는 실시간으로 반영되어야 하는 데이터</li>
<li>레디스의 sorted set에서 데이터는 저장될 때부터 정렬되어 들어감</li>
<li>유저의 스코어를 sorted set의 가중치로 설정한다면 스코어를 기준으로 유저가 정렬됨 ➡️ 리더보드 데이터를 읽어오기 위해 매번 데이터 정렬할 필요 X</li>
<li>sorted set은 기본적으로 set이기 때문에 데이터가 중복 저장되지 않으며, 같은 아이템을 저장하고자 할 때 스코어가 다르면 기존 데이터의 스코어만 신규 입력한 스코어로 업데이트 됨</li>
</ul>
<h2 id="sorted-set을-이용한-최근-검색-기록">sorted set을 이용한 최근 검색 기록</h2>
<ul>
<li>RDBMS에서 최근 검색 기록을 가져올 때 검색한 시점을 기준으로 소팅(sorting)을 해야하기 때문에 사용자와 검색 기록이 늘어날 수록 많은 데이터를 테이블에서 관리해야 한다는 문제점 있음</li>
<li>sorted set은 중복을 허용하지 않으며, 유저가 검색한 시간을 스코어로 사용하면 검색 기록으로 정렬될 수 있음</li>
<li>sorted set의 음수 인덱스를 사용해서 매번 데이터를 저장할 때 아이템의 개수를 확인하고 삭제해야 하는 번거로움을 줄일 수 있음</li>
</ul>
<h2 id="sorted-set을-이용한-태그-기능">sorted set을 이용한 태그 기능</h2>
<ul>
<li>포스트 id를 기준으로 하는 set에 태그를 데이터로 넣어 특정 게시물이 어떤 태그와 연관되어 있는지 확인</li>
<li>태그를 기준으로 하는 set에 포스트 id를 데이터로 넣어 특정 태그를 포함한 게시물 확인</li>
</ul>
<h2 id="랜덤-데이터-추출">랜덤 데이터 추출</h2>
<ul>
<li>RDBMS에서 랜덤 데이터 추출을 사용할 때 ORDER BY RAND() 함수를 사용</li>
<li>ORDER BY RAND()는 쿼리의 결과값을 랜덤하게 정렬하지만, 조건 절에 맞는 모든 행을 읽은 뒤, 임시 테이블에 넣어 정렬한 다음 랜덤으로 limit에 해당할 때까지 데이터 추출 ➡️ 데이터가 1만건 이상일 경우 부하 매우 많아짐</li>
<li>레디스를 사용하면 O(1)의 시간 복잡도를 이용해 랜덤한 데이터 추출 가능</li>
<li>HRANDFIELD(hash), SRANDMEMBER(set), ZRANDMEMBER(sorted set)</li>
</ul>
<h2 id="레디스에서의-다양한-카운팅-방법">레디스에서의 다양한 카운팅 방법</h2>
<h3 id="좋아요-처리하기">좋아요 처리하기</h3>
<ul>
<li>좋아요를 누를 때마다 RDBMS의 테이블의 특정 행에서 좋아요 개수 데이터를 증가시키는 것은 데이터베이스에 직접적인 영향을 끼침</li>
<li>또한 하나의 유저는 같은 댓글에 한 번씩만 좋아요를 누를 수 있음</li>
<li>댓글 id을 기준으로 set을 생성한 뒤, 좋아요를 누른 유저의 id를 set에 저장하면 중복 없이 데이터 저장 가능</li>
</ul>
<h3 id="읽지-않은-메시지-수-카운팅하기">읽지 않은 메시지 수 카운팅하기</h3>
<ul>
<li>채팅 메시지가 도착할 때마다 RDBMS에 업데이트 하는 대신 인메모리 데이터베이스에 일시적으로 저장한 뒤 필요한 시점에 한꺼번에 업데이트하면 부하를 줄이고 성능 향상됨</li>
<li>사용자의 id를 키로 하고, 채널의 id를 아이템의 키로 활용해 숫자 형태의 메시지 카운트를 관리</li>
</ul>
<h3 id="daudaily-active-user-구하기">DAU(Daily Active User) 구하기</h3>
<ul>
<li>DAU는 하루 동안 서비스에 방문한 사용자의 수를 의미하며, 하루에 여러번 방문했다 하더라도 한 번으로 카운팅 됨</li>
<li>레디스의 비트맵을 이용해 사용자의 id를 하나의 비트로 표현</li>
<li>날짜를 키로 하고 사용자가 방문했으면 해당 사용자의 id에 해당하는 비트를 1로 설정</li>
</ul>
<h3 id="hyperloglog를-이용한-애플리케이션-미터링">hyperloglog를 이용한 애플리케이션 미터링</h3>
<ul>
<li>클라우드 컴퓨팅의 미터링 솔루션은 사용자의 서비스 사용 내역을 이용하기 때문에 대용량 데이터를 처리할 수 있어야 함</li>
<li>미터링 솔루션은 높은 처리량과 낮은 대기 시간을 가져야 함</li>
<li>다음 조건을 만족한다면 레디스의 hyperloglog를 사용하는 것을 고려해볼 수 있음<ul>
<li>집합 내의 유일한 데이터의 개수를 카운팅해야 함</li>
<li>1% 미만의 오차는 허용 가능함</li>
<li>카운팅할 때 사용한 정확한 데이터를 다시 확인하지 않아도 됨</li>
</ul>
</li>
<li>로그 수집, 검색, 조회 서비스에서 각 유저를 구분하는 id를 키로 사용하고 API를 호출할 때마다 저장되는 로그의 식별자를 hyperloglog에 저장</li>
</ul>
<h2 id="geospatial-index를-이용한-위치-기반-애플리케이션-개발">Geospatial Index를 이용한 위치 기반 애플리케이션 개발</h2>
<h3 id="위치-데이터란">위치 데이터란</h3>
<ul>
<li>모바일 기기의 확산으로 위치 데이터와 같은 공간 데이터 처리가 점점 중요해지고 있음</li>
<li>위치 데이터는 경도와 위도(x, y) 좌표 쌍으로 표현</li>
<li>데이터 저장소의 역할<ul>
<li>사용자의 현재 위치 파악</li>
<li>사용자의 이동에 따른 실시간 변동 위치 업데이트</li>
<li>사용자의 위치를 기준으로 근처의 장소 검색</li>
</ul>
</li>
</ul>
<h3 id="레디스에서의-위치-데이터">레디스에서의 위치 데이터</h3>
<ul>
<li>geo set<ul>
<li>위치 공간 관리에 특화된 데이터 구조로, 각 위치 데이터는 경도와 위도의 쌍으로 저장됨</li>
<li>데이터는 내부적으로 sorted set 구조로 저장됨</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]레디스 기본 개념]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%A0%88%EB%94%94%EC%8A%A4-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Fri, 29 Dec 2023 05:00:55 GMT</pubDate>
            <description><![CDATA[<p> 레디스에서 모든 데이터는 키에 연결되어 있기 때문에 데이터를 저장하고, 저장된 데이터를 검색할 때에는 항상 키를 식별자로 이용</p>
<h1 id="레디스의-자료-구조">레디스의 자료 구조</h1>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/e65bad46-ad67-45ec-b3e2-d2c9e57c8d96/image.png" alt=""></p>
<hr>
<h2 id="string">string</h2>
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/a7110436-d0dd-43ed-a5d8-a2e18f9dfbf5/image.png" width="500px"  height="200px">

<ul>
<li>최대 512MB의 문자열 데이터 저장 가능</li>
<li>이진 데이터를 포함하는 모든 종류의 문자열이 binary-safe하게 처리
➡️ JPEG 이미지와 같은 바이트 값, HTTP 응답값 등의 다양한 데이터 저장 가능</li>
<li>키와 실제 저장되는 아이템이 일대일로 연결</li>
</ul>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>SET</strong></td>
<td>데이터 저장</td>
<td>NX, XX</td>
</tr>
<tr>
<td><strong>GET</strong></td>
<td>데이터 조회</td>
<td></td>
</tr>
<tr>
<td><strong>INCR</strong></td>
<td>저장된 데이터 1씩 증가</td>
<td></td>
</tr>
<tr>
<td><strong>INCRBY</strong></td>
<td>입력한 값 만큼 데이터 증가</td>
<td></td>
</tr>
<tr>
<td><strong>DECR</strong></td>
<td>저장된 데이터 1씩 감소</td>
<td></td>
</tr>
<tr>
<td><strong>DECRBY</strong></td>
<td>입력한 값 만큼 데이터 감소</td>
<td></td>
</tr>
<tr>
<td><strong>MSET</strong></td>
<td>여러 데이터 한번에 저장</td>
<td></td>
</tr>
<tr>
<td><strong>MGET</strong></td>
<td>여러 데이터 한번에 조회</td>
<td></td>
</tr>
<tr>
<td>## list</td>
<td></td>
<td></td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/2a330271-df5d-4bfd-bb35-956a1f3c9281/image.png" alt=""></td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 순서를 가지는 문자열의 목록</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 하나의 list에 최대 42억여개의 아이템 저장 가능</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 스택과 큐로 사용</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- LPUSH, RPUSH, LPOP, RPOP ➡️ O(1)</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 인덱스나 데이터를 이용해 list의 중간 데이터에 접근할 때는 ➡️ O(n)</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>LPUSH</strong></td>
<td>list의 왼쪽(head)에 데이터 추가</td>
<td></td>
</tr>
<tr>
<td><strong>RPUSH</strong></td>
<td>list의 오른쪽(tail)에 데이터 추가</td>
<td></td>
</tr>
<tr>
<td><strong>LPOP</strong></td>
<td>list에 저장된 첫 번째 아이템 반환과 동시에 삭제</td>
<td></td>
</tr>
<tr>
<td><strong>LRANGE</strong></td>
<td>시작과 끝 아이템의 인덱스를 각각 인수로 받아 출력</td>
<td></td>
</tr>
<tr>
<td><strong>LTRIM</strong></td>
<td>시작과 끝 아이템의 인덱스를 인자로 전달받아 지정한 범위에 속하지 않는 아이템 모두 삭제, 삭제되는 아이템 반환X</td>
<td></td>
</tr>
<tr>
<td><strong>LINSERT</strong></td>
<td>원하는 데이터의 앞이나 뒤에 데이터 추가</td>
<td>BEFORE, AFTER</td>
</tr>
<tr>
<td><strong>LSET</strong></td>
<td>지정한 인덱스의 데이터를 신규 입력하는 데이터로 덮어 씀, list의 범위를 벗어난 인덱스를 입력함녀 에러 반환</td>
<td></td>
</tr>
<tr>
<td><strong>LINDEX</strong></td>
<td>원하는 인덱스의 데이터 확인</td>
<td></td>
</tr>
<tr>
<td>## hash</td>
<td></td>
<td></td>
</tr>
<tr>
<td>&lt;img src=&quot;<a href="https://velog.velcdn.com/images/jeongyeon_kim/post/7587dd80-0f7e-4e24-9ff1-9976aa916719/image.png&quot;">https://velog.velcdn.com/images/jeongyeon_kim/post/7587dd80-0f7e-4e24-9ff1-9976aa916719/image.png&quot;</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td>width=&quot;500px&quot; height=&quot;300px&quot;&gt;</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 필드-값 쌍을 가진 아이템의 집합</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 필드는 하나의 hash 내에서 유일하며, 필드와 값 모두 문자열 데이터로 저장됨</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 각 아이템마다 다른 필드를 가질 수 있으며, 동적으로 다양한 필드를 추가할 수 있음</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 객체를 표현하기에 적절하기 때문에 관계형 데이터베이스의 테이블 데이터로 변환 간편</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>HSET</strong></td>
<td>hash에 아이템 저장, 한 번에 여러 필드-값 쌍 저장 가능</td>
<td></td>
</tr>
<tr>
<td><strong>HGET</strong></td>
<td>hash에 저장된 데이터 가져옴</td>
<td></td>
</tr>
<tr>
<td><strong>HMGET</strong></td>
<td>하나의 hash 내에서 다양한 필드의 값 가져옴</td>
<td></td>
</tr>
<tr>
<td><strong>HGETALL</strong></td>
<td>hash 내의 모든 필드-값 쌍을 차례로 반환</td>
<td></td>
</tr>
<tr>
<td><strong>HRANDFIELD</strong></td>
<td>랜덤으로 아이템 추출</td>
<td>COUNT. WITHVALUES</td>
</tr>
</tbody></table>
<h2 id="set">set</h2>
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/2d261e27-c3b4-4d08-a4ab-79f9b1394b81/image.png" width="500px" height="300px">

<ul>
<li>정렬되지 않은 문자열의 모음</li>
<li>교집합, 합집합, 차집합 등의 집합 연산과 관련한 커맨드 제공</li>
<li>객체 간의 관계를 계산하거나 유일한 원소를 구해야 할 경우에 사용됨</li>
</ul>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>SADD</strong></td>
<td>set에 아이템 저장, 저장되는 실제 아이템 수 반환</td>
<td></td>
</tr>
<tr>
<td><strong>SMEMBERS</strong></td>
<td>set에 저장된 전체 아이템 출력, 데이터 저장 순서와 관계없이 랜덤하게 출력</td>
<td></td>
</tr>
<tr>
<td><strong>SREM</strong></td>
<td>set에서 원하는 데이터 삭제</td>
<td></td>
</tr>
<tr>
<td><strong>SPOP</strong></td>
<td>set 내부의 아이템 중 랜덤으로 하나의 아이템을 반환하는 동시에 set에서 그 아이템 삭제</td>
<td></td>
</tr>
<tr>
<td><strong>SUNION</strong></td>
<td>합집합</td>
<td></td>
</tr>
<tr>
<td><strong>SINTER</strong></td>
<td>교집합</td>
<td></td>
</tr>
<tr>
<td><strong>SDIFF</strong></td>
<td>차집합</td>
<td></td>
</tr>
<tr>
<td><strong>SRANDMEMBER</strong></td>
<td>랜덤으로 아이템 추출</td>
<td>COUNT, WITHSCORE</td>
</tr>
<tr>
<td><strong>SCARD</strong></td>
<td>해당 set에 저장된 키의 개수 리턴</td>
<td></td>
</tr>
</tbody></table>
<h2 id="sortedset">sortedSet</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/8364b78e-773d-48bf-a6f3-370021627b55/image.png" alt=""></p>
<ul>
<li>스코어(score) 값에 따라 정렬되는 고유한 문자열의 집합</li>
<li>모든 아이템은 스코어-값 쌍을 가지며, 저장될 때부터 스코어 값으로 정렬되어 저장됨</li>
<li>같은 스코어를 가진 아이템을 데이터의 사전 순으로 정렬</li>
<li>list에서 인덱스를 이용해 데이터에 접근하는 것은 O(n)으로 처리되지만, sorted set에서는 O(log(n))으로 처리됨</li>
</ul>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>ZADD</strong></td>
<td>sorted set에 아이템 저장, 스코어-값 쌍으로 입력, 한 번에 여러 아이템 입력 가능, 저장과 동시에 스코어 값으로 정렬됨</td>
<td>XX, NX, LT, GT</td>
</tr>
<tr>
<td><strong>ZRANGE</strong></td>
<td>sorted set에 저장된 데이터 조회(인덱스 기반), start와 stop(범위) 반드시 입력</td>
<td>WITHSCORE, REV, BYSCORE, BYLEX</td>
</tr>
<tr>
<td><strong>ZREVRANGE</strong></td>
<td>sorted set에 저장된 데이터를 내림차순으로 반환</td>
<td></td>
</tr>
<tr>
<td><strong>ZINCRBY</strong></td>
<td>sorted set 내의 아이템의 스코어를 입력한 만큼 증가</td>
<td></td>
</tr>
<tr>
<td><strong>ZUNIONSTORE</strong></td>
<td>지정한 키에 연결된 각 아이템이 스코어를 합산, 스코어에 가중치 부여 가능</td>
<td>WEIGHTS</td>
</tr>
<tr>
<td><strong>ZRANDMEMBER</strong></td>
<td>랜덤으로 아이템 추출</td>
<td>COUNT, WITHSCORE</td>
</tr>
</tbody></table>
<h2 id="비트맵">비트맵</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/51bf19c8-abae-40a0-9141-f8e7cc23cd77/image.png" alt=""></p>
<ul>
<li>string 자료구조에 bit 연산을 수행할 수 있도록 확장한 형태</li>
<li>저장 공간을 획기적으로 줄일 수 있다는 장점이 있음</li>
</ul>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>SETBIT</strong></td>
<td>비트 저장</td>
<td></td>
</tr>
<tr>
<td><strong>GETBIT</strong></td>
<td>비트 조회</td>
<td></td>
</tr>
<tr>
<td><strong>BITFIELD</strong></td>
<td>한 번에 여러 비트 SET</td>
<td></td>
</tr>
<tr>
<td><strong>BITCOUNT</strong></td>
<td>1로 설정된 비트의 개수 카운팅</td>
<td></td>
</tr>
<tr>
<td><strong>BITTOP</strong></td>
<td>AND, OR, XOR, NOT 연산</td>
<td>AND, OR, XOR, NOT</td>
</tr>
</tbody></table>
<h2 id="hyperloglog">Hyperloglog</h2>
<ul>
<li>집합의 원소 개수인 카디널리티를 추정할 수 있는 자료 구조</li>
<li>대량 데이터에서 중복되지 않는 고유한 값을 집계할 때 유용</li>
<li>입력되는 데이터 그 자체를 저장하지 않고 자체적인 방법으로 데이터를 변경해 처리</li>
<li>저장되는 데이터 개수에 구애받지 않고 계속 일정한 메모리를 유지할 수 있으며, 중복되지 않는 유일한 원소의 개수 계산 가능</li>
<li>최대 12KB 크기</li>
<li>카디널리티 추정 오차 0.81%로 비교적 정확하게 데이터 추정 가능</li>
</ul>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>PFADD</strong></td>
<td>hyperloglog에 아이템 저장</td>
<td></td>
</tr>
<tr>
<td><strong>PFCOUNT</strong></td>
<td>저장된 아이템의 수(카디널리티) 추정</td>
<td></td>
</tr>
<tr>
<td><strong>PFMERGE</strong></td>
<td>여러 개의 hyperloglog 합침</td>
<td></td>
</tr>
</tbody></table>
<h2 id="geospatial">Geospatial</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/63177815-4267-4365-b911-cce4b24e80ee/image.png" alt=""></p>
<ul>
<li>경도, 위도 데이터 쌍의 집합</li>
<li>내부적으로 데이터는 sorted set으로 저장되며, 하나의 자료 구조 안에 키는 중복되어 저장되지 않음</li>
</ul>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GEOADD</strong></td>
<td>위치 데이터 저장</td>
<td>XX, NX</td>
</tr>
<tr>
<td><strong>GEOPOS</strong></td>
<td>지정된 위치 데이터 조회</td>
<td></td>
</tr>
<tr>
<td><strong>GEODIST</strong></td>
<td>두 아이템 사이의 거리 반환</td>
<td></td>
</tr>
<tr>
<td><strong>GEOSEARCH</strong></td>
<td>지정된 위치 데이터 조회</td>
<td>BYRADIUS, BYBOX, FROMLONLAT, FROMMEMBER</td>
</tr>
</tbody></table>
<h2 id="stream">stream</h2>
<p><img src="https://velog.velcdn.com/images/jeongyeon_kim/post/9ed29738-7dd5-43b1-9bdb-37fe99608118/image.png" alt=""></p>
<ul>
<li>레디스를 메시지 브로커로서 사용할 수 있게 하는 자료 구조</li>
<li>카프카에서 영향을 받아 만들어짐</li>
<li>소비자 그룹 개념을 도입해 데이터를 분산 처리할 수 있는 시스템</li>
<li>데이터를 계속해서 추가하는 방식(append-only)로 저장 ➡️ 실시간 이벤트 or 로그성 데이터의 저장에 사용</li>
</ul>
<h1 id="레디스에서-키를-관리하는-법">레디스에서 키를 관리하는 법</h1>
<hr>
<h2 id="키의-자동-생성과-삭제">키의 자동 생성과 삭제</h2>
<ol>
<li>키가 존재하지 않을 대 아이템을 넣으면 아이템을 삽입하기 전에 빈 자료 구조 생성<ul>
<li>저장하고자 하는 키에 다른 자료 구조가 이미 생성되어 있을 때 아이템을 추가하는 작업은 에러 반환</li>
</ul>
</li>
<li>모든 아이템을 삭제하면 키도 자동으로 삭제됨(stream은 예외)</li>
<li>키가 없는 상태에서 키 삭제, 아이템 삭제, 자료 구조 크기 조회 같은 읽기 전용 커맨드를 수행하면 에러를 반환하는 대신 키가 있으나 아이템이 없는 것 처럼 동작</li>
</ol>
<h2 id="키와-관련된-커맨드">키와 관련된 커맨드</h2>
<table>
<thead>
<tr>
<th>command</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>EXISTS</strong></td>
<td>키가 존재하는지 확인</td>
</tr>
<tr>
<td><strong>KEYS</strong></td>
<td>레디스에 저장된 모든 키 조회, 한 번에 모든 키를 반환 ➡️ 다른 클라이언트가 무한정 대기하면서 페일 오버 발생 가능</td>
</tr>
<tr>
<td><strong>SCAN</strong></td>
<td>KEYS를 대체해 키를 조회할 때 사용, 커서를 기반으로 특정 범위의 키만 조회</td>
</tr>
<tr>
<td>** SORT**</td>
<td>키 내부의 아이템을 정렬해 반환, list, set, sorted set에서만 사용 가능</td>
</tr>
<tr>
<td><strong>RENAME/RENAMENX</strong></td>
<td>키의 이름 변경</td>
</tr>
<tr>
<td><strong>COPY</strong></td>
<td>source에 지정된 키를 destination 키에 복사</td>
</tr>
<tr>
<td><strong>OBJECT</strong></td>
<td>키에 대한 상세 정보 반환</td>
</tr>
<tr>
<td><strong>FLUSHALL</strong></td>
<td>레디스에 저장된 모든 키 삭제</td>
</tr>
<tr>
<td><strong>DEL</strong></td>
<td>키와 키에 저장된 모든 아이템 삭제, 동기적으로 작동</td>
</tr>
<tr>
<td><strong>UNLINK</strong></td>
<td>키와 데이터 삭제, 백그라운드에서 다른 스레드에 의해 처리되며 우선 키와 연결된 데이터의 연결 끊음</td>
</tr>
<tr>
<td><strong>EXPIRE</strong></td>
<td>키가 만료될 시간을 초 단위로 정의</td>
</tr>
<tr>
<td><strong>EXPIREAT</strong></td>
<td>키가 특정 유닉스 타임스탬프에 만료될 수 있도록 키의 만료 시간 직접 지정</td>
</tr>
<tr>
<td><strong>EXPIRETIME</strong></td>
<td>키가 삭제되는 유닉스 타임스탬프를 초 단위로 반환, 키가 존재하지만 만료 시간이 설정되어 있지 않은 경우에는 -1, 키가 없을 때는 -2 반환</td>
</tr>
<tr>
<td><strong>TTL</strong></td>
<td>키가 몇 초 뒤에 만료되는지 반환, 키가 존재하지만 만료 시간이 설정되어 있지 않은 경우에는 -1, 키가 없을 때는 -2 반환</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redis]마이크로서비스 아키텍처와 레디스]]></title>
            <link>https://velog.io/@jeongyeon_kim/Redis%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%EB%A0%88%EB%94%94%EC%8A%A4</link>
            <guid>https://velog.io/@jeongyeon_kim/Redis%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%EB%A0%88%EB%94%94%EC%8A%A4</guid>
            <pubDate>Wed, 27 Dec 2023 05:47:09 GMT</pubDate>
            <description><![CDATA[<h1 id="nosql의-등장-배경">NoSQL의 등장 배경</h1>
<hr>
<h2 id="모놀리틱-아키텍처">모놀리틱 아키텍처</h2>
<blockquote>
<p>전체 애플리케이션을 하나의 통합된 패키지로 개발, 배포하는 방식</p>
</blockquote>
<ul>
<li>작은 규모의 프로젝트나 애플리케이션에서 운영 쉬움</li>
<li>서비스 규모가 확장되면서 유지보수의 복잡도 증가</li>
<li>한 개의 시스템에 문제가 발생하면 전체 시스템의 장애로 이어짐</li>
<li>하나의 모듈을 수정하면 전체 애플리케이션 다시 배포해야하며 배포 시간 길어짐</li>
<li>요구 사항 변경에 유연하게 대처하기 힘듦</li>
<li>하나의 시스템에서 리소스가 부족해 확장이 필요하면 전체 시스템을 확장할 수 밖에 없어 리소스 낭비<h2 id="마이크로서비스-아키텍처">마이크로서비스 아키텍처</h2>
</li>
</ul>
<blockquote>
<p>독립된 각각의 모듈을 조립해 하나의 서비스를 만드는 아키텍처
기능별로 작게 나뉘어진 서비스가 독립적으로 동작하는 서비스</p>
</blockquote>
<ul>
<li>새로운 기능을 추가해 배포하는 것이 비교적 편리해 요구사항에 민첩하게 대처 가능</li>
<li>서비스 확장이 필요할 때 원하는 서비스만 업그레이드 가능해 서비스 관리 유연</li>
<li>서비스 간의 독립성으로 인해 한 서비스에서의 문제가 다른 서비스에 영향을 주지 않아 운영의 안정성 향상</li>
<li>소규모 팀에서는 서비스 분리로 인한 관리의 복잡도와 운영 부담 증가</li>
</ul>
<h2 id="데이터-저장소-요구-사항의-변화">데이터 저장소 요구 사항의 변화</h2>
<ul>
<li>모놀리틱 아키텍처에서는 중앙 집약적인 <strong>관계형 데이터베이스(RDBMS)</strong>가 표준</li>
<li>최근 서비스에서 비정형 데이터(다차원적, 깊은 계층 구조) 증가 ➡️ 관계형 데이터베이스의 정형화된 테이블에서는 관리 어려움</li>
<li>NoSQL의 경우 개발 팀이 바로 데이터 구조를 바꿀 수 있어 더 빠른 개발 가능</li>
</ul>
<blockquote>
<p>마이크로서비스 아키텍처에서 가장 중요한 것은 각 서비스가 독립적으로 동작할 수 있도록 하나의 서비스가 다른 서비스들과 밀접하게 연관되지 않아야 한다는 것!</p>
</blockquote>
<ul>
<li>마이크로서비스 아키텍처의 각각의 서비스는 스스로의 상태를 유지해야 하고, 독립된 데이터 저장소 필요</li>
</ul>
<h1 id="nosql이란">NoSQL이란?</h1>
<hr>
<blockquote>
<p>SQL을 사용하지 않는 데이터 저장소</p>
</blockquote>
<ul>
<li>관계가 정의되어 있지 않은 데이터를 저장</li>
</ul>
<h2 id="nosql의-특징">NoSQL의 특징</h2>
<h3 id="1-실시간-응답">1. 실시간 응답</h3>
<ul>
<li>마이크로서비스 내의 저장소에서는 빠른 응답 속도가 중요</li>
<li>각각의 개별 서비스가 빠르게 동작하지 않으면 서비스 자체가 병목 현상을 유발할 수 있음<h3 id="2-확장성">2. 확장성</h3>
</li>
<li>트랜잭션의 증가에 유연하게 확장<h3 id="3-고가용성">3. 고가용성</h3>
</li>
<li>장애 상황에서 신속하게 복구되어 항상 사용할 수 있는 상태 유지<h3 id="4-클라우드-네이티브">4. 클라우드 네이티브</h3>
</li>
<li>클라우드 제공 업체에서 제공하는 DBaas(DataBase-as-a-service)를 사용하면 직접 설치, 운영할 필요 없이 설치된 상품을 바로 사용 가능<h3 id="5-단순성">5. 단순성</h3>
</li>
<li>마이크로서비스 아키텍처 서비스가 세분화 될수록 관리 포인트가 늘어나기 때문에 개발자와 운영자는 데이터 저장소를 간단하게 사용하고 싶어함</li>
<li>한 가지의 데이터 모델이 모든 서비스에 최적화되진 않기 때문에 서비스별로 적절한 데이터 모델(멀티 모델 데이터베이스) 사용을 원함<h3 id="6-유연성">6. 유연성</h3>
</li>
<li>NoSQL은 비정형 데이터를 저장할 수 있는 방법 제공</li>
</ul>
<h1 id="nosql-데이터-저장소-유형">NoSQL 데이터 저장소 유형</h1>
<hr>
<h2 id="그래프-유형">그래프 유형</h2>
<ul>
<li>엔티티 간의 관계를 효율적으로 저장하도록 설계됨</li>
<li>노드(node), 엣지(edge), 속성(properties)</li>
<li>저장되는 속성의 크기가 크거나 혹은 매우 많은 속성을 저장할 때에는 적합하지 않은 경우가 많음</li>
<li>추천 서비스, 사기 감지, 소셜미디어, 네트워크 및 IT 운영 등에 필요
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/74c22e55-991c-4571-86c0-742a24cdf381/image.png" alt=""></li>
</ul>
<h2 id="칼럼-유형">칼럼 유형</h2>
<ul>
<li>열(column)은 기준으로 저장</li>
<li>칼럼 지향적(column-oriented), 와이드 칼럼(wide column)</li>
<li>데이터는 하나의 열에 중첩된 키-값 형태로 저장될 수 있기 때문에 기존의 관계형 데이터베이스와 비교했을 때보다 유연한 스키마를 저장할 수 있음</li>
<li>대량의 데이터에 대한 집계 쿼리를 다른 유형보다 빠르게 처리 가능</li>
<li>기업의 BI 분석을 위한 데이터 웨어하우스, 분석, 보고, 빅데이터 처리에 적합</li>
<li>Apache Cassandra, HBase 등
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/ccf2c07c-87e4-46ec-8192-0cd0c0bfa059/image.png" alt=""></li>
</ul>
<h2 id="문서-유형">문서 유형</h2>
<ul>
<li>JSON 형태로 데이터 저장 가능</li>
<li>스키마가 따로 정해져 있지 않기 때문에 애플리케이션에 맞게 데이터를 그대로 저장할 수 있어 유연성이 큼</li>
<li>모든 값은 항상 키와 연결되는 계층적 트리와 같은 구조를 가짐</li>
<li>데이터를 저장하거나 검색하는 데 효과적</li>
<li>MongoDB, CouchDB, AWS의 DocumentDB 등
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/dd7ea65c-63ff-4d6d-9415-3c53a155a7d2/image.png" alt=""></li>
</ul>
<h2 id="키-값-유형">키-값 유형</h2>
<ul>
<li>가장 단순하고 빠름</li>
<li>모든 값은 키에 연결되어 있고, 키 자체도 유의미한 데이터</li>
<li>데이터 저장이 간단하기 때문에 수평적 확장 쉬움</li>
<li>구조의 단순성으로 인해 빠른 데이터 액세스와 처리 속도 보장</li>
<li>실시간 서비스(게임, IoT), 로그 남기는 작업, 대규모 세션 실시간 관리 등</li>
<li>Redis, AWS의 ElastiCache, AWS의 DynamoDB, Oracle NoSQL Database, Memcached 등
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/c7e689b4-8297-4dbb-b708-8978db4b6ca7/image.png" alt=""></li>
</ul>
<h1 id="레디스란">레디스란?</h1>
<hr>
<blockquote>
<p>Remote dictionary server
고성능 <strong>키-값 유형의 인메모리 NoSQL 데이터베이스</strong>로, 오픈 소스 기반의 데이터 저장소</p>
</blockquote>
<h2 id="레디스의-특징">레디스의 특징</h2>
<h3 id="1-실시간-응답빠른-성능">1. 실시간 응답(빠른 성능)</h3>
<ul>
<li><strong>온디스크(disk-based) 데이터베이스</strong>: 데이터가 영구적으로 디스크에 저장됨</li>
<li>디스크에 저장된 데이터는 페이지 단위로 메모리에 올려 메모리에서 데이터를 찾고, 없는 경우 다른 페이지를 디스크에서 가져와 메모리에 올린 뒤 찾는 과정 반복</li>
<li>HDD와 SSD와 같은 디스크에 접근하는 속도는 RAM과 같은 메모리에 접근하는 속도보다 현저히 느림</li>
<li>디스크에 접근하는 빈도가 증가할 수록 시스템 성능 저하</li>
<li><strong>인메모리(in-memory) 데이터베이스</strong>: 모든 데이터가 컴퓨터의 메모리에서 관리됨</li>
<li>인메모리 데이터베이스는 디스크에 접근하는 과정이 필요 없기 때문에 데이터의 처리 성능이 빠름
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/c398ee1b-fd8b-418a-a667-4bcfa3bcc14a/image.png" alt=""></li>
</ul>
<h3 id="2-단순성">2. 단순성</h3>
<ul>
<li>키에 매핑되는 값에는 문자열, hash, set 등 다양한 데이터 구조를 저장할 수 있도록 지원</li>
<li>문자열, hash, set과 같은 데이터 타입은 프로그래밍의 기본 자료 구조와 밀접한 관련이 있어 추가적인 데이터의 가공 없이 애플리케이션에서 쉽게 사용 가능</li>
<li>레디스는 내장된 다양한 자료구조를 통해 임피던스 불일치 해소<ul>
<li>임피던스 불일치(impedance mismatches): 기존 관계형 데이터베이스의 테이블과 프로그래밍 언어 간 데이터 구조, 기능의 차이로 인해 발생하는 충돌</li>
</ul>
</li>
<li>100개가 넘는 오픈 소스 클라이언트 사용 가능</li>
<li>Java, Python, PHP, C, C++, JavaScript, Node.js, R, Go를 비롯한 다수의 언어 지원</li>
</ul>
<ul>
<li>레디스는 <strong>싱글 스레드</strong>로 동작(메인 스레드 1개 + 별도의 스레드 3개)</li>
<li>클라이언트의 커맨드를 처리하는 부분은 이벤트 루프를 이용한 싱글 스레드로 동작</li>
<li>최소 하나의 코어만 있어도 레디스를 사용할 수 있기 때문에 배포가 쉽고, CPU가 적은 서버에서도 좋은 성능을 낼 수 있음</li>
<li>동기화나 잠금 매커니즘 없이도 안정적이고 빠르게 사용자의 요청 처리 가능</li>
<li>싱글 스레드로 동작한다는 것은 한 사용자가 오래 걸리는 커맨드를 수행한다면, 다른 사용자는 그 쿼리가 완료될 때까지 대기해야 한다는 것</li>
<li>레디스는 메모리에서 동작하기 때문에 대부분의 커맨드는 빠른 응답 시간을 갖지만 반환이 느린 특정 커맨드 존재
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/103fbcdb-be54-47c8-b3df-156cce1747f5/image.png" alt=""><h3 id="3-고가용성-1">3. 고가용성</h3>
</li>
<li>자체적으로 HA(High Availability) 기능 제공</li>
<li>복제를 통해 데이터를 여러 서버에 분산시킬 수 있음</li>
<li>센티널(sentinel)은 장애 상황을 탐지해 자동으로 페일오버(failover) 시켜줌</li>
<li>마스터에 장애가 발생하면 복제를 새로운 마스터로 승격시켜 레디스로의 엔드포인트를 변경할 필요 없이 페일오버 완료됨<h3 id="4-확장성">4. 확장성</h3>
</li>
<li>데이터는 레디스 클러스터 내에서 자동을 샤딩된 후 저장되며, 여러 개의 복제본이 생성될 수 있음<ul>
<li>샤딩(sharding): 각 데이터를 특정 조건에 따라서 서버를 분산 저장하는 기법</li>
</ul>
</li>
<li>애플리케이션에서는 대상 데이터가 어떤 샤드에 있는지 신경쓰기 않아도 됨</li>
<li>클러스터 구조에서 모든 레디스 인스턴스는 클러스터 버스를 통해 서로 감시 ➡️ 마스터 노드에 문제가 발생하면 자동을 페일오버 시켜 고가용성 유지
<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/cdd0bb84-2828-47aa-882c-81c1b564ee41/image.png" alt=""><h3 id="5-클라우드-네이티브-멀티-클라우드">5. 클라우드 네이티브-멀티 클라우드</h3>
</li>
<li>클라우드 네이티브<ul>
<li>클라우드 환경에 특화된 애플리케이션의 개발 및 운영 방식</li>
<li>마이크로서비스, 컨테이너, 오케스트레이션, 데브옵스와 같은 현대의 개발 및 운영 패러다임 포용</li>
<li>빠른 배포와 확장성, 높은 복원력을 중심으로 한 애플리케이션 추구</li>
<li>빠른 데이터 액세스 및 처리를 지원하는 구조로 인해, 마이크로서비스 아키텍처와의 연계에서 큰 장점 지님</li>
</ul>
</li>
<li>멀티 클라우드<ul>
<li>여러 클라우드 제공업체의 서비스를 동시에 혹은 혼합해 활용하는 전략</li>
<li>단일 클라우드 환경의 장애나 제한된 자원에 대한 의존성을 줄이며, 각 클라우드 서비스 제공자의 강점을 활용할 수 있게 해줌</li>
<li>데이터가 특정 지역이나 국가 내에 물리적으로 위치하도록 조절할 수 있어 더 가까운 저장소에서 데이터를 처리하게 되므로 대기 시간을 줄이고 장애 상황에 더욱 강건하게 대응 가능</li>
<li>레디스는 여러 클라우드 환경에 걸쳐 일관된 성능과 기능을 제공함으로써 서비스의 연속성과 데이터의 일관성 보장<img src="https://velog.velcdn.com/images/jeongyeon_kim/post/fc9223a3-a7ed-4f49-b5e2-2b541693f8b1/image.png" width="500px" height="300px">

</li>
</ul>
</li>
</ul>
<h1 id="마이크로서비스-아키텍처와-레디스">마이크로서비스 아키텍처와 레디스</h1>
<hr>
<h2 id="데이터-저장소로서의-레디스">데이터 저장소로서의 레디스</h2>
<ul>
<li>마이크로서비스 아키텍처에서 각 서비스별 개별 저장소로 사용</li>
<li>설치 간편, 최소한의 리소스로 막대한 처리량, 다양한 자료 구조 제공</li>
<li>고가용성을 위해 로드 밸러서나 프록시 등 추가적인 서비스를 설치할 필요가 없음</li>
<li>데이터의 영속성을 위해 AOF(Append Only File)와 RDB(Redis DataBase) 형식으로 디스크에 주기적으로 저장 가능</li>
<li>레디스에 있는 데이터가 유실되더라고 백업 파일을 통해 복구 가능<h2 id="메시지-브로커로서의-레디스">메시지 브로커로서의 레디스</h2>
</li>
<li>마이크로서비스 아키텍처에서 각 서비스를 완전히 분리되어 있는 구조로 동작하기 때문에 서로 다른 서비스 간에 지속적인 통신 필요</li>
<li>메시징 큐 혹은 stream과 같은 메시지 브로커를 이용해 서비스 간에 비동기적으로 데이터를 전달</li>
<li>레디스의 pub/sub은 메시징 기능으로 빠르게 동작하며 간단하게 사용 가능<ul>
<li>1개의 채널에 데이터를 던지면 이 채널을 듣고 있는 모든 소비자는 데이터를 빠르게 가져갈 수 있음</li>
<li>pub/sub에서 모든 데이터는 전달된 뒤 삭제되는 일회성</li>
<li>fire-and-forget 패턴이 필요한 간단한 알림 서비스에서는 유용</li>
</ul>
</li>
<li>레디스의 list 자료 구조는 메시징 큐로 사용하기 알맞음<ul>
<li>빠르게 데이터 push/pop 가능</li>
<li>애플리케이션은 매번 list에 데이터가 있는지 확인할 필요 없이 대기하다가 새로운 데이터가 들어오면 읽어갈 수 있는 블로킹 기능 사용 가능</li>
</ul>
</li>
<li>레디스의 stream 자료 구조를 이용하면 레디스를 완벽한 스트림 플랫폼으로 사용할 수 있음<ul>
<li>데이터는 계속해서 추가되는 방식으로 저장됨(append-only)</li>
<li>카프카처럼 저장되는 데이터를 읽을 수 있는 소비자와 소비자 그룹이 존재해 데이터의 분산 처리 가능하며 저장된 데이터를 시간대별로 검색하는 것도 가능</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체지향 쿼리 언어(3)]]></title>
            <link>https://velog.io/@jeongyeon_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%BF%BC%EB%A6%AC-%EC%96%B8%EC%96%B43</link>
            <guid>https://velog.io/@jeongyeon_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%BF%BC%EB%A6%AC-%EC%96%B8%EC%96%B43</guid>
            <pubDate>Tue, 07 Feb 2023 09:58:02 GMT</pubDate>
            <description><![CDATA[<h2 id="5-네이티브-sql">5. 네이티브 SQL</h2>
<ul>
<li><p>특정 데이터베이스에 종속적인 기능을 지원하는 방법</p>
<ul>
<li>특정 데이터베이스만 사용하는 함수</li>
<li>특정 데이터베이스만 지원하는 SQL 쿼리 힌트</li>
<li>인라인 뷰(From 절에서 사용하는 서브 쿼리), UNION, INTERSECT</li>
<li>스토어 프로시저</li>
<li>특정 데이터베이스만 지원하는 문법</li>
</ul>
</li>
<li><p>네이티브 SQL을 사용하면 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있음</p>
</li>
<li><p>JDBC API를 직접 사용하면 데이터의 나열을 조회할 뿐</p>
</li>
</ul>
<h3 id="네이티브-sql-사용">네이티브 SQL 사용</h3>
<h4 id="엔티티-조회">엔티티 조회</h4>
<ul>
<li>em.createNativeQuery(SQL, 결과 클래스)</li>
<li>실제 데이터베이스 SQL 사용</li>
<li>위치기반 파라미터만 지원(하이버네이트는 이름 기반 파라미터 사용 가능)</li>
<li>조회한 엔티티 영속성 컨텍스트에서 관리됨</li>
</ul>
<h4 id="값-조회">값 조회</h4>
<ul>
<li>em.createNativeQuery(SQL)</li>
<li>조회할 값들을 Object[]에 담아서 반환</li>
<li>스칼라 값을 조회했으므로 영속성 컨텍스트가 관리 X</li>
</ul>
<h4 id="결과-매핑-사용">결과 매핑 사용</h4>
<ul>
<li>@SqlResultSetMapping</li>
<li>@EntityResult</li>
<li>@FieldResult</li>
<li>@ColumnResult</li>
</ul>
<h3 id="named-네이티브-sql">Named 네이티브 SQL</h3>
<ul>
<li>createNamedQuery 사용</li>
<li>TypeQuery 사용 가능</li>
<li>@NamedNativeQuery</li>
</ul>
<h3 id="네이티브-sql-정리">네이티브 SQL 정리</h3>
<ul>
<li>네이티브 SQL도 JPQL을 사용할 때와 마찬가지로 Query, TypeQuery(Named 네이티브 쿼리의 경우에만) 반환</li>
<li>JPQL API 사용 가능</li>
<li>네이티브 SQL은 관리하기 쉽지 않고 자주 사용하면 특정 데이터베이스에 종속적인 쿼리가 증가해서 이식성 떨어짐</li>
<li>될 수 있으면 표준 JPQL을 사용하고 기능이 부족하면 차선책으로 JPA 구현체가 제공하는 기능을 사용</li>
<li>그래도 안되면 마지막 방법으로 네이티브 SQL 사용</li>
</ul>
<h3 id="스토어드-프로시저">스토어드 프로시저</h3>
<ul>
<li>proc_multiply: 입력 값을 두 배로 증가</li>
<li>@NamedStoredProcedureQuery</li>
</ul>
<h2 id="6-객체지향-쿼리-심화">6. 객체지향 쿼리 심화</h2>
<h3 id="벌크-연산">벌크 연산</h3>
<ul>
<li>벌크 연산: 여러 건을 한 번에 수정하거나 삭제</li>
<li>executeUpdate(): 벌크 연산으로 영향을 받은 엔티티 건수 반환</li>
<li>벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리</li>
<li>영속성 컨텍스트에 있는 엔티티의 내용과 데이터베이스에 있는 내용이 다를 수 있음
➡️ 해결 방법<ul>
<li>em.refresh()</li>
<li>벌크 연산 먼저 실행</li>
<li>벌크 연산 수행 후 영속성 컨텍스트 초기화<h3 id="영속성-컨텍스트와-jpql">영속성 컨텍스트와 JPQL</h3>
</li>
</ul>
</li>
<li>조회한 엔티티만 영속성 컨텍스트가 관리</li>
<li>JPQL로 조회한 엔티티는 영속 상태</li>
<li>영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 기존 엔티티 반환</li>
<li>영속성 컨텍스트는 영속 상태인 엔티티의 동일성 보장</li>
<li>em.find()는 영속성 컨텍스트에서 엔티티를 먼저 찾고 없으면 데이터베이스 조회</li>
<li>JPQL은 항상 데이터베이스에 SQL을 실행해서 결과 조회</li>
</ul>
<h3 id="jpql과-플러시-모드">JPQL과 플러시 모드</h3>
<ul>
<li>JPQL은 영속성 컨텍스트에 있는 데이터 고려하지 않고 데이터베이스에서 데이터 조회하므로 JPQL 실행 전에 영속성 컨텍스트의 내용을 데이터베이스에 반영해야 함</li>
<li>쿼리에 설정하는 플러시 모드는 엔티티 매니저에 설정하는 플러시 몯보다 우선권 가짐</li>
<li>JPA 쿼리를 사용할 때 영속성 컨텍스트에는 있지만 아직 데이터베이스에 반영하지 않은 데이터 조회 불가 ➡️ 데이터 무결성에 심각한 피해</li>
<li>플러시가 너무 자주 일어나는 상황에 FlushModeType.COMMIT 모드 사용하면 쿼리시 밠생하는 플러시 횟수를 줄여서 성능 최적화</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체지향 쿼리 언어(2)]]></title>
            <link>https://velog.io/@jeongyeon_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%BF%BC%EB%A6%AC-%EC%96%B8%EC%96%B42</link>
            <guid>https://velog.io/@jeongyeon_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%BF%BC%EB%A6%AC-%EC%96%B8%EC%96%B42</guid>
            <pubDate>Sun, 05 Feb 2023 01:10:47 GMT</pubDate>
            <description><![CDATA[<h2 id="3-criteria">3. Criteria</h2>
<h3 id="criteria-기초">Criteria 기초</h3>
<ul>
<li>Criteria API는 javax.persistence.criteria 패키지에 있음<pre><code class="language-java">// JPQL
// select m from Member m
// where m.username=&#39;회원1&#39;
//order by m.age desc
</code></pre>
</li>
</ul>
<p>CriteriaBuilder cb = em.getCriteriaBuilder();</p>
<p>CriteriaQuery<Member> cq = cb.createQuery(Member.class);</p>
<p>Root<Member> m = cq.from(Member.class);</p>
<p>// 검색 조건 정의
Predicate usernameEqual = cb.equal(m.get(&quot;username&quot;), &quot;회원1&quot;);</p>
<p>// 정렬 조건 정의
javax.persistence.criteria.Order ageDesc = cb.desc(m.get(&quot;age&quot;));</p>
<p>// 쿼리 생성
cq.select(m)
    .where(usernameEqual)
    .orderBy(ageDesc);</p>
<p>List<Member> resultList = em.createQuery(cq).getResultList();</p>
<pre><code>- 쿼리 루트
  - Root&lt; Member &gt; m = cq.from(Member.class); 여기서 m이 쿼리 루트
  - 쿼리 루트는 조회의 시작점
  - Criteria에서 사용되는 특별한 별칭
  - 별칭은 엔티티에만 부여 가능

### Criteria 쿼리 생성
  - CriteriaBuilder.createQuery() 메소드로 Criteria 쿼리 생성
  - Criteria 쿼리 생성 시 파라미터로 쿼리 결과에 대한 반환 타입 지정 가능
  - 반환 타입을 지정할 수 없거나 반환 타입이 둘 이상이면 Object(Object[])로 반환 받음
  - 튜플로도 반환 받을 수 있음

### 조회
  - 조회 대상을 한 건, 여러 건 지정
    - select: 조회 대상 한 건
    - multiselect: 조회 대상 여러 건
 - DISTINCT
    - select, multiselect 다음에 distinct(true) 사용
- NEW, construct()
  -    cb.construct(클래스 타입, ...)
- 튜플
  - 이름 기반이므로 순서 기반의 Object[] 보다 안전
  - tuple.getElements() 같은 메소드를 사용해서 현재 튜플의 별칭과 자바 타입 조회 가능
  - 튜플 사용할 때는 별칭 필수

### 집합
- groupBy
- having

### 정렬
- orderBy
- cb.desc(...), cb.asc(...)

### 조인
- join()
- JoinType 클래스
- fetch(조인대상, JoinType)

### 서브 쿼리
~~~java
/* JPQL
    select m from Member m
    where exists
        (select t from m.team t where t.name=&#39;팀A&#39;)
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery&lt;Member&gt; mainQuery = cb.createQuery(Member.class);

// 서브 쿼리에서 사용되는 메인 쿼리의 m
Root&lt;Member&gt; m = mainQuery.from(Member.class);

// 서브 쿼리 생성
Subquery&lt;Team&gt; subQuery = mainQuery.subquery(Team.class);
Root&lt;Member&gt; subM = subQeury.correlate(m);
Join&lt;Member, Team&gt; t = sbuM.join(&quot;team&quot;);
subQuery.select(t)
    .where(cb.equal(t.get(&quot;name&quot;), &quot;팀A&quot;));

// 메인 쿼리 생성
mainQuery.select(m)
    .where(cb.exists(subQuery));

List&lt;Member&gt; resultList = em.createQuery(mainQuery).getResultList();</code></pre><h3 id="in-식">IN 식</h3>
<ul>
<li>in(...)</li>
</ul>
<h3 id="case-식">CASE 식</h3>
<ul>
<li>selectCase()</li>
<li>when()</li>
<li>otherwise()</li>
</ul>
<h3 id="파라미터-정의">파라미터 정의</h3>
<pre><code class="language-java">...
cq.select(m)
    .where(cb.equal(m.get(&quot;username&quot;), cb.parameter(String.class, &quot;usernameParam&quot;)));

List&lt;Member&gt; resultList = em.createQuery(cq)
    .setParameter(&quot;usernameParam&quot;, &quot;회원1&quot;)
    .getResultList();</code></pre>
<h3 id="네이티브-함수-호출">네이티브 함수 호출</h3>
<ul>
<li>cb.function(...)</li>
<li>하이버네이트 구현체는 방언에 사용자정의 SQL 함수를 등록해야 호출할 수 있음</li>
</ul>
<h3 id="동적-쿼리">동적 쿼리</h3>
<ul>
<li>다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 것을 동적 쿼리라 함<pre><code class="language-java">// 검색 조건
Integer age = 10;
String username = null;
String teamName = &quot;팀A&quot;;
</code></pre>
</li>
</ul>
<p>// Criteria 동적 쿼리 생성
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);</p>
<p>Root<Member> m = cq.from(Member.class);
Join&lt;Member, Team&gt; t = m.join(&quot;team&quot;);</p>
<p>List<Predicate> criteria = new ArrayList<Predicate>();</p>
<p>if (age != null) criteria.add(cb.equal(m.<Integer>get(&quot;age&quot;), 
    cb.parameter(Integer.class, &quot;age&quot;)));
if (username != null) criteri.add(cb.equal(m.get(&quot;username&quot;),
    cb.paramter(String.class, &quot;username&quot;)));
if (teamName != null) criteria.add(cb.equal(t.get(&quot;name&quot;),
    cb.paramter(String.class, &quot;teamName&quot;)));</p>
<p>cq.where(cb.and(criteria.toArray(new Predicate[0])));</p>
<p>TypedQuery<Member> query = em.createQuery(cq);
if (age != null) query.setParameter(&quot;age&quot;, age);
if (username != null) query.setParameter(&quot;username&quot;, username);
if (teamName ! null) query.setParameter(&quot;teamName&quot;, teamName);</p>
<p>List<Member> resultList = query.getResultList();</p>
<pre><code>
### Criteria 메타 모델 API
- m.get(&quot;age&quot;)에서 age는 문자인데 실수로 잘못 적어도 컴파일 시점에 에러 발견 불가
➡️ 메타 모델 API 사용
- 엔티티 -&gt; 코드 자동 생성기 -&gt; 메타 모델 클래스

## 2. QueryDSL
### QueryDSL 설정
~~~xml
&lt;dependency&gt;
  &lt;groupId&gt;com.mysema.querydsl&lt;/groupId&gt;
  &lt;artifactId&gt;querydsl-jpa&lt;/artifactId&gt;
  &lt;version&gt;3.6.3&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;com.mysema.querydsl&lt;/groupId&gt;
  &lt;artifactId&gt;querydsl-apt&lt;/artifactId&gt;
  &lt;version&gt;3.6.3&lt;/version&gt;
  &lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;</code></pre><ul>
<li>querydsl-jpa: QueryDSL JPA 라이브러리</li>
<li>querydsl-apt: 쿼리 타입(Q) 생성할 때 필요한 라이브러리<pre><code class="language-xml">&lt;build&gt;
&lt;plugins&gt;
  &lt;plugin&gt;
    &lt;groupId&gt;com.mysema.maven&lt;/groupId&gt;
      &lt;artifactId&gt;apt-maven-plugin&lt;/artifactId&gt;
      &lt;version&gt;1.1.3&lt;/version&gt;
    &lt;executions&gt;
      &lt;execution&gt;
        &lt;goals&gt;
          &lt;goal&gt;process&lt;/goal&gt;
        &lt;/goals&gt;
        &lt;configuration&gt;
          &lt;outputDirectory&gt;target/generated-sources/java&lt;/outputDirectory&gt;
          &lt;processor&gt;com.mysema.query.apt.jpa.JPAAnnotationProcessor&lt;/processor&gt;
        &lt;/configuration&gt;
      &lt;/execution&gt;
    &lt;/executions&gt;
  &lt;/plugin&gt;
&lt;/plugins&gt;
&lt;/build&gt;</code></pre>
<h3 id="시작">시작</h3>
<pre><code class="language-java">import static jpabook.jpashop.domain.QMember.member
</code></pre>
</li>
</ul>
<p>public void basic() {
    EntityManager em = emf.createEntityManager();</p>
<pre><code>JPAQuery query = new JPAQuery(em);
List&lt;Member&gt; members = query.from(member)
                        .where(member.name.eq(&quot;회원1&quot;))
                        .orderBy(member.name.desc())
                        .list(member);</code></pre><pre><code>
### 검색 조건 쿼리
- QueryDSL의 where 절에는 and나 or, between, contains, startsWith 사용 가능

### 결과 조회
- uniqueResult(): 조회 결과가 한 건일 때 사용, 조회 결과가 없으면 null을 반환하고 하나 이상이면 com.mysema.query.NonUniqueResultException 예외 발생
- singleResult(): uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터 반환
- list(): 결과가 하나 이상일 대 사용, 결과가 없으면 빈 컬렉션 반환

### 페이징과 정렬
- 정렬은 orderBy를 사용하는데 쿼리 타입(Q)이 제공하는 asc(), desc() 사용
- 페이징은 offset과 limit 조합해서 사용
- 페이징은 restrict() 메소드에 com.mysema.query.QueryModifiers를 파라미터로 사용해도 됨
- 실제 페이징 처리를 하려면 검색된 전체 데이터 수를 알아야 하기 때문에 listResults() 사용 
➡️ 전체 데이터 조회를 위한 count 쿼리를 한 번 더 실행하고 SearchResults를 반환하는데 이 객체에서 전체 데이터 수를 조회할 수 있음

### 그룹
- groupBy
- having

### 조인
- innerJoin, join
- leftJoin, rightJoin, fullJoin
- fetch

### 서브 쿼리
- com.mysema.query.jpa.JPASubQuery 생성해서 사용
- 서브 쿼리의 결과가 하나면 unique(), 여러 건이면 list() 사용

### 프로젝션과 결과 반환
- 프로젝션 대상으로 여러 필드 선택하면 com.mysema.query.Tuple 사용
- 쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶으면 빈 생성 기능 사용
  - 프로퍼티 접근: Projections.bean()
  - 필드 직접 접근: Projections.fields()
  - 생성자 사용: Projections.constructor()
- distinct()

### 수정, 삭제 배치 쿼리
- QueryDSL도 JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리함
- 수정 배치 쿼리는 com.mysema.query.jpa.impl.JPAUpdateClause 사용
- 삭제 배치 쿼리는 com.mysema.query.jpa.impl.JPADeleteClause 사용

### 동적 쿼리
- com.mysema.query.BooleanBuilder

### 메소드 위임
- 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있음
~~~java
public class ItemExpression {

    @QueryDelegate(Item.class)
    public static BooleanExpression isExpensive(QItem item, Integer price) {
        return item.price.gt(price);
    }
}</code></pre><pre><code class="language-java">// 쿼리 타입에 생성된 결과
public class QItem extends EntityPathBase&lt;Item&gt; {
    ...
    public com.mysema.query.types.expr.BooleanExpression isExpensive(Integer price) {
        return ItemExpression.isExpensive(this, price);
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>