<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>kyo</title>
    <link>https://calgary.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 30 Jun 2026 13:51:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>캥거루</managingEditor>
    <item>
      <title>Kafka 개념, 구조</title>
      <link>https://calgary.tistory.com/82</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h1&gt;Kafka 란&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 링크드인에서 파편화된 데이터 파이프라인의 문제를 해결하기 위해 만들어졌다. 서비스 초기에는 데이터 사이즈가 크지 않고 파이프라인도 단순했지만, 시스템이 거대해지면서 데이터를 전달해야 할 노드의 수가 늘어났고 연결하는 시스템간의 복잡도가 매우 증가했다. 또한 MSA 를 채택하거나 기존 시스템을 마이그레이션하고, 연결해야 할 서비스들이 많아지고 처리해야 할 트래픽들은 높아지는데 장애 상황을 대비한 효과적인 대안들을 마련해야했다.&lt;br /&gt;&lt;b&gt;카프카는 대규모 데이터를 수집, 처리, 통합하기 위해 사용되는 이벤트 스트리밍 플랫폼이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;983&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFYQpq/btsiO5v0H3r/ZKH65qiOLnlso3LC8UTkc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFYQpq/btsiO5v0H3r/ZKH65qiOLnlso3LC8UTkc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFYQpq/btsiO5v0H3r/ZKH65qiOLnlso3LC8UTkc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFYQpq%2FbtsiO5v0H3r%2FZKH65qiOLnlso3LC8UTkc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;983&quot; height=&quot;381&quot; data-origin-width=&quot;983&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이벤트는 소프트웨어 또는 응용 프로그램에서 식별하거나 기록하는 모든 유형의 사건, 작업 또는 변경사항을 의미하고 또 다르게 메시지라고 말하기도 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 회원 시스템이 있다면 회원가입, 회원정보변경 등이 이벤트의 한 예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 시스템이라면 상품등록, 상품정보수정, 재고차감 등이 될 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문 시스템이라면 고객의 주문, 취소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지라면 사용자의 클릭 이벤트 등 이러한 이벤트들은 사용자의 다양한 데이터를 분석하기 위해 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 이벤트 발행자와 구독자 사이에서 중계자 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 작고 단순한 상황에서는 이벤트를 생성하는 시스템과 사용하는 시스템이 서로 직접 메시지를 정의하고 트래픽의 양이나 특성을 공유할 수 있었지만, &lt;b&gt;대규모 시스템에서는 이러한 시스템 간의 공유가 너무 많아 시스템의 복잡도가 크게 증가하고, 변경에 취약하고, 장애에 노출되기 쉽게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 브로커가 없는 상태라면 아래 그림과 같이 각각의 이벤트 발행자는 이벤트를 수신하거나 활용하는 시스템을 직접 연결해야한다. 또한 시스템 간의 직접적인 연결은 이후에 상대 시스템의 변경이나 장애 상황에도 직접 노출되기에 잠재적인 위협이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RoKIL/btsiSSoUVdd/aKrWPCGTionTHfDEWXworK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RoKIL/btsiSSoUVdd/aKrWPCGTionTHfDEWXworK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RoKIL/btsiSSoUVdd/aKrWPCGTionTHfDEWXworK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRoKIL%2FbtsiSSoUVdd%2FaKrWPCGTionTHfDEWXworK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;451&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 카프카와 같은 이벤트 스트리밍 플랫폼을 적용하면 이벤트 발행자는 카프카 브로커에게 메시지를 전달함으로써 &lt;b&gt;프로세스를 단순화할 수 있고, 서로 다른 시스템에 대한 의존성을 낮추며, 서로간의 변동이나 장애 등에서 비교적으로 자유롭게 될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 트래픽이 발생하는 상황에서 상대적으로 시간이 많이 발생하는 로직을 가지고 있는 기능이거나 여러 관심사를 한꺼번에 처리해야하는 로직에서는 동기 방식의 프로세스보다는 비동기 방식 프로세스를 적극 활용하는 것이 유리하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9kh67/btsiPjU6bZJ/lj3xsNmB2OpznFE5HQkq2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9kh67/btsiPjU6bZJ/lj3xsNmB2OpznFE5HQkq2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9kh67/btsiPjU6bZJ/lj3xsNmB2OpznFE5HQkq2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9kh67%2FbtsiPjU6bZJ%2Flj3xsNmB2OpznFE5HQkq2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;865&quot; height=&quot;451&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Zookeeper 란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주키퍼는 카프카 클러스터의 토픽 정보와 같은 메타 데이터를 관리하는 역할을 한다.&lt;/p&gt;
&lt;h1&gt;Kafka cluster 구조&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOFGhn/btsiJ7akkp9/EXHpwfxhPlkk1cbsCFKmK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOFGhn/btsiJ7akkp9/EXHpwfxhPlkk1cbsCFKmK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOFGhn/btsiJ7akkp9/EXHpwfxhPlkk1cbsCFKmK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOFGhn%2FbtsiJ7akkp9%2FEXHpwfxhPlkk1cbsCFKmK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1534&quot; height=&quot;789&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 대부분 3대 이상의 클러스터로 구성되어 사용할 것이다. 이는 서버에 장애가 생겨도 서버의 중단이나 데이터 유실없이 안정적으로 시스템을 운영하기 위한 필수적인 요구사항이다. 데이터 복제 즉 레플리케이션과 결합하여 고가용성 구성이라고 한다. 엘라스틱서치, 레디스, 카산드라 클러스터 등 대규모 트래픽 처리를 위한 분산 서비스 도구들은 구체적인 방법들은 조금씩 차이는 있지만 클러스터 구성과 데이터 레플리케이션을 통해 특정 노드의 시스템 장애상황에서도 서비스 위험을 최소화하기 위해 자기만의 아키텍처를 제시하고 있다.&lt;br /&gt;아래 그림에서 장애 상황을 가정하여 팔로워가 있는 브로커2에 장애가 발생할 경우 기본적으로 프로듀서와 컨슈머에 영향을 끼치지 않는다. &lt;b&gt;프로듀서와 컨슈머가 리더 파티션에서만 읽기와 쓰기를 하기 때문이다.&lt;/b&gt; 물론 이 경우에도 해당 토픽의 레플리케이션 팩터나 프로듀서 설정에 따라 영향을 받을수도 있다.&lt;br /&gt;&lt;b&gt;리더 파티션이 있는 브로커0에 장애가 생긴다면 카프카 클러스터 내에 컨트롤러 모듈이 이를 탐지하여 팔로워 파티션 중 하나를 즉시 리더 파티션으로 만들어 서비스를 계속할 수 있도록 한다.&lt;/b&gt; 리더가 결정되는 순간에는 일시적으로 읽기와 쓰기시에 타임아웃이 일어날 수 있는데 프로듀서는 새로운 리더 파티션이 확정될 때 까지 재시도를 통해 서비스 영향을 최소화할 수 있다. 리더 파티션 선정이 완료되면 프로듀서와 컨슈머는 다시 정상적인 발행과 수신을 이어나갈 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zsjxN/btsiFDgrevN/FWIo3RYJylh7J4jpHX4Gc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zsjxN/btsiFDgrevN/FWIo3RYJylh7J4jpHX4Gc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zsjxN/btsiFDgrevN/FWIo3RYJylh7J4jpHX4Gc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzsjxN%2FbtsiFDgrevN%2FFWIo3RYJylh7J4jpHX4Gc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1564&quot; height=&quot;615&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Topic&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WLILm/btsiOKyDMut/VTjhJafrS6YX8v6JPrXWo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WLILm/btsiOKyDMut/VTjhJafrS6YX8v6JPrXWo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WLILm/btsiOKyDMut/VTjhJafrS6YX8v6JPrXWo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWLILm%2FbtsiOKyDMut%2FVTjhJafrS6YX8v6JPrXWo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1622&quot; height=&quot;729&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;토픽은 하나의 주제 즉 데이터 또는 메시지 종류를 구분하기 위한 카프카의 기본 단위이다.&lt;/b&gt;&lt;br /&gt;예를 들어 주문 서비스 개발팀에서 주문완료 라는 토픽을 생성하고 새로운 주문이 완료될 때마다 주문정보를 해당 토픽에 지속적으로 발행해줄 수 있다.&lt;br /&gt;&lt;b&gt;토픽은 프로듀서와 컨슈머가 이벤트 종류를 구별하는 문자열로 이름을 정할 때 나름의 네이밍룰을 고려해두는 것이 좋다.&lt;/b&gt; 메시지를 발행하는 팀이나 도메인의 이름, 이벤트의 종류, 버전 등의 값을 이용하여 문자열만으로 그 의미를 쉽게 유추할 수 있게 하는 것이 좋다.&lt;br /&gt;토픽을 생성할 때 하위 요소인 파티션의 레플리케이션 팩터를 설정하는 옵션이 있는데 최소 3을 설정하는 것이 바람직하다. 레플리케이션 팩터를 디폴트값인 1로 설정하면 원본 이외에 복제를 수행하지 않겠다는 설정이고, 3이면 2개의 복제본을 생성하겠다는 의미이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Partition&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로듀서가 토픽을 지정하여 메시지를 발행하면 브로커는 해당 토픽의 하위 저장소인 파티션에 메시지를 안전하게 저장한다.&lt;/b&gt; 카프카는 운영체제의 파일시스템 자체를 저장소로 활용하는데, 그래서 토픽을 파일시스템의 폴더와 같다고 생각하면 된다.&lt;br /&gt;이는 server.properties 파일에 log.dirs 옵션을 확인하면 되는데, 이 위치는 카프카의 메시지가 저장되는 위치이다.&lt;br /&gt;&lt;b&gt;레플리케이션은 이 파티션 단위로 실행되어 복제본 생성을 통해 브로커 장애에 대한 대비를 한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;리더 파티션은 프로듀서와 컨슈머가 연결되어 읽기와 쓰기가 실행되는 파티션이다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;팔로워 파티션은 레플리케이션 팩터에 의해 만들어진 복제본으로 장애 상황에 대비하여 준비되어 있을 뿐 실제 프로듀서와 컨슈머가 직접 연결되어 있지는 않다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;팔로워 파티션들은 리더 파티션이 문제가 생겼을 때 언제라도 리더가 될 수 있도록 데이터를 지속적으로 싱크하여 만약의 장애 상황을 대비하고 있다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;보통의 스토리지들이 복제본을 통해 읽기 트래픽을 분산시키는 경우가 많은데, 카프카의 경우 리더 파티션에서만 읽기와 쓰기가 일어난다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Partition 확장&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BTYNK/btsiFC9JwVA/HlCCYXgRjlQNbyJTSLxZDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BTYNK/btsiFC9JwVA/HlCCYXgRjlQNbyJTSLxZDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BTYNK/btsiFC9JwVA/HlCCYXgRjlQNbyJTSLxZDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBTYNK%2FbtsiFC9JwVA%2FHlCCYXgRjlQNbyJTSLxZDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1624&quot; height=&quot;629&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카프카는 리더 파티션에서만 읽기와 쓰기가 일어나서 만약 하나의 파티션만 사용한다면 많은 머신과 브로커를 준비하느라 처리량 확대는 제한적일 수 밖에 없을 것이다. 카프카는 하나의 토픽에 인입되는 메시지를 분할하여 개별 파티션에 나누어 처리하도록 하여 이 문제를 해결한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에는 topic-example1 에 4개의 파티션으로 구분되어 있다. 이 그림처럼이면 하나의 토픽에 하나의 파티션만 있을 때 보다 분산환경에 장점을 살릴 수 있고, 훨씬 많은 동시처리 능력을 제공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토픽을 생성할 때 파티션의 개수는 동시처리량과 깊은 관계를 갖기 때문에 토픽 생성시에 주요한 관심사인데, 해당 토픽에서 예상되는 메시지 인입량과 컨슈머의 처리량 등을 고려하여 초기값을 설정하고, 이후 서비스를 운영하는 과정에서 모니터링을 통해 파티션을 확대하는 경우도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Parition 내에 데이터 소비방식&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7gfgB/btsiU43OM8R/MxfRgbIsJfkQ1hKTos2sA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7gfgB/btsiU43OM8R/MxfRgbIsJfkQ1hKTos2sA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7gfgB/btsiU43OM8R/MxfRgbIsJfkQ1hKTos2sA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7gfgB%2FbtsiU43OM8R%2FMxfRgbIsJfkQ1hKTos2sA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1620&quot; height=&quot;347&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션 내부는 하나의 큐처럼 보인다. 일반적인 메시지큐는 컨슈머가 데이터를 가져가면 브로커에 메시지는 사라지는데, &lt;b&gt;카프카는 컨슈머가 데이터를 가져가는 것과 상관없이 파티션 내에 고유의 순서에 따라 메시지를 저장하다가 토픽에 지정된 시간이 지나면 자동으로 오래된 메시지가 삭제되는 방식으로 메시지를 관리한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;카프카는 오프셋을 통해 각각의 컨슈머 그룹이 각각의 파티션별로 읽어간 레코드의 위치를 관리한다. 오프셋은 컨슈머 그룹이 이미 가져간 마지막 레코드에 대한 포인터인데,&lt;/b&gt; 컨슈머 그룹은 동일한 그룹명을 사용하는 컨슈머들의 집합이다.&lt;br /&gt;예를 들어 프로듀서가 메시지를 발행하여 파티션 내에 순서에 따라 기록하여 마지막으로 4번의 오프셋에 레코드를 저장했을경우 새로운 컨슈머가 메시지를 읽어간 후 컨슈머 내에서 정상적으로 데이터를 처리하게 되면 컨슈머는 커밋 메시지를 카프카에 전달하여 current offset 을 이동하게 된다. 이후 프로듀서가 5,6번의 메시지를 전송하면 컨슈머가 current offset 이후부터 다시 메시지를 읽어가기 시작한다.&lt;br /&gt;current offset 은 컨슈머 그룹별로 카프카 내부에 저장되고 있기에 새로운 컨슈머 그룹은 기존 컨슈머 그룹이 얼마만큼의 메시지를 읽어갔는지와 무관하게 from, beginning 옵션을 통해 처음부터 메시지를 읽어가거나 그렇지 않으면 현재 접속한 시점 이후 새로 인입된 메시지부터 읽어갈 수 있다.&lt;br /&gt;파티션을 여러 개 만들게 되면 컨슈머 그룹내에 컨슈머 숫자를 증가시켜 병렬처리 능력을 향상 시킬 수 있어 실무에서는 특별한 경우가 아니면 파티션을 다수로 설정하여 처리량 증가를 도모한다.&lt;br /&gt;&lt;b&gt;토픽이 여러 개의 파티션으로 나뉘게 되면 프로듀서가 메시지 발행 시 여러 메시지를 어떤 파티션에 기록할 지 결정할 방법이 필요하다. 일반적으로 발행 메시지 내에 키 값이 비어있으면 메시지들은 모든 파티션에 라운드 로빈 방식으로 기록된다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B1K1Y/btsiRExzaPY/vZELKHnsYmsU5zKq9yxGbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B1K1Y/btsiRExzaPY/vZELKHnsYmsU5zKq9yxGbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B1K1Y/btsiRExzaPY/vZELKHnsYmsU5zKq9yxGbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB1K1Y%2FbtsiRExzaPY%2FvZELKHnsYmsU5zKq9yxGbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1151&quot; height=&quot;490&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하나의 파티션을 사용할 때는 카프카에 인입된 순서 그대로 컨슈머에서 읽어가는 순서를 보장할 수 있지만 여러 개의 파티션에 라운드 로빈 방식으로 메시지를 기록하게 되면 컨슈머가 메시지를 처리할 때 반드시 카프카의 인입된 순서대로 처리하는 것을 보장하지 않는 특성이 있다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;컨슈머의 메시지 처리순서를 보장하기 위해서는 파티션을 한 개만 설정하거나 여러 개 파티션을 사용하게 되면 라운드 로빈 방식이 아니라 발행 메시지의 키 설정을 통해 특정한 메시지 키의 경우 동일한 파티션에 할당되도록 강제할 수 있다.&lt;/b&gt;&lt;br /&gt;예를 들어 모두 동일한 고객과 연결된 이벤트를 생성한 경우 컨슈머가 메시지를 처리할 시 카프카에 인입순서가 중요하다면 고객의 아이디를 키로 설정하여 모두 동일한 파티션으로 할당될 수 있도록하고, 해당 고객의 모든 이벤트가 항상 순서대로 도착하도록 할 수 있다. &lt;b&gt;메시지 순서가 매우 중요한 시스템이라면 파티션 개수 및 메시지 키 설정, 컨슈머 처리 프로세스를 고민해볼 필요가 있지만 이러한 제약은 분산 병렬처리 관점에서의 이점을 약화시키고 메시지 처리코드의 복잡성을 증가시키거나 메시지 처리의 어려움을 발생시킬 수 있기에 순서에 기반한 처리가 반드시 필요한 것인지 신중히 검토해서 결정할 필요가 있다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;만약 가능하다면 메시지 발행 순서에 의존하지 않도록 메시지 스펙이나 데이터 플로우 설계를 해보는 것이 시스템 관점에서 유연성을 제공하는데 더 이로울 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;Kafka connect&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카프카 커넥트는 카프카를 이용한 퍼블리싱이나 컨슈밍같은 반복적인 개발을 단순히 하기 위해서 카프카 오픈소스에 포함되어 있는 모듈이다.&lt;/b&gt; 커넥트용 JAR 파일과 설정파일을 이용해서 지속적으로 로그파일을 읽어들여 카프카에 퍼블리싱하거나 컨슘해서 엘라스틱 서치에 기록하는 행위들을 단순화할 수 있는 도구이다.&lt;br /&gt;오픈소스 형태로 다양한 소스 커넥트와 싱크 커넥트가 공개되고 있다.&lt;br /&gt;파일 싱크 커넥트는 카프카의 특정 토픽을 컨슘하여 파일에 지속적으로 쓰기를 해주는 커넥터이다.&lt;/p&gt;</description>
      <category>IT/대용량 데이터&amp;amp;트래픽 처리</category>
      <category>Kafka</category>
      <category>메시지큐</category>
      <category>브로커</category>
      <category>이벤트스트리밍</category>
      <category>카프카</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/82</guid>
      <comments>https://calgary.tistory.com/82#entry82comment</comments>
      <pubDate>Tue, 6 Jun 2023 15:22:16 +0900</pubDate>
    </item>
    <item>
      <title>Redis 에 대해서 (세션, 캐시, 클러스터, 쿼리튜닝)</title>
      <link>https://calgary.tistory.com/81</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 Redis7, SpringBoot2.7 기준으로 작성되었습니다.&lt;/p&gt;
&lt;h1&gt;Redis 란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레디스는 손쉽게 사용할 수 오픈소스 인메모리 저장소이다.&lt;/b&gt; 높은 성능을 가지고 다양한 곳에 활용할 수 있다. 관계형 데이터베이스와 다르게 테이블 구조가 아닌 String, Set, Map, Sorted Map 등 여러 데이터 저장소를 가지고 있어 유연성이 좋아 많은 기능들을 구현할 수 있고, 현대적인 서버 구조에서 세션 관리나 캐시는 빠질 수 없는 구성요소로 많이 이용된다. 많은 서비스들이 점점 더 속도가 빨라져야하고, 많은 유저의 트래픽을 감당 해야하는 상황에서 분산환경에서 캐시, 세션관리가 필수이고, 개발자는 개발하기 위한 기능에만 집중하고 데이터 저장과 읽기는 쉽게 구현하고, 문제가 생길경우 계층이 구분되어 있기에 문제가 생긴 부분만 확인해보면 된다는 이점이 있다.&lt;br /&gt;&lt;b&gt;Redis 는 Remote Dictionary Server 의 약자로 원격지에 딕셔너리 방식으로 데이터를 저장하는 서버라는 뜻이다.&lt;/b&gt; 데이터 관점에서는 스토리지이고, 영속성 관점에서는 전통적인 DBMS 역할을 수행한다. 또한 미들웨어로 볼 수도 있는데, 최종적인 서비스를 제공하는 어플리케이션이 아니라 어플리케이션이 자신의 서비스를 제공하기 위해 이용하는 중간적인 기능을 가진 소프트웨어이다. 레디스는 단순하게 데이터를 저장하고 읽어오는 데이터 저장소이지만, 기능들이 다양하여 미들웨어라고도 볼 수 있을것이다.&lt;br /&gt;&lt;b&gt;레디스는 Key-value 저장소로 키를 이용해서 연관된 값을 저장하는 구조로 키를 가지고 데이터 조회, 수정, 삭제를 할 수 있으며 가장 단순한 데이터 저장 방식이여서 빠르고 성능이 좋다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;기존에는 개발 방식이 RDB 를 사용해서 복잡한 관계를 정의해서 저장하는 방식이었다면, 최근에는 데이터는 단순화하고, 분산을 많이 할 수 있는 방식으로 변화하고 있다. 이런 환경에서 키밸류 스토어의 활용성이 커지고, 레디스가 많이 사용되는 이유라고 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;In-memory Database 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 디스크에 저장하지 않고, 휘발성인 RAM 에 저장하며, 디스크와 비교해서 빠른 속도를 가지지만, 가격이 비싸다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Key-value 구조의 장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순해서 사용하기가 쉬움&lt;/li&gt;
&lt;li&gt;해시를 이용해서 값을 바로 읽으므로 속도가 빠름 (추가 연산이 필요없다)&lt;br /&gt;키가 밸류의 위치를 찾아내는 방식은 해싱을 사용한다. 해싱은 해싱 알고리즘을 사용하면 어떤 값을 해시값으로 변환할때 복잡한 연산없이 아주 빠른 속도로 수행할 수 있다.&lt;/li&gt;
&lt;li&gt;분산환경에서의 수평적 확장성 (스케일 아웃)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Key-value 구조의 단점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키를 통해서만 값을 읽을 수 있음 (데이터로 검색을 할 수 없다)&lt;/li&gt;
&lt;li&gt;범위 검색 등의 복잡한 질의가 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 활용범위&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아주 빠른 데이터 저장소로 활용가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산된 서버들간의 커뮤니케이션 (동기화, 작업분할 등)&lt;/b&gt;&lt;br /&gt;분산된 서버들간에 데이터를 공유할 수 있다. 예를들면 레디스는 외부 저장소이기에 세션과 같은 데이터는 복제된 서버들간에 공유가 되어야하는 데이터인데, 레디스는 이러한 데이터를 서버들간에 공유하는데 자주 사용된다.&lt;/li&gt;
&lt;li&gt;내장된 자료구조를 활용한 기능 구현&lt;br /&gt;레디스의 내장된 자료구조를 사용해서 여러 기능을 쉽게 구현할 수 있는데 예를들면 Sorted set 을 이용한 랭킹 시스템을 쉽게 구현할 수 있다. 지금도 레디스에 많은 기능들이 계속 추가되고 있고, 카프카에서 제공하는 pub/sub 이나 stream 과 같은 기능들이 추가되고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레디스는 세션 스토어, 캐시, Rate limiting(API 요청량 제한), Job queue(여러 서버들간에 task job 을 저장해두었다가 한 쪽에서는 소비해가는 작업) 이러한 것들을 레디스를 사용하면 쉽게 구현이 가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;휘발성 성질을 가진 레디스를 어떻게 사용하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 사용방법은 레디스에 세션 데이터와 같은 단기적인 데이터를 사용하는 것이다.&lt;br /&gt;DB에 들어가는 데이터는 영속성이 절대적으로 필요한 데이터, 높은 무결성을 필요로 하는 데이터가 들어가고, 세션 데이터와 같은 것들은 사용자의 임시적인 로그인 상태를 나타내는 것이기에 만약 레디스에 장애가 발생해서 세션 데이터가 사라지더라도 사용자에게 미치는 영향은 로그인 상태가 해제되어 재로그인이 필요로 하게 되는 정도이다. 물론 이 또한 발생해서는 안되지만 DB의 장애보다는 비교적 영향력이 덜 하다. &lt;b&gt;즉, 레디스로 사용하기 적절한 세션 데이터와 같은 것들은 라이프사이클이 짧은 데이터라고 볼 수 있다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;세션 데이터 외에도 캐시로 사용할 수도 있는데 캐시는 어플리케이션과 DB의 중간에서 디스크의 접근 횟수를 줄이기 위해 DB에 가기전에 먼저 레디스에 저장해두고 동일한 데이터 요청이 왔을때 레디스에서 읽어가는 것이다.&lt;/b&gt; 이럴때도 레디스에 장애가 발생했을때 사라지는 것은 캐시 데이터이고, 원천 데이터는 디스크에 있어 DB에서 다시 읽어올 수 있다. 그리고 장애가 복구된 후에 레디스에 다시 캐시 데이터를 넣을 수 있다. 이런 장애가 일시적으로 서비스의 속도를 늦출수는 있지만 환경에 따라 사용자가 크게 체감할 수 있는 장애상황은 아닐것이다.&lt;br /&gt;또한, &lt;b&gt;레디스도 어느정도 영속성을 가질 수 있다. 레디스가 제공하는 백업방식을 이용하면 장애가 생겨도 재시작 되었을 때 기존에 가지고 있던 데이터를 디스크에 저장해두었다가 다시 읽어가는 방식으로 어느정도 영속성을 가지고 있다. 하지만 DBMS 만큼은 아니기 때문에 이 부분도 절충을 통해 절대적인 무결성이 필요한 데이터는 DBMS 에서 관리하는 것이 나을 것이다. 또한, 높은 안정성을 얻기 위해서는 어느정도 속도의 희생이 필요하기에 비효율적이고 영속성을 위해서만 사용하는 경우는 없을 것이다.&lt;/b&gt;&lt;br /&gt;정리하면 용도에 맞게 DB와 레디스를 사용하고, 혼합해서 사용하기에도 좋고, 레디스 백업 기능으로 영속성을 확보할 수도 있다.&lt;/p&gt;
&lt;h1&gt;Redis 데이터 타입의 이해&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Strings&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 기본적인 데이터 타입으로 제일 많이 사용됨 (키, 밸류형태)&lt;/li&gt;
&lt;li&gt;바이트 배열로 저장됨 (binary-safe)&lt;/li&gt;
&lt;li&gt;바이너리로 변환할 수 있는 모든 데이터를 저장 가능 (jpg 와 같은 파일 등)&lt;/li&gt;
&lt;li&gt;최대 크기는 512mb&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 문자는 여기에 들어갈 수 있다. null 도 자체로 문자 코드가 있어 가능하다. 즉, 바이트로 표현했을 때 어떠한 값이 있을것이고, 이러한 경우는 모두 저장이 가능하다. 심지어 jpg 와 같은 파일도 넣을 수 있다. 컴퓨터로 표현할 수 있는 데이터는 바이트로 표현이 가능하여 어떠한 데이터도 스트링으로 넣을 수 있다. 주로 많이 사용하는 것은 캐시와 같은 것인데, 웹 브라우저에서 표시되는 웹 컨텐츠 html 엘리먼트로 이루어진 것들을 저장하기도 하고, 이런 문자열 데이터 타입의 최대 크기는 512 mb 이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Strings 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcz6DJ/btsiOfTk7XZ/w66Ils1HvuBYyqreCgAuM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcz6DJ/btsiOfTk7XZ/w66Ils1HvuBYyqreCgAuM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcz6DJ/btsiOfTk7XZ/w66Ils1HvuBYyqreCgAuM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcz6DJ%2FbtsiOfTk7XZ%2Fw66Ils1HvuBYyqreCgAuM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;264&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;incr 과 decr 은 동시처리를 해도 중복되거나 누락되는 일 없이 최종 결과는 항상 동일하게 나온다. 실제로 게시물 좋아요 수를 실시간으로 카운팅하고 공유하고 싶을 때 서버를 분산시켜 여러 서버에서 한 레디스로 incr 와 decr 를 빈번하게 호출할 것이고, 이 연산은 연산결과가 잘못되지 않고, 중복이나 누락 없이 정확한 결과가 저장이 되어 손쉽게 카운터를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 set, get 으로 작은 사이즈를 여러번 호출하게 되면 네트워크 비용이 높아지는데, mset, mget 은 한 번에 통신해서 많은 데이터를 처리할 수 있게 하여 성능 향상에 큰 도움이 될 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lists&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfq6fs/btsiJzSyGS4/7d1pKUxkDk8Qbn7bw761OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfq6fs/btsiJzSyGS4/7d1pKUxkDk8Qbn7bw761OK/img.png&quot; data-alt=&quot;Linked-list&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfq6fs/btsiJzSyGS4/7d1pKUxkDk8Qbn7bw761OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfq6fs%2FbtsiJzSyGS4%2F7d1pKUxkDk8Qbn7bw761OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;100&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Linked-list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linked-list 형태의 자료구조 (인덱스 접근은 느리지만 데이터 추가, 삭제가 빠름)&lt;/li&gt;
&lt;li&gt;Queue 와 Stack 으로 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트는 키 밸류 형태에서 밸류안에 하나의 값이 아닌 여러 개의 값이 집합으로 들어가있는 형태인데, 자료구조는 linked-list 형태를 띄고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크드 리스트는 데이터들이 일종의 포인터로 연결된 상태로 중간이나 양끝에 데이터를 삽입, 삭제하는게 성능이 아주 좋은 특성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;array 와 비교를 해서 설명할 때 array 는 인덱스를 통해서 빠른 접근을 할 수 있지만, 데이터 삽입, 삭제가 성능면에서 불리하다. 링크드 리스트는 인덱스로 바로 접근은 할 수 없지만 데이터 추가, 삭제가 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, queue 와 stack 으로 사용할 수도 있다. 앞, 뒤 데이터를 넣고 빼는 방식으로 앞에서 넣고 앞에서 빼면 stack 이 되고, 앞에서 넣고 뒤에서 빼면 queue 가 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lists 의 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci287Q/btsiFCIEcGm/JozswQWkXnjMrHRonvRhXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci287Q/btsiFCIEcGm/JozswQWkXnjMrHRonvRhXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci287Q/btsiFCIEcGm/JozswQWkXnjMrHRonvRhXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci287Q%2FbtsiFCIEcGm%2FJozswQWkXnjMrHRonvRhXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;263&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRANGE 0 은 시작점, -1 은 끝점(가장 오른쪽), -2 는 가장 오른쪽에서 바로 왼쪽이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sets&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2D8mA/btsiOiWIn7A/qvGG0EGm66UhnXCZzejzkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2D8mA/btsiOiWIn7A/qvGG0EGm66UhnXCZzejzkk/img.png&quot; data-alt=&quot;Set&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2D8mA/btsiOiWIn7A/qvGG0EGm66UhnXCZzejzkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2D8mA%2FbtsiOiWIn7A%2FqvGG0EGm66UhnXCZzejzkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;327&quot; height=&quot;100&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Set&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순서가 없는 유니크한 값의 집합&lt;/li&gt;
&lt;li&gt;검색이 빠름&lt;/li&gt;
&lt;li&gt;개별 접근을 위한 인덱스가 존재하지 않고, 집합 연산이 가능 (교집합, 합집합 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 set 안에 같은 값을 여러 번 넣더라도 중복으로 여러 개가 들어가지 않고, 하나로만 들어가는 특성이 있다. 이 데이터 구조를 사용하는 이유는 검색이 빨라서 특정 값이 set에 포함되어 있는지 아닌지를 빠르게 알아낼 수 있기 때문이다. 그래서 어떤 인덱스를 통해서 특정 위치에 있는 값에 접근할 수 는 없고, 개별값에 대한 존재여부를 체크하거나 집합연산이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sets 의 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxqD2I/btsiQ6AXndS/KCAZ6dDsv8CrYvkorWwBU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxqD2I/btsiQ6AXndS/KCAZ6dDsv8CrYvkorWwBU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxqD2I/btsiQ6AXndS/KCAZ6dDsv8CrYvkorWwBU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxqD2I%2FbtsiQ6AXndS%2FKCAZ6dDsv8CrYvkorWwBU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;222&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지에서 서비스가 특정시간동안 유효한 쿠폰을 발급한다고 하면 사용자들은 딱 한 번씩만 그 쿠폰을 발급 받을 수 있다. 그러면 쿠폰을 발급 받을 수 있게 처리를 시작하기 앞서서 그 사용자가 지금 시간대에 쿠폰을 발급 받지 않았는지를 빠르기 확인을 해야하는데, 이럴 때는 set에 사용자 아이디를 저장해 놓으면 빠르게 확인이 가능하다. 그래서 모든 사용자의 요청에 set 에서 그 사용자가 포함되어 있는지, 즉 쿠폰을 이미 받았는지 여부를 저장해놓고 빠르게 확인하는 활용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SISMEMBER 는 이 set 에 데이터가 몇 개가 들어있던지 상관없이 동일한 수행속도를 보장한다. 그래서 활용도가 커진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hashes&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/laA8A/btsiO6avlZQ/W7l3CpsDpSlxjXEFxKZQN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/laA8A/btsiO6avlZQ/W7l3CpsDpSlxjXEFxKZQN0/img.png&quot; data-alt=&quot;Hash&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/laA8A/btsiO6avlZQ/W7l3CpsDpSlxjXEFxKZQN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlaA8A%2FbtsiO6avlZQ%2FW7l3CpsDpSlxjXEFxKZQN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;804&quot; height=&quot;237&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Hash&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 key 아래에 여러 개의 field-value 쌍을 저장&lt;/li&gt;
&lt;li&gt;여러 필드를 가진 객체를 저장하는 것으로 생각할 수 있음&lt;/li&gt;
&lt;li&gt;HINCRBY 명령어를 사용해 카운터르 활용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트링으로도 해시의 구조처럼 만들 수 있다. user1 이라는 키에 name 과 age 를 json 형태로 만들어서 넣어도 되고, 이렇게 하면 필드의 일부만 접근하려고 해도 전체 스트링을 불러와서 파싱을 해서 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해시를 사용하면 특정 필드를 지정해서 값을 가져올 수 있어 접근성이 좋다. 다만 여러 필드에 동시에 접근해야 한다면 해시를 사용하면 조금 불편해진다. 이 때는 스트링으로 json object 로 만들면 한 번에 불러올 수 있어 편의성이 좀 더 높아진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hashes 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfk4dx/btsiU5IqchU/8DHD131WBueUxIyErQFve0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfk4dx/btsiU5IqchU/8DHD131WBueUxIyErQFve0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfk4dx/btsiU5IqchU/8DHD131WBueUxIyErQFve0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfk4dx%2FbtsiU5IqchU%2F8DHD131WBueUxIyErQFve0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;729&quot; height=&quot;235&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HINCRBY 명령어를 사용하면 숫자를 int 로 취급해서 카운터 등으로 사용할 수 있다. 기능수, 클릭수 등 각각의 필드에 접근해서 변경하는 등 활용이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sorted sets&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3w1fI/btsiM8NIRbc/vhzpn4r8aHCgekDjS8bQXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3w1fI/btsiM8NIRbc/vhzpn4r8aHCgekDjS8bQXK/img.png&quot; data-alt=&quot;Sorted set&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3w1fI/btsiM8NIRbc/vhzpn4r8aHCgekDjS8bQXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3w1fI%2FbtsiM8NIRbc%2Fvhzpn4r8aHCgekDjS8bQXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;186&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sorted set&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Set 과 유사하게 유니크한 값의 집합&lt;/li&gt;
&lt;li&gt;각 값은 연관된 score 를 가지고 정렬되어 있음&lt;/li&gt;
&lt;li&gt;정렬된 상태이기에 빠르게 최소, 최대값을 구할 수 있음&lt;/li&gt;
&lt;li&gt;순위 계산, 리더보드 구현 등에 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sorted sets 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd3rtB/btsiPP0DZaa/CBaeGQKegsBN4paOKe6Tuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd3rtB/btsiPP0DZaa/CBaeGQKegsBN4paOKe6Tuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd3rtB/btsiPP0DZaa/CBaeGQKegsBN4paOKe6Tuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd3rtB%2FbtsiPP0DZaa%2FCBaeGQKegsBN4paOKe6Tuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;238&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZADD 의 경우 동일한 값이 있으면 스코어를 변경한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bitmaps&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU5LOV/btsiO6O6X1V/DNUIME31kDjFtNwKUXvkI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU5LOV/btsiO6O6X1V/DNUIME31kDjFtNwKUXvkI1/img.png&quot; data-alt=&quot;Bitmap&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU5LOV/btsiO6O6X1V/DNUIME31kDjFtNwKUXvkI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU5LOV%2FbtsiO6O6X1V%2FDNUIME31kDjFtNwKUXvkI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;644&quot; height=&quot;158&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Bitmap&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비트 벡터를 사용해 N개의 Set 을 공간 효율적으로 저장&lt;/li&gt;
&lt;li&gt;하나의 비트맵이 가지는 공간은 4,294,967,295 (2^32-1)&lt;/li&gt;
&lt;li&gt;비트 연산 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트맵을 사용하면 공간을 굉장히 효율적으로 사용할 수 있다. 예를들어 특정일에 어떤 사이트의 사용자들의 방문현황을 저장해야한다고 할 때 인덱스는 유저 번호를 나타내고, 0과 1로 방문현황을 구분할 수있다. 42억명의 방문현황을 확인해도 4바이트 (integer 크기) 밖에 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 방문현황을 today visit, 어제의 방문현황을 yesterday visit 비트맵을 만들어서 어제와 오늘 방문한 사용자의 수를 구하고 싶을 경우 위 두 비트맵을 and 연산하여 둘 다 1인 비트만 결과로 나와서 활용이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bitmaps 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAqhFl/btsiOKMcAaH/68YUIFw472nO5G10ZEqAOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAqhFl/btsiOKMcAaH/68YUIFw472nO5G10ZEqAOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAqhFl/btsiOKMcAaH/68YUIFw472nO5G10ZEqAOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAqhFl%2FbtsiOKMcAaH%2F68YUIFw472nO5G10ZEqAOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;197&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HyperLogLog&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2EDo/btsiFPnw4xi/3vsiPyzeVUut3OSLOYFDj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2EDo/btsiFPnw4xi/3vsiPyzeVUut3OSLOYFDj1/img.png&quot; data-alt=&quot;HyperLogLog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2EDo/btsiFPnw4xi/3vsiPyzeVUut3OSLOYFDj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2EDo%2FbtsiFPnw4xi%2F3vsiPyzeVUut3OSLOYFDj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;186&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HyperLogLog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유니크한 값의 개수를 효율적으로 얻을 수 있음&lt;/li&gt;
&lt;li&gt;확률적 자료구조로서 오차가 있으며, 매우 큰 데이터를 다룰 때 사용&lt;/li&gt;
&lt;li&gt;18,446,744,073,709,551,616 (2^64) 개의 유니크 값을 계산 가능&lt;/li&gt;
&lt;li&gt;12KB 까지 메모리를 사용하며 0.81% 의 오차율을 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트 카운트와 비슷한 용도를 가진다. 100% 정확한 값을 보장하지는 않고, 약간의 정확도를 포기함으로써 더 큰 효율성을 얻는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트맵에서는 방문자수를 카운트할 때 특정 오프셋을 가지고 해야했기 때문에 사용자 아이디와 같은 숫자에 매핑시킬 수 밖에 없었다. 하지만 하이퍼로그로그를 사용하면 어떤 문자열이든 사용을 할 수 있기에 이름이나 브라우저 아이디 혹은 PC 하드웨어의 아이디를 그대로 사용할 수 있어 자유도가 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도는 정확히 카운팅을 위한 것이고 데이터가 많을 때 효과가 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 값을 넣을 때 내부에 데이터를 저장하지 않고, 확률적 데이터 구조를 내부적으로 가지고 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HyperLogLog 주요 명령어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc0qpL/btsiM871tmH/ZnKLqiTWjxBWEQ4GxqFfCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc0qpL/btsiM871tmH/ZnKLqiTWjxBWEQ4GxqFfCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc0qpL/btsiM871tmH/ZnKLqiTWjxBWEQ4GxqFfCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc0qpL%2FbtsiM871tmH%2FZnKLqiTWjxBWEQ4GxqFfCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;153&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PFMERGE 를 사용하면 두 개의 데이터가 합쳐진 형태로 중복은 걸러지고, 유니크한 값들의 개수를 얻어낼 수 있다.&lt;/p&gt;
&lt;h1&gt;Redis 연동&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 라이브러리 사용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lettuce 는 가장 많이 사용되는 라이브러리로 Spring data redis 에 내장되어 있음&lt;/li&gt;
&lt;li&gt;Spring data redis 는 Redis template 이라는 레디스 조작의 추상 레이어를 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIYiYo/btsiOfFPUwC/LoY9RyQLizmO4ImXksuhJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIYiYo/btsiOfFPUwC/LoY9RyQLizmO4ImXksuhJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIYiYo/btsiOfFPUwC/LoY9RyQLizmO4ImXksuhJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIYiYo%2FbtsiOfFPUwC%2FLoY9RyQLizmO4ImXksuhJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1445&quot; height=&quot;335&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레터스가 스프링 데이터 레디스에 내포되어 있는데, 추후에 레터스가 변경되어도 어플리케이션은 변경이 필요하지 않다. 스프링 데이터 레디스라는 레이어를 통해 연동 구현을 했기 때문이며, 이러한 점이 추상 레이어의 장점이다.&lt;/p&gt;
&lt;h1&gt;분산환경에서 Session store 만들기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Session&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 상에서 두 개 이상의 통신장치간에 유지되는 상호 연결&lt;/li&gt;
&lt;li&gt;연결된 일정시간동안 유지되는 정보를 나타냄&lt;/li&gt;
&lt;li&gt;적용 대상에 따라 다른 의미를 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 로그인 세션&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹상에서 특정 사용자가 로그인했음을 나타내는 정보&lt;/li&gt;
&lt;li&gt;브라우저는 쿠키를, 서버는 해당 쿠키에 연관된 세션정보를 저장함&lt;/li&gt;
&lt;li&gt;사용자가 로그아웃하거나 세션이 만료될때까지 유지되어 사용자에 특정한 서비스를 가능하게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GzEqy/btsiPQec1Js/m0lZJX1do7BXDFTGfXHX71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GzEqy/btsiPQec1Js/m0lZJX1do7BXDFTGfXHX71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GzEqy/btsiPQec1Js/m0lZJX1do7BXDFTGfXHX71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGzEqy%2FbtsiPQec1Js%2Fm0lZJX1do7BXDFTGfXHX71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;145&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 로그인 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGV4Wj/btsiU5Iqcis/jkUs0jRaehAfDtScAdcH20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGV4Wj/btsiU5Iqcis/jkUs0jRaehAfDtScAdcH20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGV4Wj/btsiU5Iqcis/jkUs0jRaehAfDtScAdcH20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGV4Wj%2FbtsiU5Iqcis%2FjkUs0jRaehAfDtScAdcH20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;286&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분산환경에서의 세션 처리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 세션 정보를 저장해야 함&lt;/li&gt;
&lt;li&gt;서버가 여러 대라면 최초 로그인한 서버가 아닌 서버는 세션 정보를 알지 못함&lt;/li&gt;
&lt;li&gt;세션 정보를 서버간에 공유할 방법이 필요함 (Session clustering)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm1pse/btsiSRDxl1A/WyZeNWlodFJVIvKT8nKGa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm1pse/btsiSRDxl1A/WyZeNWlodFJVIvKT8nKGa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm1pse/btsiSRDxl1A/WyZeNWlodFJVIvKT8nKGa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm1pse%2FbtsiSRDxl1A%2FWyZeNWlodFJVIvKT8nKGa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;185&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분산환경에서 RDB 를 사용한 세션 처리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계형 데이터 모델이 필요한가?&lt;/li&gt;
&lt;li&gt;영속성이 필요한 데이터인가?&lt;/li&gt;
&lt;li&gt;성능 요구사항을 충족하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq7vxp/btsiOivDdNR/D86SK4SMF1x5UMPOszfDjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq7vxp/btsiOivDdNR/D86SK4SMF1x5UMPOszfDjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq7vxp/btsiOivDdNR/D86SK4SMF1x5UMPOszfDjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq7vxp%2FbtsiOivDdNR%2FD86SK4SMF1x5UMPOszfDjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;193&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분산환경에서 Redis 를 사용한 세션 처리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 데이터는 단순 key-value 구조&lt;/li&gt;
&lt;li&gt;세션 데이터는 영속성이 필요 없음&lt;/li&gt;
&lt;li&gt;세션 데이터는 변경이 빈번하고 빠른 액세스 속도가 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EmQpJ/btsiOjae6Dh/jKBaQCk3mN9Vx3qhy7IWmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EmQpJ/btsiOjae6Dh/jKBaQCk3mN9Vx3qhy7IWmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EmQpJ/btsiOjae6Dh/jKBaQCk3mN9Vx3qhy7IWmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEmQpJ%2FbtsiOjae6Dh%2FjKBaQCk3mN9Vx3qhy7IWmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;159&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Springboot 에서 세션 관리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 생성: 요청이 들어왔을 때 세션이 없다면 만들어서 응답에 set-cookie 로 넘겨줌&lt;/li&gt;
&lt;li&gt;세션 이용: 요청이 들어왔을 때 세션이 있다면 해당 세션의 데이터를 가져옴&lt;/li&gt;
&lt;li&gt;세션 삭제: 타임아웃이나 명시적인 로그아웃 API 를 통해 세션을 무효화 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR4l5K/btsiOivDdN7/lM7z8AellRjVN3HkFiy3o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR4l5K/btsiOivDdN7/lM7z8AellRjVN3HkFiy3o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR4l5K/btsiOivDdN7/lM7z8AellRjVN3HkFiy3o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR4l5K%2FbtsiOivDdN7%2FlM7z8AellRjVN3HkFiy3o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;88&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Http Session&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션을 손쉽게 생성하고 관리할 수 있게 해주는 인터페이스&lt;/li&gt;
&lt;li&gt;UUID 로 세션 ID 를 생성&lt;/li&gt;
&lt;li&gt;JSESSIONID 라는 이름의 쿠키를 설정해서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// Springboot 에서 HttpSession 사용 예제
@GetMapping(&quot;/hello&quot;)
public String hello(HttpSession session){
    session.setAttribute(&quot;user&quot;, &quot;user1&quot;);
    session.getAttribute(&quot;user&quot;);
    session.removeAttribute(&quot;user&quot;);

    return &quot;hello&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnL18n/btsiNa5Tsj8/a5G3VDFNDtM8fuIGaY1ooK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnL18n/btsiNa5Tsj8/a5G3VDFNDtM8fuIGaY1ooK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnL18n/btsiNa5Tsj8/a5G3VDFNDtM8fuIGaY1ooK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnL18n%2FbtsiNa5Tsj8%2Fa5G3VDFNDtM8fuIGaY1ooK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;105&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 를 사용한 Session clustering&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baLsTf/btsiU3X89fe/23JfXotYFJmoS1y8x9hU41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baLsTf/btsiU3X89fe/23JfXotYFJmoS1y8x9hU41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baLsTf/btsiU3X89fe/23JfXotYFJmoS1y8x9hU41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaLsTf%2FbtsiU3X89fe%2F23JfXotYFJmoS1y8x9hU41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;174&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;서비스 속도를 높이는 캐시 레이어 만들기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Caching 이란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시는 성능 향상을 위해 값을 복사해놓고 사용하는 임시 기억 장치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시에 복사본을 저장해놓고 읽음으로써 속도가 느린 장치로의 접근 횟수를 줄임&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;캐시의 데이터는 원본이 아니며 언제든 사라질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccX67l/btsiPhXh9T4/GUZipUsUB7iEwDt7KSub0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccX67l/btsiPhXh9T4/GUZipUsUB7iEwDt7KSub0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccX67l/btsiPhXh9T4/GUZipUsUB7iEwDt7KSub0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccX67l%2FbtsiPhXh9T4%2FGUZipUsUB7iEwDt7KSub0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;169&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시는 원천 데이터에 접근하는 것보다 조금 더 비용이 적게들고, 빠르게 접근하기 위해 사용한다. 하지만 캐시에 저장하는 데이터는 복사본이어서 데이터 일관성 문제도 생길 수 있는데 이 부분을 신경써야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cache 적용&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiQbLt/btsiJ79grVa/NwXe0bPn3q0vaPk0UGi4Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiQbLt/btsiJ79grVa/NwXe0bPn3q0vaPk0UGi4Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiQbLt/btsiJ79grVa/NwXe0bPn3q0vaPk0UGi4Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiQbLt%2FbtsiJ79grVa%2FNwXe0bPn3q0vaPk0UGi4Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;319&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹에서는 정적인 파일들인 이미지나 자바스크립트 소스 파일들을 캐시로 자주 사용한다. 웹 브라우저에서 새로고침을 해도 변경사항이 바로 나타나지 않은 경우는 캐시때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버간 통신에서 캐시를 사용하는 경우는 변경사항이 적거나 있더라도 최신 데이터를 즉각적으로 보여줄 필요가 적으면서, 속도나 요청을 할 서버에 부하를 줄이기 위해 사용하면 효율적이다. 데이터 변경사항이 생기면 다시 데이터를 가져와 캐시에 저장해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 서버들은 무상태라고 해서 데이터라는 상태를 가지지 않고, 서버를 여러개로 스케일 아웃하여 트래픽에 대응을 한다. 하지만 데이터베이스는 데이터라는 상태를 가지고 일관성을 맞추어야하기에 유연하게 스케일아웃이 쉽지않다. 그렇게 되면 데이터베이스에 병목이 생기고, 이런 경우에 캐시로 병목을 줄일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;꼭 네트워크상에서 다른 호스트간에 캐시를 적용하는 것 뿐만아니라 하나의 호스트 내에서도 최근에 사용했던 데이터를 내부적으로 가지면서 속도 향상을 위해 사용하기도 한다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Caching 관련 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시 적중(Cache hit) 는 캐시에 접근해 데이터를 발견함&lt;/li&gt;
&lt;li&gt;캐시 미스(Cache miss) 는 캐시에 접근했으나 데이터를 발견하지 못함&lt;/li&gt;
&lt;li&gt;캐시 삭제 정책(Eviction policy) 는 캐시의 데이터 공간 확보를 위해 저장된 데이터를 삭제&lt;/li&gt;
&lt;li&gt;캐시 전략: 환경에 따라 적합한 캐시 운영방식을 선택할 수 있음(Cache-aside, Write-through 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시의 적중률이 높으면 캐시를 효율적으로 사용하고 있는 것이다. 캐시는 비싸기에 삭제 정책이 필요한데, 어떤 데이터를 삭제해서 공간을 만들어야할지 정해야한다. 또한, &lt;b&gt;읽기와 쓰기가 많이 일어나는 환경과 요구사항에 맞게 적합한 캐시 운영전략을 사용하는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cache 운영 전략&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cache-aside (Lazy loading)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 캐시를 먼저 체크하고, 없으면 원본에서 읽어온 후에 캐시에 저장함&lt;/li&gt;
&lt;li&gt;장점: 필요한 데이터만 캐시에 저장되고, 캐시 미스가 있어도 치명적이지 않음&lt;/li&gt;
&lt;li&gt;단점: 최초 접근은 느리고, 업데이트 주기가 일정하지 않기 때문에 캐시가 최신 데이터가 아닐 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2JlPY/btsiQJeOMQC/F0IYYN8S8Vs4Lt1Hc7lRJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2JlPY/btsiQJeOMQC/F0IYYN8S8Vs4Lt1Hc7lRJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2JlPY/btsiQJeOMQC/F0IYYN8S8Vs4Lt1Hc7lRJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2JlPY%2FbtsiQJeOMQC%2FF0IYYN8S8Vs4Lt1Hc7lRJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;190&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 일반적으로 많이 사용되는 전략이다. 읽기 시도가 된 데이터만 캐시에 저장되어서 자주 사용되는 데이터만 캐시에 올라갈 확률이 높아진다. 캐시는 부가적인 성능향상을 위한 기능이다. 캐시에 존재하는 시간을 짧게 주고 만료하는 등의 정책을 통해 최신성 유지가 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Write-through&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 쓸 때 항상 캐시를 업데이트하여 최신 상태를 유지함&lt;/li&gt;
&lt;li&gt;장점: 캐시가 항상 동기화되어 있어 데이터가 최신임&lt;/li&gt;
&lt;li&gt;단점: 자주 사용하지 않는 데이터도 캐시되고, 쓰기 지연시간이 증가함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pymk4/btsiJy0gSc0/kiiWd7E0kq6RjIoN4zMhAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pymk4/btsiJy0gSc0/kiiWd7E0kq6RjIoN4zMhAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pymk4/btsiJy0gSc0/kiiWd7E0kq6RjIoN4zMhAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpymk4%2FbtsiJy0gSc0%2FkiiWd7E0kq6RjIoN4zMhAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;615&quot; height=&quot;126&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시를 먼저 쓰고 이후에 DB에 쓰기에 데이터 일관성에 대한 고민없이 사용이 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Write-back&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 캐시에만 쓰고, 캐시의 데이터를 일정 주기로 DB에 업데이트 함&lt;/li&gt;
&lt;li&gt;장점: 쓰기가 많은 경우 DB 부하를 줄일 수 있음&lt;/li&gt;
&lt;li&gt;단점: 캐시가 DB에 쓰기 전에 장애가 생기면 데이터 유실 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVEfWy/btsiQjUPQNU/wzQLaXOphZi6nzsg45ykL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVEfWy/btsiQjUPQNU/wzQLaXOphZi6nzsg45ykL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVEfWy/btsiQjUPQNU/wzQLaXOphZi6nzsg45ykL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVEfWy%2FbtsiQjUPQNU%2FwzQLaXOphZi6nzsg45ykL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;89&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;89&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 캐시 운영전략은 로그 데이터에 활용해볼 수도 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cache 데이터 제거 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시에서 어떤 데이터를 언제 제거할 것인가?&lt;/li&gt;
&lt;li&gt;Expiration: 각 데이터에 TTL (Time to live) 을 설정하여 시간 기반으로 삭제&lt;/li&gt;
&lt;li&gt;Eviction algorithm: 공간을 확보해야할 경우 어떤 데이터를 삭제할지 결정하는 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LRU (Least Recently Used): 가장 오랫동안 사용되지 않은 데이터를 삭제&lt;/li&gt;
&lt;li&gt;LFU (Least Frequently Used): 최근에 사용되었더라도 가장 적게 사용된 데이터를 삭제&lt;/li&gt;
&lt;li&gt;FIFO (First In First Out): 먼저 들어온 데이터를 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring caching 기능 이용하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CacheManager 를 통해 일반적인 캐시 인터페이스 구현 (다양한 캐시 구현체가 존재함)&lt;/li&gt;
&lt;li&gt;메소드에 캐시를 손쉽게 적용 가능&lt;/li&gt;
&lt;li&gt;@Cacheable : 메소드에 캐시를 적용함 (Cache-aside 패턴 수행)&lt;/li&gt;
&lt;li&gt;@CachePut : 메소드의 리턴값을 캐시에 설정함&lt;/li&gt;
&lt;li&gt;@CacheEvict : 메소드의 키값을 기반으로 캐시를 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;    @Cacheable
    public int getUserAge(String userId){
      ...
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Redis 를 활용할 경우 쉽게 구현이 가능한 예시&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;게임 리더보드(Leaderboard) 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리더보드는 게임이나 경쟁에서 상위 참가자의 랭킹과 점수를 보여주는 기능이고, 레디스는 순위로 나타낼 수 있는 다양한 대상에 응용이 가능하다.(최다 구매 상품, 리뷰 순위, 최다 뷰, 최다 구매상품, 최다 댓글 등) 또한, &lt;b&gt;리더보드와 같은 기능은 많은 사용자들이 볼 수 있는 특성이 있고, 빈번히 요청되기에 빠른 업데이트와 빠른 조회가 필요하다.&lt;/b&gt;&lt;br /&gt;이를 RDB 를 통해 구현하려면 업데이트, 데이터 정렬, 카운트 등의 집계 연산을 수행해야 하므로 데이터가 많아질수록 속도가 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레디스를 사용하게 되면 얻는 장점은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순위 데이터에 적합한 Sorted set 의 자료구조를 사용하면 score 를 통해 자동으로 정렬됨&lt;/li&gt;
&lt;li&gt;용도에 특화된 오퍼레이션(set 삽입, 업데이트, 조회)이 존재하므로 사용이 간단함&lt;/li&gt;
&lt;li&gt;자료구조의 특성으로 데이터 조회가 빠름 (범위 검색, 특정 값의 순위 검색)&lt;/li&gt;
&lt;li&gt;빈번한 액세스에 유리한 인메모리 DB 의 속도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리더보드의 동작을 API 관점에서 보면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;점수 생성/업데이트 =&amp;gt; ex: SetScore(userId, score)&lt;/li&gt;
&lt;li&gt;상위 랭크 조회(범위 기반 조회) =&amp;gt; ex: getRange(1~10)&lt;/li&gt;
&lt;li&gt;특정 대상 순위 조회(값 기반 조회) =&amp;gt; ex: getRank(userId)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis pub/sub 을 이용한 채팅방 구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pub/sub 패턴의 이해&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시징 모델 중의 하나로 발행(Publish)과 구독(Subscribe) 역할로 개념화 한 형태&lt;/li&gt;
&lt;li&gt;발행자와 구독자는 서로에 대한 정보 없이 특정 주제(토픽 or 채널)를 매개로 송수신함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yZSnf/btsiQHBjtwZ/6NZv6RiqKXoaAtXKUqfZok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yZSnf/btsiQHBjtwZ/6NZv6RiqKXoaAtXKUqfZok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yZSnf/btsiQHBjtwZ/6NZv6RiqKXoaAtXKUqfZok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyZSnf%2FbtsiQHBjtwZ%2F6NZv6RiqKXoaAtXKUqfZok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;200&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메시징 미들웨어 사용의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기: 통신의 비동기 처리&lt;/li&gt;
&lt;li&gt;낮은 결합도: 송신자와 수신자가 직접 서로 의존하지 않고 공통 미들웨어에 의존함&lt;/li&gt;
&lt;li&gt;탄력성: 구성원들간에 느슨한 연결로 인해 일부 장애가 생겨도 영향이 최소화됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기 방식은 메시지를 받을 서버가 항상 실행중이어야 하고, 메시지가 몰렸을 때 부하분산을 적절히 할 수 없는 단점이 있다. 하지만 메시지 미들웨어를 사용하더라도 자체에 장애가 생긴다면 단일 실패점이 될 수도 있다. 이런 부분들은 분산화 통해 해결할 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis 의 pub/sub 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지가 큐에 저장되지 않음&lt;/li&gt;
&lt;li&gt;카프카의 컨슈머 그룹같은 분산처리 개념이 없음&lt;/li&gt;
&lt;li&gt;메시지 발행 시 push 방식으로 subscriber 들에게 전송&lt;/li&gt;
&lt;li&gt;subscriber 가 늘어날수록 성능이 저하됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레디스의 펍섭 특징은 현재 온라인으로 실행중인 구독자들에게만 메시지가 전송된다. 발행자가 메시지 발행 시 레디스는 메시지를 받고 구독자에게 바로 전송한다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis 의 pub/sub 의 활용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실시간으로 빠르게 전송되어야 하는 메시지&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;메시지 유실을 감내할 수 있는 케이스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최대 1회 전송(at-most-once) 패턴이 적합한 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구독자들이 다양한 채널을 유동적으로 바꾸면서 한시적으로 구독하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;큐에 메시지를 저장하지 않기에 유실을 감내할 수 있어야한다. 즉 메시지의 라이프사이클이 짧아야 하고, 중복 메시지는 전송하지 않고, 유실되거나 최대 1회 전송이 되는 패턴을 가진다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis 의 pub/sub 을 이용한 채팅방 구현&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVGjhj/btsiRFiWvNi/M6henyY377SS8kTMq6l530/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVGjhj/btsiRFiWvNi/M6henyY377SS8kTMq6l530/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVGjhj/btsiRFiWvNi/M6henyY377SS8kTMq6l530/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVGjhj%2FbtsiRFiWvNi%2FM6henyY377SS8kTMq6l530%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;264&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Redis streams 을 이용한 Event-driven 아키텍처&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산 시스템에서의 통신 방식을 정의한 아키텍처로 이벤트의 생성/소비 구조로 통신이 이루어짐&lt;/li&gt;
&lt;li&gt;각 서비스들은 이벤트 저장소인 Event-broker 와의 의존성만 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 브로커로 레디스 스트림즈를 사용할 수도 있고 카프카를 사용할 수도 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Event-driven 아키텍처의 모습&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/np3C1/btsiFMYDR3J/YeCVPPmrVHWtximbPeo2hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/np3C1/btsiFMYDR3J/YeCVPPmrVHWtximbPeo2hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/np3C1/btsiFMYDR3J/YeCVPPmrVHWtximbPeo2hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnp3C1%2FbtsiFMYDR3J%2FYeCVPPmrVHWtximbPeo2hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;263&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서버들은 이벤트 브로커에 이벤트를 생산/소비함으로써 통신한다. 핵심은 서버들이 직접 호출하지 않는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Event-driven 아키텍처의 장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 생산자와 소비자 간의 결합도가 낮아짐&lt;br /&gt;공통적인 Event-broker 에 대한 결합만 있음&lt;/li&gt;
&lt;li&gt;생산자와 소비자의 유연한 변경&lt;br /&gt;서버 추가, 삭제 시에 다른 서버를 변경할 필요가 적어짐&lt;/li&gt;
&lt;li&gt;장애 탄력성&lt;br /&gt;이벤트를 소비할 일부 서비스에 장애가 발생해도 이벤트는 저장되고 이후에 처리됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 이벤트 기반 아키텍처를 사용하면 위 아키텍츠 그림에서 알림서버를 삭제한다고 해도 결제서버에서 알림서버를 호출하는 부분이 없기에 결제서버를 변경할 필요가 없다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Event-driven 아키텍처의 단점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템의 예측가능성이 떨어짐&lt;br /&gt;느슨하게 연결된 상호작용에서 기인함&lt;/li&gt;
&lt;li&gt;테스트의 어려움&lt;/li&gt;
&lt;li&gt;장애 추적의 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 어떻게 동작하는지 파악하기가 어렵다. 예를들어 기존 방식의 아키텍처에서는 주문서버가 어떤 서버를 호출하는지 등의 부분을 알아보기가 쉬운데, 이 아키텍처에서 주문서버는 주문 이벤트 발행하는 로직만 확인할 수 있고 이 이벤트를 어떤 서버들이 소비를 하는지 알기가 어렵다.&lt;br /&gt;예측가능성이 떨어지기에 테스트도 어렵다. 시스템이 유연하기에 정형화된 테스트를 하기가 어렵고, 유연성과의 트레이드오프를 가진다.&lt;br /&gt;비슷한 이유로 장애 추적도 어렵다. 정형화된 메시지 흐름이 바로 보이지 않기에 문제가 생겼을때 하나 하나 추적을 해가야하는데 어려움이 있을 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis streams 의 이해&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;append-only log 를 구현한 자료구조&lt;/li&gt;
&lt;li&gt;하나의 key 로 식별되는 하나의 stream 에 엔트리가 계속 추가되는 구조&lt;/li&gt;
&lt;li&gt;하나의 엔트리는 entry ID + (key-value 리스트) 로 구성&lt;/li&gt;
&lt;li&gt;추가된 데이터는 사용자가 삭제하지 않는 한 지워지지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSyYfc/btsiJzkwysJ/vKqKmVnmWkcI5pKn5cOMW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSyYfc/btsiJzkwysJ/vKqKmVnmWkcI5pKn5cOMW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSyYfc/btsiJzkwysJ/vKqKmVnmWkcI5pKn5cOMW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSyYfc%2FbtsiJzkwysJ%2FvKqKmVnmWkcI5pKn5cOMW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;146&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;append-only log 는 AOF 파일을 표현할 때 사용되는 구조이기도 하는데, 이는 일반적인 소프트웨어 공학의 용어로 사용된다. 추가된 데이터가 지워지지 않으면서 로그 파일처럼 끝에 계속 추가만이 가능한 데이터 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 스트림안에 엔트리 각각은 로그 한 줄이라고 생각하면 된다. 엔트리 안에는 여러 개의 field 와 value 쌍들이 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis streams 의 활용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;센서 모니터링 (지속적으로 변하는 데이터인 시간 별 날씨 수집 등)&lt;/li&gt;
&lt;li&gt;사용자별 알림 데이터 저장&lt;/li&gt;
&lt;li&gt;이벤트 저장소&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis streams 명령어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XADD: 특정 key 의 stream 에 entry 를 추가한다. (해당 key 에 stream 이 없으면 생성함)&lt;br /&gt;&lt;code&gt;XADD [key] [id] [field-value]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;XRANGE: 특정 ID 범위의 entry 를 반환한다.&lt;br /&gt;&lt;code&gt;XRANGE [key] [start] [end]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;XREAD: 한 개 이상의 key 에 대해 특정 ID 이후의 entry 를 반환한다. (offset 기반, 동기 수행 가능)&lt;br /&gt;&lt;code&gt;XREAD BLOCK [milliseconds] STREAMS [key] [id]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;XGROUP CREATE: consumer group 을 생성한다.&lt;br /&gt;&lt;code&gt;XGROUP CREATE [key] [group name] [id]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;XREADGROUP: 특정 key 의 stream 을 조회하되 특정 consumer group 에 속한 consumer 로 읽는다.&lt;br /&gt;&lt;code&gt;XREADGROUP GROUP [group name] [consumer name] COUNT [count] STREAMS [key] [id]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Consumer group&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 stream 을 여러 consumer 가 분산 처리할 수 있는 방식&lt;/li&gt;
&lt;li&gt;하나의 그룹에 속한 consumer 는 서로 다른 entry 들을 조회하게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xCrlg/btsiNaSkNNq/Pdz60BQ91RaCcCLLJMFfZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xCrlg/btsiNaSkNNq/Pdz60BQ91RaCcCLLJMFfZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xCrlg/btsiNaSkNNq/Pdz60BQ91RaCcCLLJMFfZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxCrlg%2FbtsiNaSkNNq%2FPdz60BQ91RaCcCLLJMFfZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;218&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis streams 를 이용한 event-driven 통신 개발&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 를 이용한 동기 통신 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스는 필요한 서비스를 직접 호출한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uiQ8w/btsiPPzCOSm/hzX4ZXx6MW4EqH3hrXKUfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uiQ8w/btsiPPzCOSm/hzX4ZXx6MW4EqH3hrXKUfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uiQ8w/btsiPPzCOSm/hzX4ZXx6MW4EqH3hrXKUfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuiQ8w%2FbtsiPPzCOSm%2FhzX4ZXx6MW4EqH3hrXKUfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;174&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Event-broker 를 이용한 메시지 기반 통신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스는 미리 정의된 이벤트를 소비/생성함으로써 통신한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOPToL/btsiOfZ7XbE/PKAahh8uKVTgAtl5TLtkek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOPToL/btsiOfZ7XbE/PKAahh8uKVTgAtl5TLtkek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOPToL/btsiOfZ7XbE/PKAahh8uKVTgAtl5TLtkek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOPToL%2FbtsiOfZ7XbE%2FPKAahh8uKVTgAtl5TLtkek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;288&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MeMiX/btsiOhjcRyX/0W22W8rWyAoJkJ3jyVuBPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MeMiX/btsiOhjcRyX/0W22W8rWyAoJkJ3jyVuBPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MeMiX/btsiOhjcRyX/0W22W8rWyAoJkJ3jyVuBPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMeMiX%2FbtsiOhjcRyX%2F0W22W8rWyAoJkJ3jyVuBPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;256&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문, 결제, 알림 3 개의 독립된 서버가 있을 경우 레디스 스트림을 사용하게 되면, 각 서버는 자체로서 자기 역할만 수행하면 된다. 주문이 발생하면 event broker 에 order-events 로 메시지를 넣고, 결제서버는 해당 메시지를 비동기로 결제를 끝내고 payment-events 에 메시지를 보내고, 알림서버는 order-events 와 payment-events 에서 비동기로 메시지를 받아 주문과 결제에 맞는 알림을 전송하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 결제서버가 죽는 문제가 생겼을 경우 결제와 결제에 대한 알림은 보류된다. 이후 결제서버가 다시 복구되어서 정상적으로 실행이 된다면, 레디스 스트림에서 주문에 대한 스트림을 받아 결제를 처리하게 되고, 결제 완료에 대한 이벤트를 레디스 스트림으로 보내고 알림서버는 그걸 받아서 결제 알림을 처리할 수 있어 장애회복에 대한 부분도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서 &lt;b&gt;서버를 추가하거나 제거하는 것 또한 유연하게 대처가 가능하고, 회복 탄력성이 좋고 일종의 로드 밸런싱 역할도 수행할 수 있다. 주문에 대한 결제서버를 직접 분산처리를 하지 않아도 레디스 스트림즈의 컨슈머 그룹을 활용하면 결제서버의 인스턴스를 여러 개 늘리면서 분산 효과를 주고 로드 밸런스 효과도 준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레디스를 이용하면 메시지 브로커를 구현할 수 있고, 카프카와 비교도 된다. 하지만 레디스의 스트림 기능은 아직 초창기이고 활용도는 많이 낮은편이다. 다만 카프카에 비해 환경설정이나 셋팅이 쉽기 때문에 간단하게 스트림즈 기능을 사용할 유스케이스가 있으면 사용하기에 좋을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;WidgetFloaterPanels&quot; class=&quot;LTRStyle&quot; style=&quot;display: none; text-align: left; direction: ltr; visibility: hidden;&quot; translate=&quot;no&quot;&gt;
&lt;div id=&quot;WidgetFloater&quot; style=&quot;display: none;&quot;&gt;
&lt;div id=&quot;WidgetLogoPanel&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;TRANSLATE with &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;img id=&quot;FloaterLogo&quot; /&gt; &lt;span&gt;x&lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;LanguageMenuPanel&quot;&gt;
&lt;div class=&quot;DDStyle_outer&quot;&gt;&lt;input id=&quot;LanguageMenu_svid&quot; style=&quot;display: none;&quot; autocomplete=&quot;on&quot; name=&quot;LanguageMenu_svid&quot; type=&quot;text&quot; value=&quot;en&quot; /&gt; &lt;input id=&quot;LanguageMenu_textid&quot; style=&quot;display: none;&quot; autocomplete=&quot;on&quot; name=&quot;LanguageMenu_textid&quot; type=&quot;text&quot; /&gt; &lt;span class=&quot;DDStyle&quot;&gt;English&lt;/span&gt;
&lt;div style=&quot;position: relative; text-align: left; left: 0;&quot;&gt;
&lt;div style=&quot;position: absolute; ;left: 0px;&quot;&gt;
&lt;div id=&quot;__LanguageMenu_popup&quot; class=&quot;DDStyle&quot; style=&quot;display: none;&quot;&gt;
&lt;table id=&quot;LanguageMenu&quot; border=&quot;0&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ar&quot;&gt;Arabic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#he&quot;&gt;Hebrew&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#pl&quot;&gt;Polish&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#bg&quot;&gt;Bulgarian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#hi&quot;&gt;Hindi&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#pt&quot;&gt;Portuguese&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ca&quot;&gt;Catalan&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#mww&quot;&gt;Hmong Daw&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ro&quot;&gt;Romanian&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#zh-CHS&quot;&gt;Chinese Simplified&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#hu&quot;&gt;Hungarian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ru&quot;&gt;Russian&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#zh-CHT&quot;&gt;Chinese Traditional&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#id&quot;&gt;Indonesian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#sk&quot;&gt;Slovak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#cs&quot;&gt;Czech&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#it&quot;&gt;Italian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#sl&quot;&gt;Slovenian&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#da&quot;&gt;Danish&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ja&quot;&gt;Japanese&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#es&quot;&gt;Spanish&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#nl&quot;&gt;Dutch&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#tlh&quot;&gt;Klingon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#sv&quot;&gt;Swedish&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#en&quot;&gt;English&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ko&quot;&gt;Korean&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#th&quot;&gt;Thai&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#et&quot;&gt;Estonian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#lv&quot;&gt;Latvian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#tr&quot;&gt;Turkish&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#fi&quot;&gt;Finnish&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#lt&quot;&gt;Lithuanian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#uk&quot;&gt;Ukrainian&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#fr&quot;&gt;French&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ms&quot;&gt;Malay&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ur&quot;&gt;Urdu&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#de&quot;&gt;German&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#mt&quot;&gt;Maltese&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#vi&quot;&gt;Vietnamese&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#el&quot;&gt;Greek&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#no&quot;&gt;Norwegian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#cy&quot;&gt;Welsh&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ht&quot;&gt;Haitian Creole&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#fa&quot;&gt;Persian&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;img style=&quot;height: 7px; width: 17px; border-width: 0px; left: 20px;&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script type=&quot;text/javascript&quot;&gt; var LanguageMenu; var LanguageMenu_keys=[&quot;ar&quot;,&quot;bg&quot;,&quot;ca&quot;,&quot;zh-CHS&quot;,&quot;zh-CHT&quot;,&quot;cs&quot;,&quot;da&quot;,&quot;nl&quot;,&quot;en&quot;,&quot;et&quot;,&quot;fi&quot;,&quot;fr&quot;,&quot;de&quot;,&quot;el&quot;,&quot;ht&quot;,&quot;he&quot;,&quot;hi&quot;,&quot;mww&quot;,&quot;hu&quot;,&quot;id&quot;,&quot;it&quot;,&quot;ja&quot;,&quot;tlh&quot;,&quot;ko&quot;,&quot;lv&quot;,&quot;lt&quot;,&quot;ms&quot;,&quot;mt&quot;,&quot;no&quot;,&quot;fa&quot;,&quot;pl&quot;,&quot;pt&quot;,&quot;ro&quot;,&quot;ru&quot;,&quot;sk&quot;,&quot;sl&quot;,&quot;es&quot;,&quot;sv&quot;,&quot;th&quot;,&quot;tr&quot;,&quot;uk&quot;,&quot;ur&quot;,&quot;vi&quot;,&quot;cy&quot;]; var LanguageMenu_values=[&quot;Arabic&quot;,&quot;Bulgarian&quot;,&quot;Catalan&quot;,&quot;Chinese Simplified&quot;,&quot;Chinese Traditional&quot;,&quot;Czech&quot;,&quot;Danish&quot;,&quot;Dutch&quot;,&quot;English&quot;,&quot;Estonian&quot;,&quot;Finnish&quot;,&quot;French&quot;,&quot;German&quot;,&quot;Greek&quot;,&quot;Haitian Creole&quot;,&quot;Hebrew&quot;,&quot;Hindi&quot;,&quot;Hmong Daw&quot;,&quot;Hungarian&quot;,&quot;Indonesian&quot;,&quot;Italian&quot;,&quot;Japanese&quot;,&quot;Klingon&quot;,&quot;Korean&quot;,&quot;Latvian&quot;,&quot;Lithuanian&quot;,&quot;Malay&quot;,&quot;Maltese&quot;,&quot;Norwegian&quot;,&quot;Persian&quot;,&quot;Polish&quot;,&quot;Portuguese&quot;,&quot;Romanian&quot;,&quot;Russian&quot;,&quot;Slovak&quot;,&quot;Slovenian&quot;,&quot;Spanish&quot;,&quot;Swedish&quot;,&quot;Thai&quot;,&quot;Turkish&quot;,&quot;Ukrainian&quot;,&quot;Urdu&quot;,&quot;Vietnamese&quot;,&quot;Welsh&quot;]; var LanguageMenu_callback=function(){ }; var LanguageMenu_popupid='__LanguageMenu_popup'; &lt;/script&gt;
&lt;/div&gt;
&lt;div id=&quot;CTFLinksPanel&quot;&gt;&lt;span&gt;&lt;a id=&quot;HelpLink&quot; title=&quot;Help&quot; href=&quot;https://go.microsoft.com/?linkid=9722454&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; &lt;img id=&quot;HelpImg&quot; /&gt;&lt;/a&gt; &lt;a id=&quot;EmbedLink&quot; title=&quot;Get this widget for your own site&quot;&gt;&lt;/a&gt; &lt;img id=&quot;EmbedImg&quot; /&gt; &lt;a id=&quot;ShareLink&quot; title=&quot;Share translated page with friends&quot;&gt;&lt;/a&gt; &lt;img id=&quot;ShareImg&quot; /&gt; &lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;FloaterProgressBar&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;WidgetFloaterCollapsed&quot; style=&quot;display: none;&quot;&gt;&lt;span&gt;TRANSLATE with &lt;/span&gt;&lt;img id=&quot;CollapsedLogoImg&quot; /&gt;&lt;/div&gt;
&lt;div id=&quot;FloaterSharePanel&quot; style=&quot;display: none;&quot;&gt;
&lt;div id=&quot;ShareTextDiv&quot;&gt;&lt;span&gt; COPY THE URL BELOW &lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;ShareTextboxDiv&quot;&gt;&lt;input id=&quot;ShareTextbox&quot; name=&quot;ShareTextbox&quot; readonly=&quot;readonly&quot; type=&quot;text&quot; /&gt; &lt;!--a id=&quot;TwitterLink&quot; title=&quot;Share on Twitter&quot;&gt; &lt;img id=&quot;TwitterImg&quot; /&gt;&lt;/a&gt; &lt;a-- id=&quot;FacebookLink&quot; title=&quot;Share on Facebook&quot;&gt; &lt;img id=&quot;FacebookImg&quot; /&gt;&lt;/a--&gt; &lt;a id=&quot;EmailLink&quot; title=&quot;Email this translation&quot;&gt;&lt;/a&gt; &lt;img id=&quot;EmailImg&quot; /&gt;&lt;/div&gt;
&lt;div id=&quot;ShareFooter&quot;&gt;&lt;span&gt;&lt;a id=&quot;ShareHelpLink&quot;&gt;&lt;/a&gt; &lt;img id=&quot;ShareHelpImg&quot; /&gt;&lt;/span&gt; &lt;span&gt;&lt;a id=&quot;ShareBack&quot; title=&quot;Back To Translation&quot;&gt;&lt;/a&gt; Back&lt;/span&gt;&lt;/div&gt;
&lt;input id=&quot;EmailSubject&quot; name=&quot;EmailSubject&quot; type=&quot;hidden&quot; value=&quot;Check out this page in {0} translated from {1}&quot; /&gt; &lt;input id=&quot;EmailBody&quot; name=&quot;EmailBody&quot; type=&quot;hidden&quot; value=&quot;Translated: {0}%0d%0aOriginal: {1}%0d%0a%0d%0aAutomatic translation powered by Microsoft&amp;reg; Translator%0d%0ahttp://www.bing.com/translator?ref=MSTWidget&quot; /&gt; &lt;input id=&quot;ShareHelpText&quot; type=&quot;hidden&quot; value=&quot;This link allows visitors to launch this page and automatically translate it to {0}.&quot; /&gt;&lt;/div&gt;
&lt;div id=&quot;FloaterEmbed&quot; style=&quot;display: none;&quot;&gt;
&lt;div id=&quot;EmbedTextDiv&quot;&gt;&lt;span&gt;EMBED THE SNIPPET BELOW IN YOUR SITE&lt;/span&gt; &lt;a id=&quot;EmbedHelpLink&quot; title=&quot;Copy this code and place it into your HTML.&quot;&gt;&lt;/a&gt; &lt;img id=&quot;EmbedHelpImg&quot; /&gt;&lt;/div&gt;
&lt;div id=&quot;EmbedTextboxDiv&quot;&gt;&lt;input id=&quot;EmbedSnippetTextBox&quot; name=&quot;EmbedSnippetTextBox&quot; readonly=&quot;readonly&quot; type=&quot;text&quot; value=&quot;&amp;lt;div id='MicrosoftTranslatorWidget' class='Dark' style='color:white;background-color:#555555'&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;script type='text/javascript'&amp;gt;setTimeout(function(){var s=document.createElement('script');s.type='text/javascript';s.charset='UTF-8';s.src=((location &amp;amp;&amp;amp; location.href &amp;amp;&amp;amp; location.href.indexOf('https') == 0)?'https://ssl.microsofttranslator.com':'http://www.microsofttranslator.com')+'/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&amp;amp;ctf=true&amp;amp;ui=true&amp;amp;settings=manual&amp;amp;from=en';var p=document.getElementsByTagName('head')[0]||document.documentElement;p.insertBefore(s,p.firstChild); },0);&amp;lt;/script&amp;gt;&quot; /&gt;&lt;/div&gt;
&lt;div id=&quot;EmbedNoticeDiv&quot;&gt;&lt;span&gt;Enable collaborative features and customize widget: &lt;a href=&quot;http://www.bing.com/widget/translator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bing Webmaster Portal&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;EmbedFooterDiv&quot;&gt;&lt;span&gt;&lt;a title=&quot;Back To Translation&quot;&gt;Back&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script type=&quot;text/javascript&quot;&gt; var intervalId = setInterval(function () { if (MtPopUpList) { LanguageMenu = new MtPopUpList(); var langMenu = document.getElementById(LanguageMenu_popupid); var origLangDiv = document.createElement(&quot;div&quot;); origLangDiv.id = &quot;OriginalLanguageDiv&quot;; origLangDiv.innerHTML = &quot;&lt;span id='OriginalTextSpan'&gt;ORIGINAL: &lt;/span&gt;&lt;span id='OriginalLanguageSpan'&gt;&lt;/span&gt;&quot;; langMenu.appendChild(origLangDiv); LanguageMenu.Init('LanguageMenu', LanguageMenu_keys, LanguageMenu_values, LanguageMenu_callback, LanguageMenu_popupid); window[&quot;LanguageMenu&quot;] = LanguageMenu; clearInterval(intervalId); } }, 1); &lt;/script&gt;
&lt;/div&gt;</description>
      <category>IT/대용량 데이터&amp;amp;트래픽 처리</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/81</guid>
      <comments>https://calgary.tistory.com/81#entry81comment</comments>
      <pubDate>Tue, 6 Jun 2023 15:15:02 +0900</pubDate>
    </item>
    <item>
      <title>외부 서비스 연동 시 비동기 처리에 대해서</title>
      <link>https://calgary.tistory.com/77</link>
      <description>&lt;h1&gt;외부 서비스 연동 시 발생할 수 있는 상황&lt;/h1&gt;
&lt;p&gt;외부 서비스와 연동 시 외부 서비스에 대한 요청이나 응답에 지연이 생길 경우 운영하는 서비스의 전체적인 성능에 영향이 생기게 된다. 이 때 클라이언트의 요청 많아질 경우 서버에서는 해당 요청들에 대해 대기하는 상황이 발생하게 된다. 그렇게 되면 클라이언트는 응답이 빠르게 오지 않아 재요청을 발생시킬 수 있고 그렇다면 더 많은 대기가 발생할 수 있다.&lt;/p&gt;
&lt;h1&gt;외부 서비스 연동 결과를 즉각적으로 처리해야 하는가?&lt;/h1&gt;
&lt;p&gt;예를 들어 로그인을 하면 포인트를 적립하는 요구사항이 있다면, 로그인 로직에서 외부 포인트를 적립하는 서비스를 호출한다. 이후 로그인에 성공한 뒤 수 초 이내 포인트를 적립하는 서비스를 호출한다.&lt;/p&gt;
&lt;p&gt;다른 예로 주문을 취소하면 푸시알림을 발송하는 요구사항이 있다. 주문 취소 로직에서 외부 푸시 발송 API 를 호출하고, 주문 취소 후에 수 초 이내에 외부 푸시 발송 API 를 호출한다.&lt;/p&gt;
&lt;p&gt;--&amp;gt; 이러한 예시들은 사실상 즉각적으로 처리하지 않아도 큰 문제가 되지 않는다.&lt;/p&gt;
&lt;h1&gt;외부 서비스 연동에 비동기를 고려해보자&lt;/h1&gt;
&lt;p&gt;그렇다면 별도 쓰레드나 별도 프로세스로 외부 서비스를 연동하여 비동기로 처리하는 것도 고려해보자. 외부 서비스 연동을 비동기로 하게 되면 외부 서비스의 성능 저하가 운영하는 서비스에 영향을 주는 것을 줄일 수 있다. 그렇게 되면 사용자에게 일정 수준의 품질을 제공할 수 있게 된다.&lt;/p&gt;
&lt;h1&gt;비동기 처리 방식 3가지&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;별도 쓰레드를 사용하는 방법&lt;br&gt;외부 연동 코드를 별도 쓰레드로 실행하는 것이다. 구현이 쉽지만 트랜잭션 처리에 대한 방법을 강구해야 한다. 보통은 트랜잭션이 커밋된 후에 비동기 처리를 하게 된다.&lt;br&gt;서버를 재시작하게 되면 비동기로 처리하던 작업이 유실될 수 있다. 즉, 외부 서비스 연동에 실패했을 때 재처리를 어떻게 할 것인지&lt;br&gt;외부 서비스 연동이 많으면 처리하는 시간을 줄이기 위해 쓰레드 풀 크기를 늘리고, 쓰레드 풀에서 사용하는 큐의 크기를 늘려야 한다.&lt;br&gt;그래서 별도 쓰레드를 사용하는 방법은 재처리의 필요성이 낮은 외부 서비스 연동에 적합할 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;연동 데이터를 DB에 저장하고 별도 쓰레드나 프로세스로 처리하는 방법&lt;br&gt;이 방법은 별도 쓰레드나 프로세스로 DB에 저장된 연동 데이터를 조회한 후 외부 연동 서비스를 처리하는 방법이다. 그래서 DB 트랜잭션 처리가 쉬우며, 외부 서비스 연동에 실패했을 경우 재처리하기가 쉽다. 그래서 이 방법은 외부 서비스 연동에 누락이 없어야 할 경우 적합하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;연동 데이터를 메시징 시스템에 저장하고 별도 쓰레드나 프로세스로 처리하는 방법&lt;br&gt;연동 데이터를 메시징 시스템에 저장하고 연동 모듈이 이 데이터를 수신해서 처리하는 방법이다. 메시징 시스템은 대량의 데이터를 처리하는데 이점이 있고, 외부 서비스 연동 실패시 비교적 재처리에 용이하다. 하지만 데이터 유실 가능성을 고려해야 한다. 왜냐하면 DB에는 외부 서비스 연동에 대한 상태 등의 데이터를 저장했지만, 이후 메시징 시스템에 문제가 있거나 연결 등의 상황에서 문제가 발생할 경우가 생길 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;모든 외부 서비스 연동을 비동기로 할 필요는 없다. 하지만 외부 서비스 연동이 운영하는 서비스의 성능에 영향을 준다면 고려해볼 사항이다. 다만 비동기를 사용하게 되면 방법에 따라 복잡도가 증가하게 된다.&lt;br&gt;외부 서비스와의 연동에서 비동기 처리가 필요할 때 재처리나 트랜잭션 등의 제약이 약하다면 별도 쓰레드로 비동기 처리를 고려하는 것이 좋을 것이고, 연동이나 성능에 대한 요구사항이 높다면 DB 와 메시징 시스템 조합을 고려해보는 것도 방법일 것이다.&lt;/p&gt;</description>
      <category>IT/대용량 데이터&amp;amp;트래픽 처리</category>
      <category>대외기관 연동</category>
      <category>비동기 처리</category>
      <category>연동</category>
      <category>외부 서비스</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/77</guid>
      <comments>https://calgary.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 27 Jan 2023 22:35:41 +0900</pubDate>
    </item>
    <item>
      <title>마이크로서비스 5개 배포 원칙</title>
      <link>https://calgary.tistory.com/76</link>
      <description>&lt;h1&gt;마이크로서비스 5개 배포 원칙&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 격리 Isolated execution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부하가 발생하거나 배포를 할 때 마이크로서비스 간에 영향을 주면 안된다.&lt;/b&gt;&lt;br /&gt;예를 들어 하나의 호스트에서 여러 마이크로서비스를 실행하게 되면 어떠한 서비스의 부하가 증가하게 되는 순간 다른 마이크로서비스들이 다 같이 성능이 저하되거나 혹은 마이크로서비스 배포를 위해서 리눅스 os 설정 등을 변경하게 되면 다른 마이크로서비스 배포에 영향을 준다거나 이런 상태를 해결하기 위해 &lt;b&gt;마이크로서비스는 서로 격리된 환경에서 실행되어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상화 기술, 컨테이너, FaaS 등을 사용하면 격리가 쉬워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적 장비를 사용하면 강하게 격리를 할 수 있고, 컨테이너를 사용하면 상대적으로 빠르게 격리를 할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자동화에 초점 Focus on automation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스가 많아질수록 복잡해진다. 더 많은 절차, 더 많은 설정, 더 많은 모니터링 대상이 생긴다. 그렇게 되면 운영하는데 부하도 증가하게 되고, 개발하는 시간만큼 운영하는데 들어가는 시간도 늘어난다. 그래서 &lt;b&gt;운영 부하를 감소하기 위해서 자동화하는데 초점을 맞춰야한다.&lt;/b&gt; 자동화하지를 않으면 성장에 빠르게 대응할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 중요한 것은 개발자가 직접 인프라 서비스를 제공할 수 있어야 하는 조직의 문화나 규칙이 필요하다. 개발자가 원하는 시간이나 상황에 컨테이너를 만들고 배포할 수 있는 상황이 되지 않으면 생산성이 떨어지게 될 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드형 인프라 Infrastructure as code&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라스트럭처를 코드로 정의하는 것이다. 코드형 인프라는 자동화를 구현하는 한 가지 방법이다. 보통 텍스트형식으로 인프라스트럭처를 설정파일 형태로 정리하니 버전관리도 가능해지고, 잘못 설정했을 때 복구하는 것도 상대적으로 쉬워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 환경이 점점 보편화 되어가고 있어서 Terraform, Pulumi 등의 특화된 도구를 사용하게 되고, 클라우드나 컨테이너가 확대되다 보니 Puppet 이나 Ansible 같은 도구의 사용은 감소세이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pulumi 는 코드로 인프라스트럭처를 정의할 수 있는 도구이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무중단 배포 Zero-downtime deployment&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스에서 개발 및 배포할 때 무중단 배포는 필수이다. &lt;b&gt;배포나 출시를 위해서 마이크로서비스를 사용하는 팀 혹은 사용자에게 통지하지 않고 출시하는 것이 중요하다.&lt;/b&gt; 출시를 위해 사용자와 통지하고 일정을 협의하고 이런 과정은 최대한 제거하는 것이다. 또한 독립적 배포를 위해서라도 무중단 배포는 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기대 상태 관리 Desired state management&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스에 대해 기대하는 상태를 관리할 수 있는 수단이 필요하다. &lt;b&gt;개발자의 개입없이 인프라스트럭처를 원하는 상태로 유지할 수 있는 상태를 가져야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 최소 세 개의 인스턴스가 실행 중이어야 한다. 그러면 마이크로서비스 인스턴스 세 개 중에 하나가 죽었을 때 자동으로 3개를 맞춰주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 다른 예로 CPU 부하가 50% 이상 증가하면 인스턴스를 자동으로 한 개 더 늘린다. 부하가 증가할 때 자동으로 스케일아웃이 된다던가 이러한 것들이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 개발자의 개입없이 기대하는 상태를 유지할 수 있는 방법을 강구해야한다. 이를 지원하는 도구는 많이 있고, 직접 코드로 구현하는 것도 방법이다.&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Building Microservices&lt;/p&gt;</description>
      <category>IT/아키텍처</category>
      <category>MSA</category>
      <category>마이크로서비스</category>
      <category>아키텍처</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/76</guid>
      <comments>https://calgary.tistory.com/76#entry76comment</comments>
      <pubDate>Thu, 26 Jan 2023 16:37:59 +0900</pubDate>
    </item>
    <item>
      <title>마이크로서비스 핵심 개념</title>
      <link>https://calgary.tistory.com/75</link>
      <description>&lt;h1&gt;마이크로서비스 6가지 핵심 개념&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;독립적 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 개념 중에 한 가지만 골라야 한다면 '독립적 배포' 가 가장 중요하다.&lt;br /&gt;&lt;b&gt;다른 마이크로서비스를 변경/배포하지 않으면서, 마이크로서비스를 변경, 배포, 출시할 수 있어야한다. 이를 위해 마이크로서비스 간에 결합도를 낮춰야 한다. 즉 서비스 간에 명시적이고, 잘 정의되어 있고 안정적인 API 명세가 필요하다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비즈니스 도메인을 중심으로 모델링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인을 기준으로 서비스 경계를 정의한다. 즉, 하나의 마이크로서비스가 특정 기능에 필요한 전체를 구현한다.&lt;/b&gt; 이렇게 되면 새 기능을 출시하는게 쉬워진다. 여러 마이크로서비스의 기능을 조합해서 새로운 기능을 구현하는게 쉬워진다.&lt;br /&gt;&lt;b&gt;한 기능이 여러 마이크로서비스에 걸쳐 있으면 기능을 출시하는 비용이 올라간다.&lt;/b&gt; 서비스 간에 조율, 순서 관리 등 더 많은 일을 해야한다. 여러 서비스에 걸친 변경은 가능한 덜 할 수 있는 방법을 찾아야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자신의 상태를 가짐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스 하나에 DB 하나를 가진다면, &lt;b&gt;마이크로서비스는 서로의 DB를 공유하지 말아야 한다.&lt;/b&gt; 다른 마이크로서비스가 갖고 있는 데이터에 접근해야 할 때는 직접 접근이 아닌 API 등을 통해서 접근해야 한다. 그러면 데이터를 공유하지 않기 때문에 자연스럽게 데이터로 어떻게 조작하는지 등의 구현이 감춰지고, 이렇게 되면 결합도가 낮아지게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;크기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스가 많아진다는 것은 복잡도가 증가한다는 것이고, 마이크로서비스의 특징들을 충족하기 위해서 새로운 기술들을 습득해야할 필요가 있다. 즉 마이크로서비스가 많아지면 한정된 인원으로 다루기가 힘들어진다. 그래서 조직 혹은 본인이 얼마나 많은 마이크로서비스를 다룰 수 있는지 고민하자.&lt;br /&gt;우리는 두 가지에 집중해야 한다. 하나는 &lt;b&gt;마이크로서비스의 경계를 정의하는데 집중&lt;/b&gt;해야 한다. 마이크로서비스의 경계가 정확하게 정의되지 않으면 마이크로서비스들이 서로 복잡하게 얽히게 될 것이다. 이렇게 되면 마이크로서비스가 주는 이점(독립적인 배포 등)이 사라지게 된다.&lt;br /&gt;그래서 마이크로서비스의 크기보다는 &lt;b&gt;마이크로서비스를 다룰 수 있는 역량과 경계(올바르게 정의하는 것) 에 집중&lt;/b&gt;하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유연함&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스는 &lt;b&gt;비용을 들여 미래에 유연해질 수 있는 옵션을 구매하는 것이다.&lt;/b&gt; 기술, 확장가능성, 견고함 등에서 유연함을 구매하는 것이다. 이 말은 &lt;b&gt;아직은 벌어지지 않은 미래를 위해서 너무 많은 옵션을 구매하는 것(너무 많은 마이크로서비스)은 힘들게 하는 원인이 될 수 있다.&lt;/b&gt; 그래서 적당하게 사용하기 위해 &lt;b&gt;점진적으로 마이크로서비스로 넘어가는 것을 권장한다. 비용에 대한 부분과 조직내에 역량을 고려하면서 조금씩 넓혀가는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아키텍처와 조직을 맞춤&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직 구조는 아키텍처에 영향을 준다. 즉 아키텍처는 조직 구조를 닮는다. 조직은 소프트웨어를 더 빨리 배포하길 원한다. 이렇게 하기 위해서는 조직 간에 발생하는 업무 이관 혹은 조직간에 막혀있는 사일로를 줄여야 한다. 그렇기 위해서는 직능으로 팀을 만드는것이 아니라 여러 직능을 한 조직으로 모아서 이관이나 소통의 문제를 줄일 필요가 있다. 예를 들면 프론트팀, 백엔드팀, DB팀이 있었다면 이것을 제품에 맞게 고객팀, 구매팀 이러한 형태로 만들어야 한다는 것이다.&lt;br /&gt;결국 비즈니스 도메인이 시스템 아키텍처를 주도하는 주요 원동력 이라는 것이다. 하지만 회사의 여러 사정들이 있기에 하나의 제품팀을 만들기는 쉽지 않을 것이다. 하지만 이런 &lt;b&gt;비즈니스 도메인을 중심으로 시스템 아키텍처를 만들어야 더 빠른 배포가 가능하다는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Building Microservices&lt;/p&gt;</description>
      <category>IT/아키텍처</category>
      <category>MSA</category>
      <category>마이크로서비스</category>
      <category>아키텍처</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/75</guid>
      <comments>https://calgary.tistory.com/75#entry75comment</comments>
      <pubDate>Thu, 26 Jan 2023 16:19:11 +0900</pubDate>
    </item>
    <item>
      <title>클린코드에 대해서</title>
      <link>https://calgary.tistory.com/72</link>
      <description>&lt;h1&gt;클린코드란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깨끗한 코드는 한 가지를 제대로 한다.&lt;br /&gt;깨끗한 코드는 절대로 설계자의 의도를 숨기지 않는다. 단순하고 직접적이다.&lt;br /&gt;코드를 일으면서 짐작했던 기능을 각 루틴이 그대로 수행하는 코드이다.&lt;br /&gt;중복 줄이고, 표현력 높인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 사람이 이해하기 쉬운 코드를 만드는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클린코드의 주요 원칙&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코딩 표준, 아키텍처 표준 및 설계가이드를 준수하기&lt;/li&gt;
&lt;li&gt;단순한 것이 효율적이다. 복잡함을 최소화하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디자인 패턴 SOLID 원칙&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Simple Responsibility Principle&lt;br /&gt;하나의 클래스는 하나의 책임만 가져야 한다.&lt;/li&gt;
&lt;li&gt;Open/Close Principle&lt;br /&gt;클래스는 확장에 대해 열려 있어야 하고, 변경에 대해서는 닫혀 있어야 한다.&lt;/li&gt;
&lt;li&gt;Liskov Substitution Principle&lt;br /&gt;서브 클래스는 메인 클래스에 기반을 두고 더 구체화하는 방식을 만들어야한다.&lt;/li&gt;
&lt;li&gt;Interface Segregation&lt;br /&gt;의존성을 줄여야 한다.&lt;/li&gt;
&lt;li&gt;Dependency Inversion Principle&lt;br /&gt;추상 클래스에 하위 클래스에 대한 내용이 들어가서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클린코드를 위한 기타 고려사항&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 가급적 작게, 하나의 작업만 수행하도록 만든다.&lt;/li&gt;
&lt;li&gt;함수는 한 가지 일만 한도록 만든다.&lt;/li&gt;
&lt;li&gt;함수 인수는 없도록 하고, 필요하면 2개 이하로 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인수가 많을수록 테스트가 복잡해지고, 함수 이름만으로도 더 쉽게 이해가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;try/catch 블록을 별도로 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;try, catch 블록 내에서 일어나는 코드는 별도의 함수로 빼내는 편이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 쉽게 흐름제어 만들기&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삼항연산자는 꼭 필요하거나 간단한 경우에 이용하기&lt;/li&gt;
&lt;li&gt;조건은 부정보다는 긍정으로 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 주석을 작성하기&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드로 표현하지 못하는 정보를 제공&lt;/li&gt;
&lt;li&gt;의도를 설명&lt;/li&gt;
&lt;li&gt;결과를 경고&lt;/li&gt;
&lt;li&gt;중요성을 강조&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오류처리&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오류 코드보다 예외를 사용하기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/코드</category>
      <category>CleanCode</category>
      <category>cleancoding</category>
      <category>클린코드</category>
      <category>클린코딩</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/72</guid>
      <comments>https://calgary.tistory.com/72#entry72comment</comments>
      <pubDate>Fri, 20 Jan 2023 22:11:03 +0900</pubDate>
    </item>
    <item>
      <title>Mysql 정규화, 인덱스, 트랜잭션, Lock, 동시성에 대해서</title>
      <link>https://calgary.tistory.com/71</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h1&gt;대용량 데이터, 트래픽 처리에 대해&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 개발자의 핵심은 데이터다. 대용량 시스템이 어려운 이유는 많은 양의 데이터에서 시작된다. 어떻게 많은 양의 데이터를 안정적으로 삽입, 갱신, 조회 할 것인가? 이 글에서는 대용량 시스템에 대한 전반적인 이해와 RDBMS 관점에서 대용량 데이터 처리를 위한 &lt;b&gt;정규화, 인덱스, 트랜잭션, 동시성 제어&lt;/b&gt; 를 알아볼 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대용량 데이터, 트래픽 처리는 왜 어려울까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 이유가 있겠지만, 몇 가지를 들어보면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;핵심은 하나의 서버 또는 데이터베이스로 &lt;b&gt;감당하기 힘든 부하&lt;/b&gt; 때문이다.&lt;br /&gt;이로 인해 다수의 서버와 데이터베이스를 활용하게 되는데, 이를 &lt;b&gt;마치 하나인 것처럼 동작&lt;/b&gt;하도록 하기 위해 여러 최적화 기법이나 기술들이 활용된다.&lt;/li&gt;
&lt;li&gt;여러개의 서버에서 유입되는 &lt;b&gt;데이터의 일관성을 보장&lt;/b&gt;할 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;대부분의 웹 서비스들은 24시간 무중단의 특성을 가지고 있어 잘못된 코드 한 줄이 미치는 영향의 범위가 크다.&lt;/li&gt;
&lt;li&gt;시스템이 발전함에 따라 팀이 커지고 &lt;b&gt;도메인별로 여러 개의 마이크로 서비스들이 복잡한 의존관계&lt;/b&gt;를 가지게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대용량 시스템은 어떠해야 하는가?&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;고가용성&lt;br /&gt;언제든 서비스를 이용할 수 있어야한다.&lt;/li&gt;
&lt;li&gt;확장성&lt;br /&gt;시스템이 비대해짐에 따라 증가하는 데이터와 트래픽에 대응할 수 있어야한다.&lt;/li&gt;
&lt;li&gt;관측가능성&lt;br /&gt;문제가 생겼을 때 빠르게 인지할 수 있어야하고, 문제의 범위를 최소화할 수 있어야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스케일업과 스케일아웃&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 스케일업 한다는 것은 서버의 성능을 증가시킨다는 의미,&lt;br /&gt;서버를 스케일아웃 한다는 것은 서버의 대수를 증가시킨다는 의미이다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;스케일 업&lt;/th&gt;
&lt;th&gt;스케일 아웃&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;유지보수 및 관리&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;td&gt;여러 노드에 적절히 부하분산 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확장성&lt;/td&gt;
&lt;td&gt;제약이 있음&lt;/td&gt;
&lt;td&gt;스케일업에 비해 자유로움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장애복구&lt;/td&gt;
&lt;td&gt;서버가 1대, 다운타임이 있음&lt;/td&gt;
&lt;td&gt;장애 탄력성이 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케일아웃에 대해 조금 더 얘기를 해보면, 스케일아웃은 실제로 서버가 여러개 존재하더라도, 클라이언트에게는 마치 하나의 서버인 것처럼 동작해야 한다. 그렇기 때문에 &lt;b&gt;여러 서버들은 대부분 같은 데이터를 바라볼 수 있도록 데이터베이스를 공유하게 된다.&lt;/b&gt;&lt;br /&gt;그렇다면 데이터베이스를 스케일아웃 하기는 어려울까? 라는 생각이 들 수 있다. &lt;b&gt;데이터베이스는 데이터라는 상태를 관리하고 있어, 서버보다 스케일아웃을 하기 위해서는 훨씬 많은 비용이 필요하다.&lt;/b&gt;&lt;br /&gt;현대 서버 아키텍처는 상태관리를 데이터베이스에 위임하고, 서버는 상태관리를 하지 않는 방향으로 가고 있다. 이로 인해 서버는 자유롭게 스케일아웃을 할 수 있는 구조가 되고, 주로 서버는 메모리에서 데이터를 관리하며, 데이터베이스는 디스크의 데이터를 관리한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대용량 시스템의 아키텍처&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시작&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시작은 간단하게 클라이언트와 서버, 데이터베이스가 하나씩 있다.&lt;/li&gt;
&lt;li&gt;이 때는 서버나 데이터베이스가 중단될 경우 시스템 이용이 어려운 상황이다.&lt;/li&gt;
&lt;li&gt;트래픽이 점점 증가하면 서버의 응답속도도 느려진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버 확장&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 다수 추가되고, 부하분산을 위해 로드밸런서가 추가된다.&lt;/li&gt;
&lt;li&gt;서버를 추가할 때 고려할 점은 부하분산인데, 부하분산이 잘 될 수 있도록 앞단의 Nginx와 같은 로드밸런스를 추가한다.&lt;/li&gt;
&lt;li&gt;서버도 추가했지만 서비스가 계속 커져 데이터베이스에도 병목이 생긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터베이스 병목 해결&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 부하를 최소화 할 수 있는 메모리 캐시를 추가한다.&lt;/li&gt;
&lt;li&gt;캐시에 먼저 데이터를 질의하고 없으면 데이터베이스에 질의한 다음 캐시를 갱신하는 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;대외기관 연동&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스의 규모가 커짐에 따라 이메일, 푸시 알림 등 대외기관과의 연동이 필요한 요구사항들이 추가된다.&lt;/li&gt;
&lt;li&gt;이에 따라 점점 대외기관이 우리 트래픽을 받지 못해 서버의 응답속도가 느려진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비동기큐 도입&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답속도에 영향을 미치지 않도록 대외기관 통신을 카프카나 래빗엠큐와 같은 비동기큐를 이용한다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2oG1T/btrWwKUL4j6/d7k6NPRcAYCmkM2B0IBXhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2oG1T/btrWwKUL4j6/d7k6NPRcAYCmkM2B0IBXhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2oG1T/btrWwKUL4j6/d7k6NPRcAYCmkM2B0IBXhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2oG1T%2FbtrWwKUL4j6%2Fd7k6NPRcAYCmkM2B0IBXhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;598&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;확장한 아키텍처&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;점점 느려지는 시스템들을 개선하고, 이런 시스템들이 모여 하나의 거대한 서비스로 발전한다. 이러한 예는 극히 일부이며, 서비스의 특성과 팀에 따라 다양한 아키텍처들이 나타날 수 있다.&lt;/li&gt;
&lt;li&gt;중요한 것은 각각의 기술들이 유기적으로 연결되어 어떠한 역할을 수행하며, 어떤 문제를 해결하고, 동작하는지 이해하는 것이다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceebig/btrWxqamU5e/Q7iJuYBi7D7vdcoAB63tVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceebig/btrWxqamU5e/Q7iJuYBi7D7vdcoAB63tVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceebig/btrWxqamU5e/Q7iJuYBi7D7vdcoAB63tVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fceebig%2FbtrWxqamU5e%2FQ7iJuYBi7D7vdcoAB63tVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1159&quot; height=&quot;575&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Mysql 아키텍처&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 요청하는 SQL 은 Mysql 엔진, 스토리지 엔진을 거쳐 운영체제를 통해 디스크에 접근하여 데이터를 서버에게 전달하는데, Mysql 엔진은 사람으로 비유하면 판단과 명령을 내리는 두뇌, 스토리지 엔진은 동작을 수행하는 팔과 다리라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw7OzF/btrWRCI9xsZ/hRuc3LDMWvRlceVappuGI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw7OzF/btrWRCI9xsZ/hRuc3LDMWvRlceVappuGI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw7OzF/btrWRCI9xsZ/hRuc3LDMWvRlceVappuGI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw7OzF%2FbtrWRCI9xsZ%2FhRuc3LDMWvRlceVappuGI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1212&quot; height=&quot;554&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리파서와 전처리기는 컴파일 과정과 매우 유사하다. 하지만 SQL 은 프로그래밍 언어처럼 컴파일 타임 때 검증할 수 없어 매번 구문 평가를 진행한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql 캐시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mysql 5 버전대까지는 쿼리캐시(SQL에 해당하는 데이터를 저장하는 것) 기능이 있었다. 하지만 8.0 버전대에 들어와서 쿼리캐시는 폐기되었다.&lt;br /&gt;&lt;b&gt;쿼리캐시는 데이터를 캐시하기 때문에 테이블의 데이터가 변경되면 캐시의 데이터도 함께 갱신시켜줘야 한다.&lt;/b&gt;&lt;br /&gt;Mysql 에는 소프트 파싱이 없고, Oracle 에는 소프트 파싱이 존재한다. 하지만 모든 SQL 과 맵핑해 데이터까지 캐싱하지는 않는다. (힌트나 설정으로 가능하기도 하다)&lt;br /&gt;Mysql 에서는 쿼리캐시, Oracle 에서는 소프트 파싱 모두 성능 최적화를 위해 캐시라는 기술을 도입한 것이다. 그러나 두 DB에서의 캐시를 사용하는 범위는 다르다. 쿼리캐시는 소프트 파싱에 비해 조회성능은 더 높지만 캐시 데이터 관리에 더 높은 비용이 들어간다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소프트 파싱&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL, 실행계획을 캐시에서 찾아 옵티마이저 과정을 생략하는 것, 이후 실행 단계로 넘어간다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하드 파싱&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL, 실행계획을 캐시에서 찾지못해 옵티마이저 과정을 거치는 것, 이후 실행단계로 넘어간다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql 엔진&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Mysql 쿼리파서&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 을 파싱하여 Syntax Tree 를 만든다.&lt;br /&gt;이 과정에서 문법 오류 검사가 이루어진다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Mysql 전처리기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리파서에서 만든 Tree 를 바탕으로 전처리를 시작한다.&lt;br /&gt;테이블이나 컬럼 존재 여부, 접근권한 등 Semantic 오류를 검사한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Mysql 옵티마이저&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SQL 을 최적화를 담당하여 SQL 을 가장 빠르고 효율적으로 수행할 최적(최저비용)의 처리경로를 선택하는 것이 핵심이다.&lt;/b&gt;&lt;br /&gt;쿼리를 처리하기 위한 여러 방법들을 만들고, 각 방법들의 비용정보와 테이블의 틍계정보를 이용해 비용을 산정한다.&lt;br /&gt;테이블 순서, 불필요한 조건 제거, 통계정보를 바탕으로 전략을 결정한다. (&lt;b&gt;실행계획 수립&lt;/b&gt;)&lt;br /&gt;옵티마이저가 어떤 전략을 결정하느냐에 따라 성능이 많이 달라진다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;쿼리 실행기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저가 결정한 계획대로 Handler API 를 통해 스토리지 엔진에 요청하는 역할을 수행한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql 스토리지 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에서 데이터를 가져오거나 저장하는 역할을 수행한다.&lt;br /&gt;스토리지 엔진은 InnoDB, MyIsam 등 여러개의 스토리지 엔진이 존재하며, Mysql 8.0 대 부터는 InnoDB 엔진을 기본으로 사용한다.&lt;br /&gt;&lt;b&gt;스토리지 엔진 특성에 따라 데이터 접근이 얼마나 빠른지, 안정적인지, 트랜잭션 기능의 제공여부 등이 달라진다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;InnoDB 와 MyIsam 차이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 스토리지 엔진의 핵심 차이점이라면 &lt;b&gt;Locking 하는 방식과 트랜잭션을 제공하느냐이다.&lt;/b&gt;&lt;br /&gt;Locking 은 트랜잭션 처리의 순차성을 보장하기 위한 방법이다.&lt;br /&gt;&lt;b&gt;트랜잭션 순차성을 보장하기 위해 InnoDB 는 특정한 로우를 Locking 하는 반면, MyIsam 은 테이블 전체를 Locking 한다.&lt;br /&gt;또한 InnoDB 는 트랜잭션을 제공하며, MyIsam 은 제공하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;정규화, 비정규화&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위키백과에 데이터베이스 정규화에 대한 설명을 보면, &lt;b&gt;관계형 데이터베이스의 설계에서 중복을 최소화하게 데이터를 구조화 하는 프로세스라고 한다...*&lt;/b&gt; 데이터베이스 디자인 표준 가이드는 데이터베이스가 완전히 정규화되게 디자인되어야 한다. 하지만 그 뒤에 &lt;b&gt;일부가 성능상의 이유로 비정규화될 수 있다.&lt;/b&gt;&lt;br /&gt;이 내용에서 &lt;b&gt;결국 읽기와 쓰기 사이의 트레이드 오프라는 것을 알 수 있다. 읽기와 쓰기를 분리해서 바라보고, 둘 중 어떤 것에 중점을 두고 최적화할지에 따라 설계가 달라지기 때문이다.&lt;/b&gt;&lt;br /&gt;즉, 데이터의 중복을 최소화 한다는 것은 여러 곳에 있는 동일한 데이터를 한 곳에서만 관리한다는 것이다. 이렇게 되면 데이터의 불일치가 생기지 않게 된다. 하지만 중복을 최소화하게 되면 읽을 때는 항상 원본 데이터를 찾아가서 참조해야한다.&lt;br /&gt;&lt;b&gt;테이블 설계 관점에서 정규화는 읽기의 성능을 희생하고, 데이터 관리를 용이하게 하는 것이다.&lt;/b&gt;&lt;br /&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복을 제거하고 한 곳에서 데이터를 관리&lt;/li&gt;
&lt;li&gt;데이터 정합성 유지가 쉬움&lt;/li&gt;
&lt;li&gt;읽기시 참조가 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규화 고려사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마나 빠르게 데이터의 &lt;b&gt;최신성을 보장해야 하는가?&lt;/b&gt;&lt;br /&gt;&lt;b&gt;데이터 변경 주기와 조회 주기&lt;/b&gt;는 어떻게 되는가? (히스토리성 데이터는 오히려 정규화를 하지 않은 것이 좋은편이다.)&lt;br /&gt;읽기시에 객체(테이블)의 &lt;b&gt;탐색 깊이&lt;/b&gt;는 얼마나 깊은가? (즉, &lt;b&gt;몇 단계 조인을 하여 탐색&lt;/b&gt;해야 하는가?)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규화를 하기로 했다면 읽기 시 데이터를 어떻게 가져올 것인가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테이블 조인을 많이 활용하는데, 고민해볼 문제다.&lt;/b&gt;&lt;br /&gt;테이블 조인이 쉽게 데이터를 조회해올 수는 있지만, 서로 다른 테이블의 결합도를 높여 리팩토링이 힘들어지고 아키텍쳐 성능을 풀어가는데 어려움을 겪을 수 있으며, 조인이 성능면에서도 좋은편이 아니기 때문이다. 또한 빈번하게 테이블 조인이 일어나면 영향도 파악이 힘들어진다. 이러한 문제는 추후에 캐싱으로 확장할 여지마저 줄어들기에 최후의 수단으로 보류하는 것이 좋을 수 있다.&lt;/li&gt;
&lt;li&gt;조회시에는 성능이 좋은 &lt;b&gt;별도 데이터베이스나 캐싱 등 다양한 최적화 기법&lt;/b&gt;을 이용할 수 있다.&lt;/li&gt;
&lt;li&gt;조인을 사용하게 되면 이러한 기법들을 사용하는데 제한이 있거나 더 많은 리소스가 들 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;읽기 쿼리 한 번 더 발생되는 것은 그렇게 큰 부담이 아닐 수도 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규화 예시&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주문 테이블에 제조사 정보가 있다면, 제조사 이름이 바뀌었을 경우 바뀐 이름이 들어가는게 맞는지 혹은 기존 이름이 들어가는 것이 맞는지 이런 경우에는 비즈니스, 요구사항에 따라 다르기에 PM 혹은 기획자 등에게 확인하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;SNS 팔로잉을 예로 들어 A 가 B 를 팔로우를 한다면, 팔로잉 테이블에 A 와 B 의 닉네임이 들어갈텐데, 만약 B 의 팔로워가 100만명쯤 되는 인플루언서이고, 닉네임을 변경한다면 100만 라인을 update 할 것인가? 아니면 과거 닉네임은 update 하지 않고 그대로 보여줄 것인가? SNS 특성상 팔로잉은 최신 닉네임을 보여주는게 맞을것이다. 그렇다면 100만 라인을 update 하는 것 보다는 정규화를 하는게 맞을 가능성이 높다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비(반)정규화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복을 허용&lt;/li&gt;
&lt;li&gt;데이터 정합성 유지가 어려움&lt;/li&gt;
&lt;li&gt;참조없이 읽기가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;데이터베이스 성능 핵심&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 데이터는 메모리에 먼저 쓰고, 결국 디스크에 저장된다. 즉, 데이터베이스 성능에 핵심은 디스크 접근(I/O)을 최소화 하는 것이다. 그렇다면 디스크 접근을 줄이려면 메모리에 올라온 데이터로 최대한 요청을 처리하는 것인데 데이터베이스는 메모리에 데이터 유실을 고려해 WAL(Write Ahead Log)를 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WAL (Write Ahead Log, 로그 선행 기입)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기가 발생할 때 마다 디스크에 가서 데이터를 저장하는 것은 비효율적이기에 메모리에 쌓아뒀다가 한 번에 디스크로 보내어 쓴다.&lt;br /&gt;일단 파일의 끝부분부터 순차적으로 쿼리의 로그가 남아서 메모리에 쌓여있던 데이터가 디스크에 가지 않고 유실되더라도 이 파일에 히스토리가 있어 서버가 다시 실행되면 파일에 있던 로그를 순차적으로 재실행시켜 디스크에 있는 원본 데이터와 같아져 정합성을 유지한다. redo 및 undo 정보를 모두 로그에 기록하며, buffer 를 비우기 전에 로그 파일에 기록한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 프로파일링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 프로파일링은 쿼리 수행 시 여러 성능 지표나 통계를 확인할 수 있는 기능이다.&lt;br /&gt;Mysql에서 쿼리가 처리되는 동안 각 단계별 작업에 시간이 얼마나 걸렸는지 확인할 수 있으며, Mysql 5.1 버전 이상에서 지원한다.&lt;/p&gt;
&lt;h1&gt;인덱스&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인덱스는 정렬된 자료구조로 핵심은 인덱스를 통해 읽기 시에 탐색(검색) 범위를 최소화 하는 것이다. 읽기의 성능은 높이지만, 쓰기나 갱신, 삭제의 성능은 낮아진다. 테이블의 쓰기, 갱신, 삭제가 일어나면 인덱스 테이블에서도 동일한 과정이 일어나기 때문이다. 그래서 인덱스의 성질을 잘 이해하고 활용하는 것이 중요하다.&lt;/b&gt;&lt;br /&gt;특정 컬럼으로 인덱스를 설정해두면 해당 컬럼과 id 값에 대한 인덱스 테이블이 생성된다. 그리고 쿼리가 들어오면 인덱스를 먼저 조회하고, 다음으로 원본 데이터를 찾아간다. 그래서 데이터베이스가 데이터를 스캔하는 방식에 대해 알고, 그에 따라 인덱스를 잡는것이 좋다. &lt;b&gt;예를 들어 식별자가 적은 성별로 인덱스를 잡으면 탐색 시 데이터를 절반밖에 걸러낼 수가 없어 탐색범위가 많이 좁혀지지 않는다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Mysql 인덱스는 PK 사이즈가 커지게 되면 하나의 노드가 가질 수 있는 데이터의 개수가 적어지기에 삽입, 삭제 시에 노드의 리밸런싱이 빈번하게 일어날 수 있다. 즉 PK 사이즈가 인덱스의 사이즈를 결정한다.&lt;/b&gt; 반면 오라클은 PK 대신 인덱싱 테이블에 데이터의 주소를 가지고 있다.&lt;br /&gt;&lt;b&gt;우리의 의도대로 인덱스가 동작하지 않을 수 있는데, explain 으로 확인해보자.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;그리고 인덱스도 비용이다. 쓰기의 성능을 희생하고 읽기의 성능을 얻는 것이다. 그렇다면 꼭 인덱스로만 해결할 수 있는 문제인가를 생각해보아야 한다.&lt;/b&gt;&lt;br /&gt;Mysql 에서 탐색에 사용되는 자료구조는 B+Tree 이다. 아래 그림에서 Cherry 인덱스 키를 찾아가는 방법을 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baI0VF/btrWS1PB8Fj/askwG5cKZ9U50K8CkKkW91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baI0VF/btrWS1PB8Fj/askwG5cKZ9U50K8CkKkW91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baI0VF/btrWS1PB8Fj/askwG5cKZ9U50K8CkKkW91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaI0VF%2FbtrWS1PB8Fj%2FaskwG5cKZ9U50K8CkKkW91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1334&quot; height=&quot;556&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brNLDE/btrWR8VIykn/iwX28toNFnS6nkzDXMSo5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brNLDE/btrWR8VIykn/iwX28toNFnS6nkzDXMSo5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brNLDE/btrWR8VIykn/iwX28toNFnS6nkzDXMSo5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrNLDE%2FbtrWR8VIykn%2FiwX28toNFnS6nkzDXMSo5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1194&quot; height=&quot;576&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스를 다룰 때 주의할점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인덱스 필드 가공 (필드 가공이 생기면 인덱스를 사용할 수 없다)&lt;br /&gt;예시 1 필드의 데이터 타입을 변형하는 경우&lt;br /&gt;// age 는 int 타입&lt;br /&gt;SELECT * FROM Member WHERE age = '1';&lt;/li&gt;
&lt;li&gt;예시 2 필드를 연산하는 경우 (인덱스에 저장되어 있는 데이터로 인덱스를 찾아가기에 연산을 하게 되면 인덱스를 활용할 수 없다)&lt;br /&gt;// age 는 int 타입&lt;br /&gt;SELECT * FROM Member WHERE age * 10 = 1;&lt;/li&gt;
&lt;li&gt;복합 인덱스&lt;br /&gt;예시 1&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sSpMb/btrXfaEd99l/JgKQhJfpYUWr1BnvdWnh2k/img.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;두 번째 컬럼인 원산지는 첫 번째 컬럼인 과일을 정렬한 후에 동일한 과일에 대해 원산지가 정렬되기에 원산지로만 where 조건을 주면 인덱스를 타도 훨씬 느려진다.&lt;/li&gt;
&lt;li&gt;하나의 쿼리에는 하나의 인덱스만 사용&lt;br /&gt;기본적으로 하나의 쿼리에는 하나의 인덱스만 적용되기에 여러 인덱스 테이블을 동시에 탐색하지 않는다. (index merge hint 를 사용하면 가능) 또한 WHERE, ORDER BY, GROUP BY 를 혼합해서 사용할 때에는 인덱스를 잘 고려해서 사용하여야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클러스터형 인덱스(Clustered Index)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클러스터형 인덱스는 테이블 전체가 정렬된 인덱스가 되는 방식의 인덱스 종류이다. 실제 데이터와 무리(cluster)를 지어 인덱싱 되므로 클러스터형 인덱스라고 부른다.&lt;br /&gt;또한, 데이터와 함께 전체 테이블이 물리적으로 정렬된다.&lt;/b&gt;&lt;br /&gt;클러스터형 인덱스는 테이블당 하나만 생성할 수 있고, 어떤 컬럼을 선택하여 클러스터형 인덱스를 만들지에 따라 성능이 크게 달라질 수 있다. 특정 컬럼을 PK 로 지정하면 클러스터형 인덱스를 생성한다. 혹은 Unique + Not null 로 지정해도 클러스터형 인덱스를 생성한다. 이 두가지가 모두 없는 경우 InnoDB 는 내부적으로 GEN_CLUST_INDEX 라는 컬럼을 생성하여 클러스터형 인덱스를 생성한다. GEN_CLUST_INDEX 는 행이 생성된 순서대로 값이 부여된다. 즉, Mysql 의 PK 는 클러스터 인덱스이며, PK 를 제외한 모든 인덱스는 PK 를 가지고 있다.&lt;br /&gt;그래서 클러스터형 인덱스는 PK 를 활용한 검색, 특히 범위 검색이 빠르다. 또한, 보조 인덱스들이 PK 를 가지고 있어 커버링에 유리하다.&lt;br /&gt;또한 클러스터형 인덱스는 데이터 위치를 결정하는 키 값이다. 즉, 클러스터 키가 4를 제외한 1~5 까지 정렬되어 있는데, 4 를 추가하게 되면 5 부터는 뒤로 밀리고, 3과 5 사이에 4를 넣어주게 된다. 그래서 클러스터 키 삽입 및 갱신시에 성능 이슈가 발생한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비클러스터형 인덱스 (Non-Clustered Index)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비클러스터형 인덱스는 보조 인덱스(Secondary Index)라고도 불리며, 클러스터형 인덱스와 다르게 &lt;b&gt;물리적으로 테이블을 정렬하지 않는다. 대신 정렬된 별도의 인덱스 페이지를 생성하고 관리한다. 즉, 실제 데이터를 함께 가지고 있지 않는다.&lt;/b&gt;&lt;br /&gt;비클러스터형 인덱스는 테이블 당 여러개 생성이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커버링 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인덱스에서 바로 데이터를 조회할 수 있는 인덱스로 쿼리의 성능을 향상시키기 위한 인덱스의 형태이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커버링 인덱스는 인덱스에 필요한 모든 컬럼을 포함하여 쿼리를 처리할 수 있도록 하고, 인덱스 자체에 검색 조건에 필요한 컬럼 이외의 데이터도 함께 포함시킨다. 이를 통해 쿼리 실행에 필요한 추가적인 디스크 I/O 나 메모리 로딩을 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 커버링 인덱스를 사용하면 인덱스 크기가 커질 수 있으며, 업데이트 작업에 따른 추가적인 비용이 발생할 수 있어 이를 고려해야 한다.&lt;/p&gt;
&lt;h1&gt;트랜잭션&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 ACID&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACID (원자성, 일관성, 고립성, 지속성) 는 데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Atomicity 원자성&lt;br /&gt;데이터베이스 트랜잭션은 All or Nothing 이다.&lt;/li&gt;
&lt;li&gt;Consistency 무결성, 일관성&lt;br /&gt;트랜잭션이 종료되었을 때 데이터의 무결성이 보장된다. 제약조건을 통해 무결성을 유지한다. (유니크, 외래키 제약 등)&lt;/li&gt;
&lt;li&gt;Isolation 독립성&lt;br /&gt;트랜잭션은 서로 간섭하지 않고 독립적으로 동작한다. 하지만 많은 성능을 포기해야함으로 트랜잭션 격리레벨을 통해 제어를 한다.&lt;/li&gt;
&lt;li&gt;Durability 지속성&lt;br /&gt;완료된 트랜잭션은 유실되지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 사용 시 유의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트랜잭션도 비용이 든다. 그래서 트랜잭션의 범위를 작게 가져가는 것이 좋다. 트랜잭션이 길어지면 DB의 커넥션을 오래 유지하기 때문에 동시다발적으로 일어나면 커넥션 풀 고갈로 이어질 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선언적 트랜잭션 사용 시 유의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신에게 @Transactional 을 선언하고, 자신을 호출하는 상위 메소드가 있을 경우 트랜잭션이 정상적으로 작동하지 않는다. (프록시 패턴으로 인해서)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SQL 로 트랜잭션을 확인하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;START TRANSACTION; SQL... COMMIT; &amp;lt;- SQL 부분에는 원하는 트랜잭션을 확인하기 위한 본인의 SQL 을 작성한다. 이 SQL 을 실행하면 트랜잭션이 수행된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 격리 레벨 (Isolation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dirty Read: 커밋되지 않은 데이터를 읽는 것&lt;br /&gt;Non Repeatable Read: 하나의 트랜잭션에서 같은 데이터를 여러 번 읽었을 때 결과가 다른 경우&lt;br /&gt;Phantom Read: 같은 조건으로 데이터를 읽었을 때 없던 데이터가 생긴 경우&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Dirty Read&lt;/th&gt;
&lt;th&gt;Non Repeatable Read&lt;/th&gt;
&lt;th&gt;Phantom Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read Uncommitted&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read Committed&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeatable Read&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serializable Read&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 표를 보면 Read Uncommitted -&amp;gt; Serializable Read 로 갈수록 이상현상이 없어진다. 대신 아래로 갈수록 동시 처리량은 낮아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBMS 마다 트랜잭션 격리레벨이 다르기에 확인해보고 사용하자. &lt;b&gt;DB Lock 의 범위가 길어질수록 커넥션 풀 점유시간이 길어지고, 커넥션 풀 고갈로 이어질 수 있기에 필요에 맞게 최소화하는 것이 중요하다.&lt;/b&gt; 그래서 보통 Read Committed, Repeatable Read 를 많이 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Propagation 레벨 (전파 레벨)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산시스템에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;트랜잭션을&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;여러&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;서비스&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;간에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전달하고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;동기화하는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;과정의 수준을 나타내는 것이다. 이를 통해 다음과 같은 결과를 만들어 낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션이 여러 단계로 나누어지거나 여러 서비스에 걸쳐 실행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;트랜잭션&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;시작점부터&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;끝점까지&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;안전하게&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전달하고&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;동기화한다.&lt;span&gt;&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;데이터의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;일관성과&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;동기화를&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;보장하는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트에서 선언적 트랜잭션에 대해 트랜잭션이 어떻게 동작하는지 방법을 정의할 수 있다. 선언적 트랜잭션은 트랜잭션 템플릿을 사용하는 것보다 자유롭게 사용하지 못하기 때문에 Propagation 레벨을 통해 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Mysql Lock&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql Lock 종류&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 락&lt;/li&gt;
&lt;li&gt;레코드 락 (Mysql 에서는 Row 가 아닌 인덱스를 잠금 -&amp;gt; 인덱스가 없는 조건으로 Locking Read 시 불필요한 데이터들이 잠길 수 있음)&lt;/li&gt;
&lt;li&gt;gap 락&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql Lock 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트랜잭션의 커밋 혹은 롤백 시점에서 잠금이 풀린다. 트랜잭션이 곧 락의 범위이다. 락의 범위를 줄인다는 것은 트랜잭션의 범위를 줄인다는 것이다.&lt;br /&gt;매번 잠금이 발생할 경우 성능저하를 피할 수가 없다.&lt;/b&gt;&lt;br /&gt;예를 들어 트랜잭션 범위 내에서 AWS S3 에 파일 업로드를 한다면 S3 에 파일 업로드하는 시간, 네트워크 지연시간 등에 따라 트랜잭션 범위도 커지기에 가능하면 트랜잭션 범위가 늘어나는 것을 방지하기 위해 트랜잭션 밖에서 수행하는 것이 좋다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql Lock 발생할 수 있는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 데이터 양이 많을 경우 컬럼을 추가할 때 default 값을 넣으면 테이블 락이 발생할 수도 있다. 그래서 24시간 운영하는 시스템이라면 이러한 방법을 사용하는 것은 위험하다. 이런경우 컬럼을 추가할 때 별도의 마이그레이션 배치를 만들어서 조금씩 데이터를 채워넣거나 아니면 조회 시점에 null 이면 값을 채워주는 방법을 사용하기도 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mysql Lock 확인 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락 상태를 확인&lt;br /&gt;SELECT *&lt;br /&gt;FROM performance_sechema.data_locks;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 상태를 확인&lt;br /&gt;SELECT *&lt;br /&gt;FROM information_schema.innodb_trx;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Shared Lock (읽기 락)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT ... FOR SHARE 를 통해 읽기 락을 획득할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 수정은 잠그고, 읽을 수 있도록 허용한다. 동시에 여러 트랜잭션이 데이터를 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Exclusive Lock (쓰기 락)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT ... FOR UPDATE 또는 UPDATE, DELETE 쿼리를 통해 쓰기 락을 획득할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 여러 트랜잭션이 데이터를 수정하는 것을 방지하기 위해 사용한다. 읽기도 불가하게 잠근다.&lt;/p&gt;
&lt;h1&gt;동시성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 동시성 이슈가 발생하는 일반적인 패턴은 &lt;b&gt;공유자원을 조회, 갱신할 때이다.&lt;/b&gt; 동시성 제어를 위한 가장 보편적인 방법은 락을 통한 트랜잭션들을 줄 세우는 것이다. &lt;b&gt;락을 통해 동시성을 제어할 때는 락의 범위를 최소화 하는것이 중요하다. Mysql 에서는 트랜잭션의 커밋 혹은 롤백 시점에 잠금이 풀리는데, 이는 트랜잭션이 곧 락의 범위이다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동시성 이슈가 어려운 이유&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로컬에서는 대부분 하나의 스레드로 테스트한다.&lt;/li&gt;
&lt;li&gt;이슈가 발생하더라도 오류가 발생하지 않는다.&lt;/li&gt;
&lt;li&gt;코드에서 잘 보이지 않는다.&lt;/li&gt;
&lt;li&gt;항상 발생하지 않고 비결정적으로 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;낙관적 락, 비관적 락&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;락을 통한 줄 세우기는 비관적 락 이라고 하며, 락을 통한 동시성 제어는 불필요한 대기 상태를 만든다.&lt;/b&gt; 그럼 동시성이 빈번하지 않은 쿼리로 인해 다른 쿼리가 대기한다면 &lt;b&gt;동시성 이슈가 빈번하지 않길 기대하고, 어플리케이션에서 제어하는 낙관적 락이 있다.&lt;/b&gt; 낙관적 락은 CAS (Compare and set) 을 통해 제어한다. 간단히 설명해서 데이터베이스 테이블에 로우마다 데이터의 버전을 나타내고, 쿼리의 조건으로 버전을 체크하는 것이다. 그럼 동시성 이슈가 발생하더라도 버전이 맞지 않으면 해당 쿼리는 실패하게 될 것이고, 실패에 대한 처리를 직접 구현하는 방법이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인텔리제이에서 간단한 자바 API 동시성 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브레이크 포인트를 잡고 디버깅 모드로 실행한다. 그리고 브레이크 포인트에 suspend 를 All -&amp;gt; Thread 로 변경한다. 여기에서 All 은 요청을 하나밖에 처리를 못하고, 두 개를 동시에 보내도 나머지 한 개는 기다려야 한다.&lt;br /&gt;이렇게 디버깅 모드가 실행되고 나서 API 를 두 번 호출해본다. 그러면 다른 스레드로 두 개가 브레이크 포인트에 잡히게 된다.&lt;/p&gt;
&lt;h1&gt;마치며&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 업데이트 중이며, 관련해서 공부하면 좋은 주제에 대해 남기고 마무리한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TODO&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PK 설정 Auto increment, UUID 비교&lt;/li&gt;
&lt;li&gt;Null 허용에 대해서&lt;/li&gt;
&lt;li&gt;Mysql 랜덤io 순차io&lt;/li&gt;
&lt;li&gt;Mysql 데이터 스캔방식&lt;/li&gt;
&lt;li&gt;Mysql 성능 개선 메모리 활용방법&lt;/li&gt;
&lt;li&gt;Mysql 의 넥스트 키 락이 등장한 배경&lt;/li&gt;
&lt;li&gt;Mysql 외래키로 인한 잠금&lt;/li&gt;
&lt;li&gt;Mysql 데드락&lt;/li&gt;
&lt;li&gt;Mysql master / slave&lt;/li&gt;
&lt;li&gt;Mysql 파티셔닝 (초대량 데이터를 관리하는 방법)&lt;/li&gt;
&lt;li&gt;InnoDB redo, undo&lt;/li&gt;
&lt;li&gt;InnoDB buffer pool&lt;/li&gt;
&lt;li&gt;Java 에서의 동시성 이슈 제어방법&lt;/li&gt;
&lt;li&gt;분산환경에서의 동시성 이슈 제어방법&lt;/li&gt;
&lt;li&gt;ngrinder, jmeter(부하테스트 툴), easy random&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/대용량 데이터&amp;amp;트래픽 처리</category>
      <category>Database</category>
      <category>DB</category>
      <category>mysql</category>
      <category>대규모데이터</category>
      <category>대규모데이터처리</category>
      <category>대규모시스템</category>
      <category>대용량데이터</category>
      <category>대용량데이터처리</category>
      <category>대용량시스템</category>
      <category>데이터베이스</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/71</guid>
      <comments>https://calgary.tistory.com/71#entry71comment</comments>
      <pubDate>Thu, 19 Jan 2023 02:01:31 +0900</pubDate>
    </item>
    <item>
      <title>서브넷마스크란? 간단한 서브네팅 방법</title>
      <link>https://calgary.tistory.com/70</link>
      <description>&lt;h1&gt;서브넷마스크 사용이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커다란 네트워크(호스트가 많은 네트워크)를 작은 네트워크 여러개로 나누어 쓰기 위함이다.&lt;/p&gt;
&lt;h1&gt;서브넷마스크&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브넷마스크는 IP 주소와 네트워크 주소를 구분할 때 사용하는데 2진수 숫자1은 네트워크 주소, 0은 호스트 주소로 표시한다.&lt;br /&gt;보통 우리가 편하게 받아들일 수 있는 10진수를 사용해 255.0.0.0, 255.255.0.0, 255.255.255.0 과 같이 표현한다.&lt;br /&gt;예를들어 103.9.32.146 주소에 255.255.255.0 서브넷마스크를 사용하는 IP는 네트워크 주소가 103.9.32.0이고, 호스트 주소는 0.0.0.146이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브넷마스크는 비트단위나 10진수로 표현하는 방법을 사용하는데,&lt;br /&gt;비트단위로 표현하는 방법은 A클래스는 /8, B클래스는 /16, C클래스는 /24 로 표기한다.&lt;br /&gt;이를 10진수로 표현하면 A클래스는 255.0.0.0, B클래스는 255.255.0.0, C클래스는 255.255.255.0 으로 표기한다.&lt;/p&gt;
&lt;h1&gt;서브넷마스킹&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 IP 주소의 호스트 부분의 일부를 네트워크 부분으로 바꾸는 작업이다.&lt;/p&gt;
&lt;h1&gt;통신&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브넷은 하나의 네트워크이기에 서로 나뉜 서브넷끼리 라우터를 통해서만 통신이 가능하다.&lt;/p&gt;
&lt;h1&gt;서브네팅&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브넷마스크를 효율적으로 이용하기 위해 호스트를 줄이는 작업이다. 즉, 네트워크 호스트를 최적화하는 작업이다.&lt;br /&gt;원래 부여된 클래스의 기준을 무시하고 새로운 네트워크-호스트 구분 기준을 사용자가 정해 원래 클래스풀 단위의 네트워크보다 더 나누어 사용하는 것을 말한다.&lt;br /&gt;IP 주소와 서브넷마스크를 AND 연산하면 네트워크 주소를 알 수 있다.&lt;/p&gt;
&lt;h1&gt;네트워크 유효범위를 파악하는 방법&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내 IP 를 2진수로 표현한다.&lt;/li&gt;
&lt;li&gt;서브넷마스크를 2진수로 표현한다.&lt;/li&gt;
&lt;li&gt;2진수 AND 연산으로 서브네팅된 네트워크 주소를 알아낸다.&lt;/li&gt;
&lt;li&gt;호스트 주소 부분을 2진수 1로 모두 변경해 브로드캐스트 주소를 알아낸다.&lt;/li&gt;
&lt;li&gt;유효 IP 범위를 파악한다. 서브네팅된 네트워크 주소 +1 은 유효IP중 가장 작은 IP이다.&lt;/li&gt;
&lt;li&gt;브로드캐스트 주소 -1 은 유효 IP 중 가장 큰 IP이다.&lt;/li&gt;
&lt;li&gt;2진수로 연산되어 있는 결과값을 10진수로 변환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;간단한 서브네팅 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP: 103.9.32.146, 서브넷: 255.255.255.192 를 가지고 서브네팅을 해본다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브넷마스크를 2진수로 변환한다. (11111111 11111111 11111111 11000000)&lt;/li&gt;
&lt;li&gt;현재의 서브넷이 가질수 있는 최대 IP 개수 크기를 파악한다. (2의 6승 = 64)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브넷의 2진수를 보면 1로 된 부분은 네트워크 부분, 0으로 된 부분은 호스트 부분이다.&lt;/li&gt;
&lt;li&gt;여기서 호스트 부분인 0의 개수만큼 2를 곱한다 (2의 6승)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;64의 배수로 나열하여 기준이 되는 네트워크 주소를 파악한다.&lt;br /&gt;첫 블록은 0부터 시작하고, 각 네트워크의 마지막 주소가 브로드캐스트 주소가 된다.&lt;br /&gt;이 주소는 다음 블록 네트워크 주소의 -1 수이다.&lt;br /&gt;(0&lt;del&gt;63 / 64&lt;/del&gt;127 / 128&lt;del&gt;191 / 192&lt;/del&gt;255)&lt;/li&gt;
&lt;li&gt;103.9.32.146 IP 에서 호스트 주소 146 이 속한 네트워크를 선택한다. (128~191)&lt;/li&gt;
&lt;li&gt;필요한 주소를 정리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 주소: 103.9.32.128 (첫번째 숫자)&lt;/li&gt;
&lt;li&gt;브로드캐스트 주소: 103.9.32.191 (마지막 숫자)&lt;/li&gt;
&lt;li&gt;유효 IP 범위: 103.9.32.129 ~ 103.3.32.190 (네트워크 주소와 브로드캐스트 주소 사이)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/네트워크</category>
      <category>서브네팅</category>
      <category>서브넷</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/70</guid>
      <comments>https://calgary.tistory.com/70#entry70comment</comments>
      <pubDate>Wed, 21 Dec 2022 08:00:28 +0900</pubDate>
    </item>
    <item>
      <title>표준 인증 프로토콜 SAML, OAuth, OIDC</title>
      <link>https://calgary.tistory.com/69</link>
      <description>&lt;h1&gt;SAML, OAuth, OIDC 란?&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mC11M/btrUaXPffoy/aWKJ1QX1UUf07ThMnKOFzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mC11M/btrUaXPffoy/aWKJ1QX1UUf07ThMnKOFzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mC11M/btrUaXPffoy/aWKJ1QX1UUf07ThMnKOFzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmC11M%2FbtrUaXPffoy%2FaWKJ1QX1UUf07ThMnKOFzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;453&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGWz6x/btrT6YB4P3z/CrdM0ougSUNpdCV0W6MGG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGWz6x/btrT6YB4P3z/CrdM0ougSUNpdCV0W6MGG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGWz6x/btrT6YB4P3z/CrdM0ougSUNpdCV0W6MGG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGWz6x%2FbtrT6YB4P3z%2FCrdM0ougSUNpdCV0W6MGG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;218&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;관련 용어&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1TRA5/btrUacF13II/bHLBjqcvWG25D4pklpwk8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1TRA5/btrUacF13II/bHLBjqcvWG25D4pklpwk8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1TRA5/btrUacF13II/bHLBjqcvWG25D4pklpwk8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1TRA5%2FbtrUacF13II%2FbHLBjqcvWG25D4pklpwk8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;474&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;SAML 인증 시퀀스&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pWCRK/btrUbyayfo0/UqCDkXw8xxG7sI0wZelwm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pWCRK/btrUbyayfo0/UqCDkXw8xxG7sI0wZelwm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pWCRK/btrUbyayfo0/UqCDkXw8xxG7sI0wZelwm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpWCRK%2FbtrUbyayfo0%2FUqCDkXw8xxG7sI0wZelwm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;651&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Oauth 인증 시퀀스&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PiVpH/btrT9PRZ1WB/08J9yaL3VflnuQGtaBWWPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PiVpH/btrT9PRZ1WB/08J9yaL3VflnuQGtaBWWPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PiVpH/btrT9PRZ1WB/08J9yaL3VflnuQGtaBWWPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPiVpH%2FbtrT9PRZ1WB%2F08J9yaL3VflnuQGtaBWWPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;517&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;OIDC 인증 시퀀스 (RP: Relying Party)&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkcYt9/btrUceQp9Qx/pyxdejKpM2NnXNaK3WQuK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkcYt9/btrUceQp9Qx/pyxdejKpM2NnXNaK3WQuK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkcYt9/btrUceQp9Qx/pyxdejKpM2NnXNaK3WQuK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkcYt9%2FbtrUceQp9Qx%2FpyxdejKpM2NnXNaK3WQuK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;425&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT/보안</category>
      <category>oauth</category>
      <category>OIDC</category>
      <category>saml</category>
      <category>인증</category>
      <category>프로토콜</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/69</guid>
      <comments>https://calgary.tistory.com/69#entry69comment</comments>
      <pubDate>Wed, 21 Dec 2022 07:15:49 +0900</pubDate>
    </item>
    <item>
      <title>AWS 컨테이너 서비스 도입을 위한 정보 알아보기</title>
      <link>https://calgary.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 에서 서버를 관리하지 않고 컨테이너 단위로 제공되는 서비스를 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24시간 가동한다면 서버 관리 없이 컨테이너만 받아서 사용하는 컴퓨팅 옵션이 비용이 더 들겠지만, 그렇지 않는다면 컨테이너만 사용하는 컴퓨팅 옵션은 어떤식으로 비용이 측정되는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 컨테이너 서비스는 아래 이미지와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MZLac/btrT98Q4VTk/2dF9x7fIbp5SvJqAeAXXK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MZLac/btrT98Q4VTk/2dF9x7fIbp5SvJqAeAXXK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MZLac/btrT98Q4VTk/2dF9x7fIbp5SvJqAeAXXK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMZLac%2FbtrT98Q4VTk%2F2dF9x7fIbp5SvJqAeAXXK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;753&quot; height=&quot;844&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fargate 와 EC2 의 차이는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yusuw/btrT7FPNQUo/cR9dzRB0lChDJ6rEMqQhX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yusuw/btrT7FPNQUo/cR9dzRB0lChDJ6rEMqQhX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yusuw/btrT7FPNQUo/cR9dzRB0lChDJ6rEMqQhX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYusuw%2FbtrT7FPNQUo%2FcR9dzRB0lChDJ6rEMqQhX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1144&quot; height=&quot;654&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 비용 옵션은 네 가지가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIBQ1/btrT7EQUzKy/bIk9xenElAWyG3bJVxErKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIBQ1/btrT7EQUzKy/bIk9xenElAWyG3bJVxErKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIBQ1/btrT7EQUzKy/bIk9xenElAWyG3bJVxErKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIBQ1%2FbtrT7EQUzKy%2FbIk9xenElAWyG3bJVxErKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1016&quot; height=&quot;654&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스팟 인스턴스는 사용자가 제시한 가격(입찰가격)을 정해놓고, 자원의 시세가 입찰가격보다 저렴해지면 인스턴스를 사용할 수 있고, 반대로 입찰가격보다 자원의 시세가 비싸지면 인스턴스는 중단된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8L3aQ/btrT6vmm55y/CthSr02WGDN6ji3kuklXpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8L3aQ/btrT6vmm55y/CthSr02WGDN6ji3kuklXpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8L3aQ/btrT6vmm55y/CthSr02WGDN6ji3kuklXpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8L3aQ%2FbtrT6vmm55y%2FCthSr02WGDN6ji3kuklXpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;883&quot; height=&quot;654&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스팟 인스턴스는 도입에 적합한 상황이 아니기 때문에 EC2 인스턴스와 Fargate 를 비교해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2&amp;nbsp; 와 Fargate 비용측정 기준은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGRdYv/btrT6XCYrx5/atJ2bIUOfS2g7i2POQG841/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGRdYv/btrT6XCYrx5/atJ2bIUOfS2g7i2POQG841/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGRdYv/btrT6XCYrx5/atJ2bIUOfS2g7i2POQG841/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGRdYv%2FbtrT6XCYrx5%2FatJ2bIUOfS2g7i2POQG841%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;736&quot; height=&quot;433&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chZyFR/btrT81ykUOV/KdPBbUZB7ydFJugt87uze0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chZyFR/btrT81ykUOV/KdPBbUZB7ydFJugt87uze0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chZyFR/btrT81ykUOV/KdPBbUZB7ydFJugt87uze0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchZyFR%2FbtrT81ykUOV%2FKdPBbUZB7ydFJugt87uze0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;410&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 서비스의 비용을 정확히 측정하여 비교하는 것은 쉽지 않지만, 내가 필요한 성능, 상황 기준 Fargate 가 EC2 온디맨드보다 &lt;span&gt;대략 40% 조금 넘게&lt;span&gt; 비용이 예상되었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>IT/Tool</category>
      <category>AWS</category>
      <category>EC2</category>
      <category>Fargate</category>
      <category>클라우드</category>
      <category>파게이트</category>
      <author>캥거루</author>
      <guid isPermaLink="true">https://calgary.tistory.com/68</guid>
      <comments>https://calgary.tistory.com/68#entry68comment</comments>
      <pubDate>Tue, 20 Dec 2022 21:02:25 +0900</pubDate>
    </item>
  </channel>
</rss>