<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Hello World</title>
    <link>https://coding-deer.tistory.com/</link>
    <description>삽질기록 아카이브</description>
    <language>ko</language>
    <pubDate>Sat, 30 May 2026 06:14:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JeongHyeongKim</managingEditor>
    <image>
      <title>Hello World</title>
      <url>https://tistory1.daumcdn.net/tistory/2933917/attach/cf5f397be61e4a2d806608e58c451e5f</url>
      <link>https://coding-deer.tistory.com</link>
    </image>
    <item>
      <title>Kafka Message Flow 살펴보기 [Consumer 편]</title>
      <link>https://coding-deer.tistory.com/57</link>
      <description>&lt;h1 id=&quot;intro&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;Intro&lt;/h1&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서는 Producer에서 메시지를 발송하였을 때, message가 어떻게 처리되고 적재되는지 알아보았습니다. 이번 글에서는 적재된 메시지를 다시 꺼내어 처리할 때 consumer 동작 방식과 message(record) flow를 이해하는 것이 목표입니다.&lt;/p&gt;
&lt;h1 id=&quot;consumer-구조&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;Consumer 구조&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MQX9t/btsH2mNB4AY/dvZwEsvDQpw9kKSTQHzlQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MQX9t/btsH2mNB4AY/dvZwEsvDQpw9kKSTQHzlQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MQX9t/btsH2mNB4AY/dvZwEsvDQpw9kKSTQHzlQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMQX9t%2FbtsH2mNB4AY%2FdvZwEsvDQpw9kKSTQHzlQ1%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;1024&quot; height=&quot;392&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;Consumer는 크게 fetcher와 coordinator로 구성되어 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;fetcher&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Fetcher&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;poll이 실행될 때, 적절한 크기의 record를 client에 return 하기 위해 Kafka Cluster로부터 record를 요청하고, client가 받기 직전에 메모리에 미리 저장하는 역할을 합니다.&lt;/p&gt;
&lt;h2 id=&quot;fetcher-성능에-영향을-주는-configuration&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Fetcher 성능에 영향을 주는 Configuration&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;fetch.min.byte ~ fetch.max.byte : broker에서 가져올 최소/최대 데이터 크기를 결정합니다. max.partition.fetch.bytes : 하나의 partition에서 가져올 데이터의 최대 크기를 결정합니다. fetch.max.wait.ms : fetch.min.byte에 도달하기까지 기다릴 수 있는 시간을 의미합니다. 이 시간이 지나도 min.byte에 도달하지 못하면 도달하지 못한 데이터 크기를 보냅니다.&lt;/p&gt;
&lt;h2 id=&quot;coordinator&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;coordinator&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Kafka Cluster 내부의 coordinator(zookeeper, kraft)와 통신하여 어떻게 데이터를 consume할지, offset commit, consumer group, heartbeat 기능 등을 수행합니다.&lt;/p&gt;
&lt;h2 id=&quot;poll-configuration&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;poll configuration&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 poll 할 시, 아래와 같은 설정값에 의해 동작이 달라질 수 있습니다. max.poll.records(default : 500)에 의해 fetcher로부터 가져 올 record 수가 정해진다. max.poll.interval.ms : poll 요청을 받은 메시지를 처리할 때 까지 최대 기다릴 수 있는 시간. 이 시간이 초과한다면, 해당 consumer는 rebalancing 될 때 consumer group에서 제외대상이 된다.&lt;/p&gt;
&lt;h1 id=&quot;consumer-partition-assignor&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;Consumer Partition Assignor&lt;/h1&gt;
&lt;h2 id=&quot;range-assignor&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Range Assignor&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRcQ2Y/btsH36QfFLm/LraWVceYfEW8BnYfAoob5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRcQ2Y/btsH36QfFLm/LraWVceYfEW8BnYfAoob5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRcQ2Y/btsH36QfFLm/LraWVceYfEW8BnYfAoob5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRcQ2Y%2FbtsH36QfFLm%2FLraWVceYfEW8BnYfAoob5k%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;571&quot; height=&quot;651&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;이 assignor는 default 전략으로 채택되어 있다. 이 전략은 coordinator에 의해 할당된 member id를 기반으로 모든 consumer를 사전식으로 배치하고, topic partition을 이에 해당하는 순서의 숫자 순서로 매핑합니다. producer 측에서 키를 기반으로 partitioning 하였을 때, 같은 알고리즘으로 같은 키를 받았다면 같은 번호의 partition에 저장됩니다. 따라서 이 전략은 같은 키에 대한 일괄처리에 조금 더 나은 성능을 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tjGhK/btsH3xgeOgf/TBKcCKBcMxut3tkVQgKFBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tjGhK/btsH3xgeOgf/TBKcCKBcMxut3tkVQgKFBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tjGhK/btsH3xgeOgf/TBKcCKBcMxut3tkVQgKFBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtjGhK%2FbtsH3xgeOgf%2FTBKcCKBcMxut3tkVQgKFBk%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;571&quot; height=&quot;651&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;그러나 키의 개수가 consume의 개수보다 적게 구성되어 있으면 위 모식도와 같이 유휴상태의 consumer가 생길 수 있기 때문에 consumer group 설계에 유의해야합니다.&lt;/p&gt;
&lt;h2 id=&quot;round-robin-assignor-sticky-assignor&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Round Robin Assignor, Sticky Assignor&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NmFEq/btsH2EtHmfY/RJdgDrjN8iTStBYfiKt9sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NmFEq/btsH2EtHmfY/RJdgDrjN8iTStBYfiKt9sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NmFEq/btsH2EtHmfY/RJdgDrjN8iTStBYfiKt9sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNmFEq%2FbtsH2EtHmfY%2FRJdgDrjN8iTStBYfiKt9sk%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;571&quot; height=&quot;591&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;위 2가지의 assignor는 consumer에 대한 partition 분배를 최대한 균등하게 하는 기법입니다. 단순히 consumer에 대한 partition 할당을 균등하게 하는 것이기 떄문에, partitions/consumers가 정확히 나누어 떨어지지 않는 경우, partition 할당을 더 많이 받는 consumer가 생길 수 있으므로 consumer group 설계시 유의해야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 두 assignor는 consumer가 문제가 생겼을 시에 대한 대처법에서 차이가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #2e353f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;round robin : consumer가 문제가 생기면 다시 모두 나열하여 모든 partition - consumer 관계를 재정의하는 방식으로 rebalancing 처리됩니다.&lt;/li&gt;
&lt;li&gt;sticky : 기존 연결은 유지를 하고, reassign이 필요한 connection만 rebalancing을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;마치며&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;직접 쓴 글 Link : &lt;a href=&quot;https://onepredict.github.io/kafka-message-flow-consumer/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://onepredict.github.io/kafka-message-flow-consumer/&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/57</guid>
      <comments>https://coding-deer.tistory.com/57#entry57comment</comments>
      <pubDate>Tue, 18 Jun 2024 10:49:41 +0900</pubDate>
    </item>
    <item>
      <title>Kafka Message Flow 살펴보기 [Producer 편]</title>
      <link>https://coding-deer.tistory.com/56</link>
      <description>&lt;h1 id=&quot;producer-구성요소-및-동작-방식&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;Producer 구성요소 및 동작 방식&lt;/h1&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 구성요소 및 성능에 영향을 미치는 configuration은 아랫글을 참조합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CxJbq/btsH1NdJ5Hd/vKi26CRVuDU6qgguD8T7w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CxJbq/btsH1NdJ5Hd/vKi26CRVuDU6qgguD8T7w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CxJbq/btsH1NdJ5Hd/vKi26CRVuDU6qgguD8T7w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCxJbq%2FbtsH1NdJ5Hd%2FvKi26CRVuDU6qgguD8T7w1%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;1024&quot; height=&quot;412&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;accumulator&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Accumulator&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Kafka Cluster로 전송할 kafka record를 임시로 memory에 담고 있습니다. 담을 때 적용되는 주요 kafka producer configuration은 아래와 같습니다.&lt;/p&gt;
&lt;h2 id=&quot;network-thread-io_loop&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Network Thread (io_loop)&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Accumulator에 쌓인 record를 Broker로 전송하는 역할을 합니다.&lt;/p&gt;
&lt;h2 id=&quot;producer-성능에-직접적으로-영향을-주는-configuration&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Producer 성능에 직접적으로 영향을 주는 configuration&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #2e353f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;buffer.memory : kafka producer가 메시지를 보내기 전 메모리에 보관하는 전체 메시지 용량을 의미합니다. (Default : 32MB)&lt;/li&gt;
&lt;li&gt;max.request.size : 단일 요청으로 보낼 수 있는 최대 메시지의 크기를 의미합니다. (default : 1MB)&lt;/li&gt;
&lt;li&gt;batch.size : 단일 배치 요청으로 단일 파티션에 요청을 보낼 수 있는 메시지들의 크기의 최대 합을 의미합니다.&lt;/li&gt;
&lt;li&gt;linger.ms : producer가 batch.size에 도달하기까지 기다릴 수 있는 시간입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;하나의-request가-만들어지는-절차&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;하나의 request가 만들어지는 절차&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #2e353f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;message가 발행된다.&lt;/li&gt;
&lt;li&gt;message들의 합이 batch.size가 될 떄 까지 기다린다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;linger.ms보다 먼저 batch.size를 넘어 설 경우, batch.size 크기로 batch를 묶는다.&lt;/li&gt;
&lt;li&gt;linger.ms에 먼저 도달할 경우, 도달 전까지의 데이터만 batch 단위로 묶는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;max.request.size보다 message 크기가 작은 경우만 전송이 완료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;producer-partitioning에-따른-데이터-적재-방식&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;Producer Partitioning에 따른 데이터 적재 방식&lt;/h1&gt;
&lt;h2 id=&quot;partitioning-key가-있을-때&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Partitioning key가 있을 때&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;key가 있을 경우, 키를 murmur2 알고리즘을 이용하여 해쉬화 하여 partition 번호로 변환합니다. 이 번호를 기준으로 각 메시지는 파티션에 적재가 됩니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1021&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqUkZ5/btsH1jDY0BX/DLSTll3ev3flCWhucd3ZO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqUkZ5/btsH1jDY0BX/DLSTll3ev3flCWhucd3ZO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqUkZ5/btsH1jDY0BX/DLSTll3ev3flCWhucd3ZO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqUkZ5%2FbtsH1jDY0BX%2FDLSTll3ev3flCWhucd3ZO0%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;1021&quot; height=&quot;431&quot; data-origin-width=&quot;1021&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;partitioning-key가-없으며-kafka-version이-24-미만일-경우&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Partitioning key가 없으며, kafka version이 2.4 미만일 경우&lt;/h2&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 경우, 전략이 명시되지 않으면 round robin partitioning 전략이 자동으로 채택됩니다. 따라서 순차적으로 파티션 순서대로 돌아가면서 메시지가 적재됩니다. 중간에 busy한 partition을 만나게 되면 해당 partition은 건너뛸 수 있습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1021&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ1ubr/btsH3ye71Vd/7cAKk0mp7onkR6lD6yJdD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ1ubr/btsH3ye71Vd/7cAKk0mp7onkR6lD6yJdD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ1ubr/btsH3ye71Vd/7cAKk0mp7onkR6lD6yJdD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ1ubr%2FbtsH3ye71Vd%2F7cAKk0mp7onkR6lD6yJdD1%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;1021&quot; height=&quot;431&quot; data-origin-width=&quot;1021&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;partitioning-key가-없으며-kafka-version이-24-이상인-경우&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Partitioning key가 없으며, kafka version이 2.4 이상인 경우&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmLWxL/btsH2qvESE3/ZbSXxmmqwgSazOI714Es10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmLWxL/btsH2qvESE3/ZbSXxmmqwgSazOI714Es10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmLWxL/btsH2qvESE3/ZbSXxmmqwgSazOI714Es10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmLWxL%2FbtsH2qvESE3%2FZbSXxmmqwgSazOI714Es10%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;1001&quot; height=&quot;462&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;2.4 미만의 버전보다 배치 프로세싱에 성능 개선을 둔 방식입니다. 위 producer configuration에서 설명한 요소 중 batch.size, linger.ms를 이용하여 하나의 파티션에 최대한 같은 단위로 묶어 하나의 파티션에 적재하는 방법입니다. 이 방법은 sticky partitioning이라고 불립니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #2e353f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;linger.ms에 도달하기 전 batch.size를 만족시키는 모든 message들은 같은 파티션에 적재됩니다.&lt;/li&gt;
&lt;li&gt;batch.size보다 message들의 크기가 작지만, linger.ms를 넘어서기 직전까지의 모든 message들은 같은 파티션에 적재가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;마치며&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;마치며&lt;/h1&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Kafka producer에서의 주요 설정 값들에 의해서 어떤 방식으로 데이터가 broker에 흘러가고 적재되는지에 대해 알아보았습니다. 순서보장이 필요하거나 특정 단위로 처리해야 할 상황에는 sticky partitioning 전략을 통해 준 실시간 처리를 구성할 수 있으며, 순서나 배치 처리가 필요 없는 경우에는 단순 round robin을 채택하여도 무방 할 것으로 보입니다.&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #2e353f; text-align: start;&quot; 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;직접 쓴 글 link : &lt;a href=&quot;https://onepredict.github.io/kafka-message-flow-producer/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://onepredict.github.io/kafka-message-flow-producer/&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT</category>
      <category>kakfa</category>
      <category>partition</category>
      <category>Producer</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/56</guid>
      <comments>https://coding-deer.tistory.com/56#entry56comment</comments>
      <pubDate>Tue, 18 Jun 2024 10:32:38 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Kafka에서의 에러 핸들링에 대한 생각정리 (2024.02.22)</title>
      <link>https://coding-deer.tistory.com/54</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div data-testid=&quot;printable-wrapper&quot; data-macro-parameters=&quot;{&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-macro-body=&quot;true&quot; data-fabric-macro=&quot;37219f92-9a8a-460e-85df-5cb368447aab&quot;&gt;
&lt;div data-macro-id=&quot;37219f92-9a8a-460e-85df-5cb368447aab&quot; data-macro-name=&quot;toc&quot; data-hasbody=&quot;false&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-cssliststyle=&quot;none&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-서론&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 서론&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-Dead-Queue의-사용&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. Dead Queue의 사용&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-문제가-생긴-offset과-error-type을-기록&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 문제가 생긴 offset과 error type을 기록&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.-offset을-기록-하고,-retention기간이-지나면-데이터를-archiving-한다.&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. offset을 기록 하고, retention기간이 지나면 데이터를 archiving 한다.&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-서론&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;28&quot;&gt;1. 서론&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;현재 백엔드 팀 일부 프로젝트에서는 message를 관리하고 이를 적절한 곳에서 consume하기 위해 kafka를 사용하지만, 이에 대해서 exception handling이 잘 되고 있지 않으며 각 프로젝트 별로도 다르게 설계 중인 것으로 파악이 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;181&quot; data-ke-size=&quot;size16&quot;&gt;따라서 kafka exception handling 어떻게 하면 잘 했다고 소문이 날지 생각해보게 되었으며, 내가 고민한 방법들을 적어보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;337&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Dead-Queue의-사용&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;339&quot;&gt;2. Dead Queue의 사용&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;358&quot; data-ke-size=&quot;size16&quot;&gt;처리가 되지 않은 message에 대해서 나중에 메시지를 처리할 수 있도록 다른 별도의 topic에 쌓아놓는 기법이다. 이 기법을 사용 할 경우 아래와 같은 장단점이 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 토픽별로 메시지를 보관 할 수 있으며, 작업시 자연스럽게 dead queue에 연결하여 하나씩 메시지를 까볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[개인적인 생각] kafka는 데이터의 위치를 알려주는 offset이라는 강력한 기능을 제공하는데, 이를 적절하게 활용하지 못하는 느낌이다.&lt;/li&gt;
&lt;li&gt;topic이 많아지면 그만큼의 배수의 데이터가 중복으로 kafka에 생성되기 떄문에 스토리지 관점에서 비효율적이라고 생각된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;708&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;710&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-문제가-생긴-offset과-error-type을-기록&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;712&quot;&gt;3. 문제가 생긴 offset과 error type을 기록&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;kafka의 장점을 적극적으로 사용하여 offset기반으로 모든 데이터를 참조할 수 있다. 이 기법을 사용 할 경우 아래와 같은 장단점이 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kafka의 offset을 활용해 문제가 되는 데이터만 받아 볼 수 있다.&lt;/li&gt;
&lt;li&gt;데이터의 복제본을 만들지 않아도 되므로 dataio를 최소화 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;retention 기간이 지나버리면, kafka에 있는 데이터가 삭제되므로 디버깅을 할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;999&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1001&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-offset을-기록-하고,-retention기간이-지나면-데이터를-archiving-한다.&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1003&quot;&gt;4. offset을 기록 하고, retention기간이 지나면 데이터를 archiving 한다.&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1058&quot; data-ke-size=&quot;size16&quot;&gt;위 2가지 방법을 하나로 합친 방법으로 자세한 설명은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;retention기간 동안에는 데이터베이스에 기록 된 offset과 exception 타입을 보고 문제를 처리한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;database connection 문제 등 배치로 해결 할 수 있는 문제의 경우, 일괄처리 한다.&lt;/li&gt;
&lt;li&gt;배치로 처리 할 수 없는 경우는 개발자가 직접 해당 데이터를 디버깅하여 해결한 다음, 다시 처리할 수 있도록 해당 topic에 produce한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;retention기간이 지났을 경우, 배치를 이용하여 database에 기록된 offset에 해당하는 데이터를 압축하여 minio등 storage에 저장한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추후 이 문제에 대해 개발자들이 언제든 해결할 수 있도록 조치가 가능하다.&lt;/li&gt;
&lt;li&gt;중복을 kafka에 데이터를 저장하지 않기 때문에 안정적인 kafka 운영을 기대할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT</category>
      <category>deadqueue</category>
      <category>Error Handling</category>
      <category>Kafka</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/54</guid>
      <comments>https://coding-deer.tistory.com/54#entry54comment</comments>
      <pubDate>Tue, 2 Apr 2024 19:42:56 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] Generic을 활용한  class constructor 간소화하기</title>
      <link>https://coding-deer.tistory.com/53</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;340f55df-a79c-4531-9ff8-de0d7dd55c7c&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;6&amp;quot;},&amp;quot;include&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},&amp;quot;outline&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;false&amp;quot;},&amp;quot;_parentId&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;2390556734&amp;quot;},&amp;quot;indent&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;},&amp;quot;exclude&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},&amp;quot;type&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;list&amp;quot;},&amp;quot;class&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},&amp;quot;printable&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;false&amp;quot;}}&quot; data-testid=&quot;non-printable-wrapper&quot;&gt;
&lt;div data-numberedoutline=&quot;false&quot; data-cssliststyle=&quot;none&quot; data-csslistindent=&quot;&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;340f55df-a79c-4531-9ff8-de0d7dd55c7c&quot; data-structure=&quot;list&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-서론&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 서론&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-이번-문제의-key는-왜-Generic인가?&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 이번 문제의 key는 왜 Generic인가?&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-Generic&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. Generic&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.-Generic-Type-(Utility-Type)&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. Generic Type (Utility Type)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.1.-Partial&amp;lt;T&amp;gt;&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.1. Partial&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.2.-Pick&amp;lt;T,-Keys&amp;gt;&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2. Pick&amp;lt;T, Keys&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#5.-최종-적용-코드&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5. 최종 적용 코드&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h1 id=&quot;1.-서론&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot;&gt;1. 서론&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;class 정의시, constructor를 정의하곤 했다. 일반적인 constructor를 생성시, 아래와 같은 형태를 띈다&lt;/p&gt;
&lt;pre id=&quot;code_1685101904089&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export class ModbusConnection {
  constructor(modbusClient: ModbusRTU, deviceId: any, ipAddress: string, modelId: any, nestedDeviceIdList: Device[]) {
    this.modbusClient = modbusClient;
    this.deviceId = deviceId;
    this.ipAddress = ipAddress;
    this.modelId = modelId;
    this.nestedDeviceIdList = nestedDeviceIdList;
  }

  modbusClient: ModbusRTU;

  deviceId: any;

  ipAddress: string;

  modelId: any;

  nestedDeviceIdList: Device[];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;558&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;생성자로 인자를 받아 그대로 객체를 생성하는 것임에도 불구하고 불필요하게 코드가 자꾸 사용되는것 같다는 생각이 들었다. 이를 어떻게 하면 코드를 좀 더 간결하게 줄일 수 있을 까 생각하게 되었으며 이를 해결하기 위해 typescript의 generic을 사용하여 해보기로 하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;718&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-이번-문제의-key는-왜-Generic인가?&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;720&quot;&gt;2. 이번 문제의 key는 왜 Generic인가?&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;한가지 타입이 아닌 여러가지 타입을 기반으로 동작하는 function을 설계할 때 주로 사용한다. 이와 비슷한 역할을 하는 any 타입으로 대체할 수 있었지만, 나는 아래와 같은 이유로 generic을 사용하게 되었다. generic 뜻을 찾아보니 '데이터 타입을 일반화 시키는 것'이라고 한다. generic을 이용하면 아래와 같이 개선할 수 있다고 생각이 들었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;any를 사용하면 어떠한 타입이라도 모두 받을 수 있지만, 어떤 타입이 들어갔는지에 대해 알 수 없다.&lt;/li&gt;
&lt;li&gt;any는 타입 체크를 하지 않기 때문에 typescript의 최고 장점인 컴파일에서 오류를 잡을 수 없다는 단점이 존재한다.&lt;/li&gt;
&lt;li&gt;그러나 generic은 개발자로부터 이러한 타입을 쓸 것이다 라는 것을 받기 때문에 컴파일 단계에서 오류도 잡을 수 있고 유동적으로 타입을 변경할 수 있다고 생각했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;3.-Generic&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1189&quot;&gt;3. Generic&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1201&quot; data-ke-size=&quot;size16&quot;&gt;위에서 언급했지만, 한 번 더 정리하면 &amp;lsquo;데이터 타입을 일반화 시키는 것&amp;rsquo;을 의미한다. 기본적인 Generic을 Typescript화 시키면, 아래와 같이 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1201&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1685101924747&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function identity&amp;lt;T&amp;gt;(argument: T): T {
  return arg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1355&quot; data-ke-size=&quot;size16&quot;&gt;T는 Type을 지칭하는 약어로, 통상적인 약속으로 T라고 명명한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1355&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1395&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1397&quot; data-ke-size=&quot;size16&quot;&gt;generic을 이용할 시, extends를 이용하여 특정 object가 가진 property들에 한정시킬 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1685101942101&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Item {
  name: string;
  price: number;
  stock: number;
}


function identity&amp;lt;T extends keyof Item&amp;gt;(argument: T): T {
  return argument;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1618&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-Generic-Type-(Utility-Type)&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1620&quot;&gt;4. Generic Type (Utility Type)&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1652&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;Typescript 공식&quot; href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.typescriptlang.org/docs/handbook/utility-types.html&lt;/a&gt;페이지에 많은 유틸리티 타입이 존재하지만, 이 중 내가 이번에 사용한 type만 서술해본다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1707&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;4.1.-Partial&amp;lt;T&amp;gt;&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1709&quot; data-ke-size=&quot;size26&quot;&gt;4.1. Partial&amp;lt;T&amp;gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1726&quot; data-ke-size=&quot;size16&quot;&gt;입력되는 특정 type의 부분집합을 type으로 구성 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1685102659745&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial&amp;lt;Todo&amp;gt;) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: &quot;organize desk&quot;,
  description: &quot;clear clutter&quot;,
};
 
const todo2 = updateTodo(todo1, {
  description: &quot;throw out trash&quot;,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;4.2.-Pick&amp;lt;T,-Keys&amp;gt;&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2090&quot; data-ke-size=&quot;size26&quot;&gt;4.2. Pick&amp;lt;T, Keys&amp;gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2110&quot; data-ke-size=&quot;size16&quot;&gt;특정 Type의 특정 Property를 이용하여 새로운 타입으로 정의할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1685102653151&quot; class=&quot;routeros&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick&amp;lt;Todo, &quot;title&quot; | &quot;completed&quot;&amp;gt;;
 
const todo: TodoPreview = {
  title: &quot;Clean room&quot;,
  completed: false,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2381&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;5.-최종-적용-코드&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2383&quot;&gt;5. 최종 적용 코드&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2396&quot; data-ke-size=&quot;size16&quot;&gt;Class를 선언하게 되면 this를 사용할 수 있기 때문에, Object.assign을 이용하여 한번에 입력 할 수 있을 것이라고 생각했으며, 간혹 어쩔 수 없이 any를 사용하는 경우, function이 들어가는 case를 막기 위해 아래와 같은 코드를 생각하게 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1685102685822&quot; class=&quot;elm&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type ClassProperties&amp;lt;T&amp;gt; = Pick&amp;lt;T, { [P in keyof T]: T[P] extends Function ? never : P }[keyof T]&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2651&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2653&quot; data-ke-size=&quot;size16&quot;&gt;위 코드를 설명하자면 아래왜 같이 설명 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1685102627940&quot; class=&quot;crmsh&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[입력된 type의 Property] : Type의 Property가 Function type을 상속 받으면 ? 내보내지 않는다 : type&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2766&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2766&quot; data-ke-size=&quot;size16&quot;&gt;즉, 입력된 type의 property 중 function type을 제외한 모든 property가 pick되는 구조이다. 이를 이용하여 위 코드를 아래와 같이 적&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2766&quot; data-ke-size=&quot;size16&quot;&gt;용 할 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1685102643833&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export class ModbusConnection {
  constructor(data: ClassProperties&amp;lt;ModbusConnection&amp;gt;) {
    Object.assign(this, data);
  }

  modbusClient: ModbusRTU;

  deviceId: any;

  ipAddress: string;

  modelId: any;

  nestedDeviceIdList: Device[];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3114&quot; data-ke-size=&quot;size16&quot;&gt;constructor에서 약간의 연산이 들어가면 위 코드 중 constructor 쪽이 조금 더 길어지겠지만&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;, constructor 인자로 받는 부분과 &lt;/span&gt;this.{property}=arg&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;가 없어지니 코드가 간결해졌으며 보기 좋아진거같다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>IT/TypeScript</category>
      <category>generic</category>
      <category>Generic Type</category>
      <category>TypeScript</category>
      <category>utility type</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/53</guid>
      <comments>https://coding-deer.tistory.com/53#entry53comment</comments>
      <pubDate>Fri, 26 May 2023 20:59:11 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] ElasticSearch 설계 (2023.03.28)</title>
      <link>https://coding-deer.tistory.com/52</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;641910fdc4eedea1544c9d892c435f51&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;641910fdc4eedea1544c9d892c435f51&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-설계에-반영되어야-하는-내용&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 설계에 반영되어야 하는 내용&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-Shard-Index-설계&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. Shard 및 Index 설계&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-Indice-(ElasticSearch-Data-Modeling)&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. Indice (ElasticSearch Data Modeling)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.-데이터-시각화&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 데이터 시각화&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.--설계에-반영되어야-하는-내용&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;26&quot;&gt;1. 설계에 반영되어야 하는 내용&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&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 style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;181&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Shard-및-Index-설계&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;183&quot;&gt;2. Shard 및 Index 설계&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;204&quot; data-ke-size=&quot;size16&quot;&gt;설계시 생각해야 할 조건을 나열하면 아래와 같다.\&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이종간의 솔루션의 로그에 대해서 분석을 할 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.elastic.co/kr/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;ElasticSearch에서 권장하는 수준&lt;/a&gt;의 Shard 크기를 유지할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;327&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 조건인 수집된 데이터에 대해 통계 및 분석을 할 수 있어야 한다에 대해 고민을 먼저해보자. elasticsearch에는 인덱스에 대해 검색시, wildcard(*)를 이용하여 복수개의 인덱스에 대해서도 색인 및 검색이 가능하다. 따라서 이를 사용하기 위해 인덱스 패턴도 계층이 적용되어야 한다는 것을 알게 되었다. 따라서 설계된 elasticsearch index pattern은 아래와 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;554&quot; data-ke-size=&quot;size16&quot;&gt;{{로그 성격 ex.logs, metrics, }}-{{솔루션 이름}}-{{현장}}-{{YYYYMMDDHH}}&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;617&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;619&quot; data-ke-size=&quot;size16&quot;&gt;위 패턴의 인덱스를 활용하면 여러 계층 및 시간대별로 인덱스가 분리되면서 검색 및 색인을 더 빠르게 할 수 있으며 ElasticSearch에서 권장하는 수준의 shard 크기까지 맞출 수 있을 것으로 보인다. 샤드 용량 계산식은 아래와 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;756&quot; data-ke-size=&quot;size16&quot;&gt;140 byte(1개의 json object 평균 크기) * 60(sec) * 60(min) * 24(hour) * 50(device) * 50(data) = 30.24 GB&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;855&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;857&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-Indice-(ElasticSearch-Data-Modeling)&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;859&quot;&gt;3. Indice (ElasticSearch Data Modeling)&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;900&quot; data-ke-size=&quot;size16&quot;&gt;indice 설정을 어떻게 하냐에 따라 데이터 분석에 대한 내용이 달라지기 때문에 이 부분에 대해 조심해야한다. 해당 내용에 대해서는 아래 문서를 참조한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;989&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-inline-card=&quot;true&quot; data-card-url=&quot;https://crocusenergy.atlassian.net/wiki/spaces/CROC/pages/2358935617&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span data-testid=&quot;hover-card-trigger-wrapper&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://crocusenergy.atlassian.net/wiki/spaces/CROC/pages/2358935617&quot; data-testid=&quot;inline-card-resolved-view&quot;&gt;&lt;span data-testid=&quot;inline-card-icon-and-title&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://hello-world.kr/51&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hello-world.kr/51&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;993&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;995&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;997&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-데이터-시각화&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;999&quot;&gt;4. 데이터 시각화&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1011&quot; data-ke-size=&quot;size16&quot;&gt;처음에 Kibana Dashboard 원형 자체를 가져오려고 하였으나, 그렇게 되면 아래와 같은 단점이 존재하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리버스 프록시를 이용하여 kibana 대시보드를 그대로 가지고오기
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리버스 프록시 내부에 kibana 계정 정보가 있기 때문에 authentication 파편화 예상&lt;/li&gt;
&lt;li&gt;가지고 오더라도 대시보드 컴포넌트를 선택적으로 들고올 수 없다.&lt;/li&gt;
&lt;li&gt;대시보드를 커스텀 할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1241&quot; data-ke-size=&quot;size16&quot;&gt;이러한 이유로 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/query-dsl.html&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;elasticsearch dsl query&lt;/a&gt;를 이용하여 직접 대시보드에 필요한 데이터를 전송 하는 것으로 전략을 대체하였다.&lt;/p&gt;</description>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/52</guid>
      <comments>https://coding-deer.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 22 May 2023 15:51:32 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] ElasticSearch Indice에 대해 실수한 기록(2023.03.16)</title>
      <link>https://coding-deer.tistory.com/51</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;ac05c0b4-c9be-4e7f-a515-ee7007ed85f0&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;ac05c0b4-c9be-4e7f-a515-ee7007ed85f0&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-하고자-하였던-내용&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 하고자 하였던 내용&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-문제-분석&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 문제 분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-문제-해결&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 문제 해결&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.-결론&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 결론&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-하고자-하였던-내용&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;28&quot;&gt;1. 하고자 하였던 내용&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;ElasticSearch에 test라는 index를 생성 후, kafka consumer eachBatch 기능을 이용하여 주기적으로 document를 insert하고 있다. 이 데이터들을 이용하여 아래와 같은 효과를 내고자 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실시간 데이터 모니터링&lt;/li&gt;
&lt;li&gt;데이터 통계&lt;/li&gt;
&lt;li&gt;ML 분석 연계 (연구가 더 필요함)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;226&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;228&quot; data-ke-size=&quot;size16&quot;&gt;그러나 데이터를 insert 하여 모니터링 그래프를 그리는데 아래와 같은 문제가 생겼다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;279&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;281&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-문제-분석&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;283&quot;&gt;2. 문제 분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;293&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 데이터베이스의 경우, 코드 상에서 schema를 선언하거나 사전에 schema를 fix하고 작업하기 때문에 data type에 대한 에러는 코드 miss issue가 아니면 거의 일어나지 않는다. 그러나 elasticsearch 초기 설정시, index는 생성하지 않았으나, 어떤 데이터가 들어오고 이를 어떻게 핸들링하는지에 대해 명시를 하지 않았기 때문에 자동으로 이에 대한 설정이 생성되었다. 자동으로 생성된 결과는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deviceId : text, keyword&lt;/li&gt;
&lt;li&gt;name : text, keyword&lt;/li&gt;
&lt;li&gt;unit : text, keyword&lt;/li&gt;
&lt;li&gt;value : float&lt;/li&gt;
&lt;li&gt;createdAt: text, keyword&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;665&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;667&quot; data-ke-size=&quot;size16&quot;&gt;자동으로 생성된 부분들에 대해 문제점을 지적하면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;text type의 경우, 형태소 분석이 가능한 type이기 때문에 다른 타입보다 더 큰 자원을 소모하게 된다.&lt;/li&gt;
&lt;li&gt;불필요하게 2개의 type이 설정되어 있다.&lt;/li&gt;
&lt;li&gt;색인이 필요없는 property도 색인이 되어있다. (unit property)&lt;/li&gt;
&lt;li&gt;createdAt의 경우 의도 한 data type은 Date였으나, 불필요하게 text / keyword type이 세팅되어 의도와는 다른 설계가 적용되어있었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특히 이 문제로 인해 tsvb(시계열 데이터 모니터링?)기능을 위해 필요한 최소 조건이 충족되지 않아 문제가 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1013&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1015&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1017&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-문제-해결&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1019&quot;&gt;3. 문제 해결&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1029&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 인덱스를 삭제하고 indice를 의도대로 설정해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1684737808058&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET https://acelogrid.es.asia-northeast3.gcp.elastic-cloud.com/test

BODY
{
    &quot;test&quot;: {
        &quot;aliases&quot;: {},
        &quot;mappings&quot;: {
            &quot;properties&quot;: {
                &quot;createdAt&quot;: {
                    &quot;type&quot;: &quot;date&quot;
                },
                &quot;deviceId&quot;: {
                    &quot;type&quot;: &quot;keyword&quot;
                },
                &quot;name&quot;: {
                    &quot;type&quot;: &quot;keyword&quot;
                },
                &quot;unit&quot;: {
                    &quot;type&quot;: &quot;object&quot;,
                    &quot;enabled&quot;: false
                },
                &quot;value&quot;: {
                    &quot;type&quot;: &quot;float&quot;
                }
            }
        },
        &quot;settings&quot;: {
            &quot;index&quot;: {
                &quot;routing&quot;: {
                    &quot;allocation&quot;: {
                        &quot;include&quot;: {
                            &quot;_tier_preference&quot;: &quot;data_content&quot;
                        }
                    }
                },
                &quot;number_of_shards&quot;: &quot;1&quot;,
                &quot;blocks&quot;: {
                    &quot;read_only_allow_delete&quot;: &quot;false&quot;
                },
                &quot;provided_name&quot;: &quot;test&quot;,
                &quot;creation_date&quot;: &quot;1678934584715&quot;,
                &quot;number_of_replicas&quot;: &quot;1&quot;,
                &quot;uuid&quot;: &quot;lDFHfDEHRAachy1NQ1QYbw&quot;,
                &quot;version&quot;: {
                    &quot;created&quot;: &quot;8060299&quot;
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2443&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2445&quot; data-ke-size=&quot;size16&quot;&gt;그 결과, 시간축이 제대로 인식이 되면서 kibana dashboard에서 원하는 정보를 뽑아낼 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1849&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T9zdl/btsgJZZJbsf/cZHjQ6MlPPzi2TLHJ5aMMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T9zdl/btsgJZZJbsf/cZHjQ6MlPPzi2TLHJ5aMMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T9zdl/btsgJZZJbsf/cZHjQ6MlPPzi2TLHJ5aMMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT9zdl%2FbtsgJZZJbsf%2FcZHjQ6MlPPzi2TLHJ5aMMk%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;1849&quot; height=&quot;614&quot; data-origin-width=&quot;1849&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2358935617&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1158&quot; data-height=&quot;385&quot; data-id=&quot;e0459814-db51-46a0-bf34-07f717ba4cdf&quot; data-collection=&quot;contentId-2358935617&quot; data-file-name=&quot;image-20230316-101333.png&quot; data-file-size=&quot;148647&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230316-101333.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #000000; text-align: center;&quot; data-media-caption=&quot;true&quot; data-testid=&quot;media-caption&quot; data-renderer-start-pos=&quot;2512&quot;&gt;사내 계측기의 전압 평균값을 실시간 모니터링 한 결과&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2544&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2546&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2548&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2550&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-결론&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2552&quot;&gt;4. 결론&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2559&quot; data-ke-size=&quot;size16&quot;&gt;elasticsearch에 대해 자세히 모르고 일반 mongodb처럼 사용을 지양해야해야 한다는 점을 알게되었다. elasticsearch는 nosql과 유사한 기능은 schemaless기능을 제공한다. 이를 이용하면 여러가지 형태의 데이터를 하나의 인덱스로 구성할 수 있지만 데이터의 타입별로 어떤 분석이 가능하고 얼만큼의 자원이 들어가는지에 대한 부분이 다르기 때문에 이에 대한 optimizing을 적용하여 인덱스가 만들어 지기전 아래와 같은 부분들이 고려가 되어야 할 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&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;</description>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/51</guid>
      <comments>https://coding-deer.tistory.com/51#entry51comment</comments>
      <pubDate>Mon, 22 May 2023 15:47:06 +0900</pubDate>
    </item>
    <item>
      <title>[Apache Kafka] Kafka 튜닝 기록 아카이빙 (2023.05.01)</title>
      <link>https://coding-deer.tistory.com/50</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Content&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Content&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;a7b129677b75417c25756386d55b8914&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;a7b129677b75417c25756386d55b8914&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Content&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Content&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-Kafka-Batch-data-produce-도중-발생-한-문제&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. Kafka Batch data produce 도중 발생 한 문제&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-문제를-해결하기-위해-튜닝-해야-할-파라미터에-대한-추측&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 문제를 해결하기 위해 튜닝 해야 할 파라미터에 대한 추측&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-조치-사항&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 조치 사항&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-Kafka-Batch-data-produce-도중-발생-한-문제&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;27&quot;&gt;1. Kafka Batch data produce 도중 발생 한 문제&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;pre id=&quot;code_1684737505828&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{&quot;level&quot;:&quot;ERROR&quot;,&quot;timestamp&quot;:&quot;2023-04-24T08:40:10.368Z&quot;,&quot;logger&quot;:&quot;kafkajs&quot;,&quot;message&quot;:&quot;[Connection] Response Produce(key: 0, version: 7)&quot;,&quot;broker&quot;:&quot;211.49.126.109:9092&quot;,&quot;clientId&quot;:&quot;rmkr&quot;,&quot;error&quot;:&quot;The request included a message larger than the max message size the server will accept&quot;,&quot;correlationId&quot;:9,&quot;size&quot;:77}
{&quot;level&quot;:&quot;ERROR&quot;,&quot;timestamp&quot;:&quot;2023-04-24T08:40:10.370Z&quot;,&quot;logger&quot;:&quot;kafkajs&quot;,&quot;message&quot;:&quot;[Producer] The request included a message larger than the max message size the server will accept&quot;,&quot;retryCount&quot;:0,&quot;retryTime&quot;:287}
KafkaJSProtocolError: The request included a message larger than the max message size the server will accept
     at createErrorFromCode (/home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/protocol/error.js:581:10)
     at Object.parse (/home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/protocol/requests/produce/v3/response.js:45:11)
     at Connection.send (/home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/network/connection.js:433:35)
     at processTicksAndRejections (internal/process/task_queues.js:95:5)
     at async Broker.[private:Broker:sendRequest] (/home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/broker/index.js:904:14)
     at async Broker.produce (/home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/broker/index.js:241:12)
     at async /home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/producer/sendMessages.js:94:24
     at async Promise.all (index 0)
     at async /home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/producer/sendMessages.js:133:9
     at async sendBatch (/home/crocus/project/acelo-grid-local-data-producer/node_modules/kafkajs/src/producer/messageProducer.js:95:12) {
   retriable: false,
   helpUrl: undefined,
   cause: undefined,
   type: 'MESSAGE_TOO_LARGE',
   code: 10
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;67&quot; data-ke-size=&quot;size16&quot;&gt;여러 iot 데이터를 수집하는 도중 아래와 같은 로그가 발생하며 정상적으로 produce 되지 않는 오류가 발생하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;67&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;67&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2072&quot; data-ke-size=&quot;size16&quot;&gt;위 로그를 정리하면 , Kafka Broker에 설정된 최대 메시지 크기보다 더 큰메시지를 받게 된다는 의미이다. 실제로 kafka broker의 해당 topic에 데이터가 들어왔는지 확인한 결과 어떠한 데이터도 들어와 있지 않았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2204&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 batch 및 데이터 전송, 처리 관련 파라미터를 리서치 하다 아래와 같은 프로퍼티를 발견할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2276&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-문제를-해결하기-위해-튜닝-해야-할-파라미터에-대한-추측&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2278&quot;&gt;2. 문제를 해결하기 위해 튜닝 해야 할 파라미터에 대한 추측&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2314&quot; data-ke-size=&quot;size16&quot;&gt;위 문제를 해결하기 위해 문제가 된 파라미터들을 유추하여 나열한 결과 아래와 같은 파라미터를 찾을 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2379&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kafka.apache.org/documentation/#brokerconfigs_message.max.bytes&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;&lt;b&gt;message.max.bytes&lt;/b&gt;&lt;/a&gt;&lt;b&gt; (Broker Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Broker가 허용할 Producer측에서 전송한 하나의 배치를 통해 생산된 메시지의 최대 크기(압축이 적용 되었다면, 압축 후 크기)를 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2503&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2505&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kafka.apache.org/documentation/#brokerconfigs_socket.request.max.bytes&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;&lt;b&gt;socket.request.max.bytes&lt;/b&gt;&lt;/a&gt;&lt;b&gt; (Broker Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;socket 통신으로 kafka broker에 데이터를 전송 할 때, 허용할 최대 byte 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2605&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2607&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kafka.apache.org/documentation/#brokerconfigs_fetch.max.bytes&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;&lt;b&gt;fetch.max.bytes&lt;/b&gt;&lt;/a&gt;&lt;b&gt; (Consumer Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Consumer instance가 한번의 fetch에 가장 많이 들고올 수 있는 데이터의 양&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2699&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2701&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kafka.apache.org/documentation/#consumerconfigs_fetch.min.bytes&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;&lt;b&gt;fetch.min.bytes&lt;/b&gt;&lt;/a&gt;&lt;b&gt; (Consumer Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Consumer Instance가 한번의 fetch에 가장 적게 들고 올 수 있는 데이터의 양&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2794&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2796&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kafka.apache.org/documentation/#consumerconfigs_fetch.max.wait.ms&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;&lt;b&gt;fetch.max.wait.ms&lt;/b&gt;&lt;/a&gt;&lt;b&gt; (Consumer Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch.min.byte보다 반환할 데이터의 양이 적지만 Consume 요청에 응답해야할 때, fetch.min.byte를 충족시키기 위해 기다릴 수 있는 최대의 시간 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2936&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2938&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kafka.apache.org/documentation/#consumerconfigs_fetch.max.wait.ms&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;&lt;b&gt;fetch.max.wait.ms&lt;/b&gt;&lt;/a&gt;&lt;b&gt; (Consumer Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch.min.byte보다 반환할 데이터의 양이 적지만 Consume 요청에 응답해야할 때, fetch.min.byte를 충족시키기 위해 기다릴 수 있는 최대의 시간 값.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3078&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3080&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;maxBytesPerPartition (Consumer Config)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션 별 얼마나 많은 데이터를 들고 올지에 대한 파라미터이며, kafkaJS에만 있는 설정 값으로 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3186&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3188&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-조치-사항&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3190&quot;&gt;3. 조치 사항&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3200&quot; data-ke-size=&quot;size16&quot;&gt;우선적으로, Kafka Broker에서 메시지 크기가 제한되어 이를 늘려주기로 결정하였으며, 현재 사내에서는 kafka가 bitnami 에서 제작한 docker로 운영되고 있었기 때문에, 이에 대한 설정을 docker-compose.yaml에 해주기로 하였으며 문서를 참조하여 아래와 같이 설정하였다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2375647736&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;757&quot; data-height=&quot;78&quot; data-id=&quot;d7473300-3f85-4a7c-9ffe-2f47cef85769&quot; data-collection=&quot;contentId-2375647736&quot; data-file-name=&quot;image-20230426-122650.png&quot; data-file-size=&quot;68546&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230426-122650.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1684737544998&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  zookeeper:
    image: docker.io/bitnami/zookeeper:3.8
    ports:
      - &quot;2181:2181&quot;
    volumes:
      - &quot;zookeeper_data:/bitnami&quot;
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
  kafka:
    image: docker.io/bitnami/kafka:3.2
    ports:
      - &quot;9092:9092&quot;
    volumes:
      - &quot;kafka_data:/bitnami&quot;
    environment:
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_CFG_MESSAGE_MAX_BYTES=10485880
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://211.49.126.109:9092
    depends_on:
      - zookeeper&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3944&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;3944&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 env 설정하는 방법을 찾지 못해 해매다가 bitnami kafka docker document를 찾아보니 명시되지 않은 parameter들은 KAFKA_CFG prefix를 붙이면 된다고 한다. 이를 이용하여 docker-compose.yaml에 KAFKA_CFG_MESSAGE_MAX_BYTES=10485880 를 선언하여 임시로 10mb 까지 늘려주었다. 현장에 디바이스 수가 많아짐에 따라 한번의 배치에 많은 데이터를 담아 produce하게 되니 위와 같은 문제가 발생 한 것으로 보인다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4228&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4230&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 kafkaJS에만 존재하던 값을 튜닝해보기로 하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4267&quot; data-ke-size=&quot;size16&quot;&gt;현재 테스트를 진행 중인 상태이므로, iot data stream을 담당하는 topic의 경우 현재 partition을 하나만 사용하고 있다. 이 때, partition별로 가지고 올 수 있는 데이터를 더 크게 늘리면 한번에 더 많은 사이즈의 데이터를 batch를 통해 elasticsearch로 전달 할 수 있을 것이라 생각했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4454&quot; data-ke-size=&quot;size16&quot;&gt;실제로 이 값을 바꾸어 돌려본 결과, 아래와 같았다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;15&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2375647736&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;456&quot; data-height=&quot;524&quot; data-id=&quot;148c2007-48fd-4195-886b-c555bb51a29d&quot; data-collection=&quot;contentId-2375647736&quot; data-file-name=&quot;image-20230430-174402.png&quot; data-file-size=&quot;55408&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230430-174402.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lJmGq/btsgNXUwhYm/Ae6mSBXr2KJmytVEsqkZg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lJmGq/btsgNXUwhYm/Ae6mSBXr2KJmytVEsqkZg0/img.png&quot; data-alt=&quot;partitionMaxByte가 10mb 일 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lJmGq/btsgNXUwhYm/Ae6mSBXr2KJmytVEsqkZg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlJmGq%2FbtsgNXUwhYm%2FAe6mSBXr2KJmytVEsqkZg0%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;456&quot; height=&quot;524&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;partitionMaxByte가 10mb 일 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oqhS8/btsgYiX354O/nID8IEY9EC79pmAAFRKZmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oqhS8/btsgYiX354O/nID8IEY9EC79pmAAFRKZmk/img.png&quot; data-alt=&quot;partitionMaxByte가 1mb 일 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oqhS8/btsgYiX354O/nID8IEY9EC79pmAAFRKZmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoqhS8%2FbtsgYiX354O%2FnID8IEY9EC79pmAAFRKZmk%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;454&quot; height=&quot;1022&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;partitionMaxByte가 1mb 일 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;partitionMaxByte가 10mb 일 때 : 58초에 19739 byte&lt;/li&gt;
&lt;li&gt;partitionMaxByte가 1mb 일 때 : 19초에 7063 byte&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4589&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4591&quot; data-ke-size=&quot;size16&quot;&gt;계산해보면 거의 비슷한 성능을 내는것 처럼 보이지만, 한번에 많은 데이터를 밀어넣는 10mb로 세팅하였을 때가 elasticsearch bulk query의 성능을 내는데에는 더 좋다고 생각이 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4591&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/50</guid>
      <comments>https://coding-deer.tistory.com/50#entry50comment</comments>
      <pubDate>Mon, 22 May 2023 15:42:02 +0900</pubDate>
    </item>
    <item>
      <title>[Apache Kafka]Kafka Consumer Group에 대한 고찰(2023.03.30)</title>
      <link>https://coding-deer.tistory.com/49</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;0d672606abd1566acf7f3a3e29be75b9&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;0d672606abd1566acf7f3a3e29be75b9&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-Consumer-Group이란-무엇인가&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. Consumer Group이란 무엇인가&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-Consumer-Group이-왜-필요한가&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. Consumer Group이 왜 필요한가&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.1.-Consumer-Failover&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.1. Consumer Failover&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.-다수의-Instance에서의-Offset-관리&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2. 다수의 Instance에서의 Offset 관리&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.1.-독립적인-Consumer로-구성하였을-경우&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2.1. 독립적인 Consumer로 구성하였을 경우&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.2.-같은-Consumer-Group의-Instance로-구성하였을-경우&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2.2. 같은 Consumer Group의 Instance로 구성하였을 경우&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-Consumer-Group과-Partition의-관계&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. Consumer Group과 Partition의 관계&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-Consumer-Group이란-무엇인가&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;28&quot;&gt;1. Consumer Group이란 무엇인가&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;Consumer Group이란, Consumer Instance를 대표하는 그룹이며 아래 그림을 참조한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Consumer (Instance) : Broker로부터 데이터를 pull 받아 처리하는 단일 process.&lt;/li&gt;
&lt;li&gt;Offset : Partition의 데이터 위치를 표시하는 pointer. Consumer는 offset을 이용해 어디까지 데이터가 소비되었는지 알 수 있다.&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;201&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drr62o/btsgFebK0Ca/JAVT6BZdX9PXRdXfZ9yuIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drr62o/btsgFebK0Ca/JAVT6BZdX9PXRdXfZ9yuIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drr62o/btsgFebK0Ca/JAVT6BZdX9PXRdXfZ9yuIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrr62o%2FbtsgFebK0Ca%2FJAVT6BZdX9PXRdXfZ9yuIk%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;201&quot; height=&quot;201&quot; data-origin-width=&quot;201&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;31&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;91&quot; data-height=&quot;91&quot; data-id=&quot;b6f6faae-39dc-46f6-aa74-243ef45f3b96&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230329-095359.png&quot; data-file-size=&quot;8692&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230329-095359.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;280&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;282&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;284&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Consumer-Group이-왜-필요한가&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;286&quot;&gt;2. Consumer Group이 왜 필요한가&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h3 id=&quot;2.1.-Consumer-Failover&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;313&quot; data-ke-size=&quot;size23&quot;&gt;2.1. Consumer Failover&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;337&quot; data-ke-size=&quot;size16&quot;&gt;1번 섹션의 그림을 떠올리면서 아래 그림을 참조해보자.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;65&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;269&quot; data-height=&quot;91&quot; data-id=&quot;0c84031e-4fa3-471c-b3d0-057d3b6ad317&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230329-102301.png&quot; data-file-size=&quot;13808&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230329-102301.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6qc89/btsg1bqne7x/8gmudjEX4KBSf4lRYCji6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6qc89/btsg1bqne7x/8gmudjEX4KBSf4lRYCji6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6qc89/btsg1bqne7x/8gmudjEX4KBSf4lRYCji6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6qc89%2Fbtsg1bqne7x%2F8gmudjEX4KBSf4lRYCji6K%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;591&quot; height=&quot;201&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;65&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;269&quot; data-height=&quot;91&quot; data-id=&quot;1a5c07e1-af7d-44f2-a3ac-56758d0b9d16&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230329-102521.png&quot; data-file-size=&quot;33893&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230329-102521.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;379&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;381&quot; data-ke-size=&quot;size16&quot;&gt;위 첫번째 그림의 경우, Broker에 저장되어 있는 Topic A의 Message를 하나의 Consumer Instance에서 가지고 가고 있는 상황이다. 위 상황에서 두번 째 사진과 같이 Consumer Instance가 장애가 날 시 제대로 된 Data Consume을 할 수 없다. 그렇기 때문에 Group에는 여러개의 인스턴스를 포함하여 그룹 내부에서 다른 인스턴스가 대체할 수 있도록 해야한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;65&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;269&quot; data-height=&quot;182&quot; data-id=&quot;aed9b5ad-d1fc-41aa-8078-9d1146471bcc&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230330-013958.png&quot; data-file-size=&quot;46834&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230330-013958.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dylFsR/btsg1cJAcdH/sVVRxtBysoXW3YEH9UFcok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dylFsR/btsg1cJAcdH/sVVRxtBysoXW3YEH9UFcok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dylFsR/btsg1cJAcdH/sVVRxtBysoXW3YEH9UFcok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdylFsR%2Fbtsg1cJAcdH%2FsVVRxtBysoXW3YEH9UFcok%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;591&quot; height=&quot;401&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;616&quot; data-ke-size=&quot;size16&quot;&gt;위 그림은 Consumer Group 중 하나가 장애가 난 상황이다. 그러나 이 그림은 앞서 설명한 그림과는 다르게 4개의 Consumer Instance로 구성되어있기 때문에 Consumer A의 역할을 다른 Instance가 처리 할 수 있게 된다. 이러한 이유로 인해 Consumer Group을 구성하여 시스템 안정성을 확보하는 것이 중요하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;814&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;816&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2.2.-다수의-Instance에서의-Offset-관리&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;818&quot; data-ke-size=&quot;size23&quot;&gt;2.2. 다수의 Instance에서의 Offset 관리&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;850&quot; data-ke-size=&quot;size16&quot;&gt;Kafka는 내부적으로 각 topic마다 어디까지 message가 소비되었는지에 대한 pointer를 offset이라는 요소로 관리하게 된다. 그림으로 그려보면 아래와 같은 형태이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;65&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;212&quot; data-height=&quot;155&quot; data-id=&quot;096f5e02-800c-4789-bbe9-0ba2d768f019&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230330-021404.png&quot; data-file-size=&quot;20049&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230330-021404.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfagHq/btsgJ1Dgsc3/7JyvcLXl6yCc7mkOQMlNX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfagHq/btsgJ1Dgsc3/7JyvcLXl6yCc7mkOQMlNX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfagHq/btsgJ1Dgsc3/7JyvcLXl6yCc7mkOQMlNX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfagHq%2FbtsgJ1Dgsc3%2F7JyvcLXl6yCc7mkOQMlNX1%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;341&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;959&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;961&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;963&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;965&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;2.2.1.-독립적인-Consumer로-구성하였을-경우&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;967&quot; data-ke-size=&quot;size20&quot;&gt;2.2.1. 독립적인 Consumer로 구성하였을 경우&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCcHQQ/btsg1VnhKD6/mbiALktPAAYJSb7746rm41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCcHQQ/btsg1VnhKD6/mbiALktPAAYJSb7746rm41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCcHQQ/btsg1VnhKD6/mbiALktPAAYJSb7746rm41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCcHQQ%2Fbtsg1VnhKD6%2FmbiALktPAAYJSb7746rm41%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;829&quot; height=&quot;531&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;82&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;377&quot; data-height=&quot;241&quot; data-id=&quot;b6f2f5a0-8dc2-4abb-8313-e39dd09988c1&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230330-072757.png&quot; data-file-size=&quot;29470&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230330-072757.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1004&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1006&quot; data-ke-size=&quot;size16&quot;&gt;위 사진을 참고하며 아래와 같은 시퀀스를 생각해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Consumer A, B 모두 서버측에서 관리되고 있는 offset은 3이다.&lt;/li&gt;
&lt;li&gt;Consumer A가 pull 받아서 broker의 offset을 4로 변경하였다.&lt;/li&gt;
&lt;li&gt;Consumer B가 뒤이어 pull 받으려 하지만, 서버측에 저장되어 있는 offset이 3이기 때문에 병렬 처리가 되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1213&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1215&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1217&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;2.2.2.-같은-Consumer-Group의-Instance로-구성하였을-경우&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1219&quot; data-ke-size=&quot;size20&quot;&gt;2.2.2. 같은 Consumer Group의 Instance로 구성하였을 경우&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;82&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;380&quot; data-height=&quot;255&quot; data-id=&quot;2681ff88-5ccd-4783-92ee-a6731a462104&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230330-073133.png&quot; data-file-size=&quot;30712&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230330-073133.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3L1mm/btsgJZSVl3X/2rWsNJ9SMQJlMxTlk3xLc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3L1mm/btsgJZSVl3X/2rWsNJ9SMQJlMxTlk3xLc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3L1mm/btsgJZSVl3X/2rWsNJ9SMQJlMxTlk3xLc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3L1mm%2FbtsgJZSVl3X%2F2rWsNJ9SMQJlMxTlk3xLc0%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;836&quot; height=&quot;561&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1270&quot; data-ke-size=&quot;size16&quot;&gt;이 경우, consumer group으로 관리를 하기 때문에 offset 정보를 다른 Consumer Instance간 공유를 하게된다. 따라서 병렬처리를 할 때, offset이 꼬이지 않고 데이터의 손실이 없이 운영될 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1400&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-Consumer-Group과-Partition의-관계&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1402&quot;&gt;3. Consumer Group과 Partition의 관계&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1462&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 Kafka에서는 하나의 Partition에 대해 하나의 Consumer Group 내의 consumer instance만 허용하여 데이터를 순서대로 읽을 수 있도록 보장한다. 이 이론을 먼저 인지하고 난 다음 아래의 예제를 이용하여 어떻게 설계하는지에 대해 생각해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6I1my/btsgNWBmsCS/ar1vNRiZRSkosNiEiGfPG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6I1my/btsgNWBmsCS/ar1vNRiZRSkosNiEiGfPG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6I1my/btsgNWBmsCS/ar1vNRiZRSkosNiEiGfPG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6I1my%2FbtsgNWBmsCS%2Far1vNRiZRSkosNiEiGfPG0%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;232&quot; height=&quot;431&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1625&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;33&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;105&quot; data-height=&quot;196&quot; data-id=&quot;64805e4e-b665-4b34-bba6-7e0cb62ff63c&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230330-090053.png&quot; data-file-size=&quot;25832&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230330-090053.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1630&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1632&quot; data-ke-size=&quot;size16&quot;&gt;위에서 언급한 결론에 따르면, 하나의 partition에는 하나의 Consumer Group에 할당된 instance만 접근할 수 있다고 하였기 때문에 Consumer B의 경우 유휴상태가 되어 데이터를 처리하지 못한다. 따라서 Consumer A가 비정상이 되었을 때, Consumer B는 제 기능을 하는 standby instance가 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1828&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOKbYI/btsgG4UxaTm/iIZsRQl2ZSKhXTAHkhXa10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOKbYI/btsgG4UxaTm/iIZsRQl2ZSKhXTAHkhXa10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOKbYI/btsgG4UxaTm/iIZsRQl2ZSKhXTAHkhXa10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOKbYI%2FbtsgG4UxaTm%2FiIZsRQl2ZSKhXTAHkhXa10%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;304&quot; height=&quot;431&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;33&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2330689621&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;138&quot; data-height=&quot;196&quot; data-id=&quot;996cee6c-1b32-4b34-8537-11c1e148e434&quot; data-collection=&quot;contentId-2330689621&quot; data-file-name=&quot;image-20230330-090602.png&quot; data-file-size=&quot;33342&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230330-090602.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1835&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1837&quot; data-ke-size=&quot;size16&quot;&gt;위 그림의 경우도 하나의 Partition에 대해 하나의 Consumer Instance가 연결되어 있다. Consumer Instance가 Partition의 수보다 적기 때문에 하나의 Consumer Instance가 여러개의 Partition Data를 담당하여 번갈아가면서 데이터를 가지고 오게 된다. 이 때, 그림처럼 Consumer B &amp;harr;︎ Partition 4의 예기치 못하게 끊기게 되면 대체제인 Consumer A로 붙어 운영이 될 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2095&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2097&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2099&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2101&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 운영을 하면서 최적의 구성을 찾는 것이 가장 좋지만, 무조건 속도를 고려하게 되었을 때를 가정였을 때 얻은 결론은 결론은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;병렬처리를 극대화 하기 위해서는 하나의 파티션당 하나의 Consumer Instance가 존재해야한다.&lt;/li&gt;
&lt;li&gt;위 상황에서 하나의 Consumer Instance가 장애가 날 경우, 즉시 다른 Consumer Instance를 해당 partition에 할당하여 데이터를 핸들링 할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2354&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2652&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2701&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/OpenSource</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/49</guid>
      <comments>https://coding-deer.tistory.com/49#entry49comment</comments>
      <pubDate>Mon, 22 May 2023 15:36:10 +0900</pubDate>
    </item>
    <item>
      <title>[Apache Kafka] kafka topic convention에 대한 고민 (2022.12.29)</title>
      <link>https://coding-deer.tistory.com/48</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;5e057a80c001d73dd1f38cc833e621ff&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;5e057a80c001d73dd1f38cc833e621ff&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-발생한-이슈-사항&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 발생한 이슈 사항&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-기본적인-Kafka-Topic-Convention&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 기본적인 Kafka Topic Convention&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-개인적으로-cut-한-생각들&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 개인적으로 cut 한 생각들&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-발생한-이슈-사항&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;26&quot;&gt;1. 발생한 이슈 사항&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;모식도를 먼저 그려보면 아래 그림과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-width=&quot;82&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2314309352&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;419&quot; data-height=&quot;296&quot; data-id=&quot;2ebed49b-b43a-496f-8d5f-8c6462a86eda&quot; data-collection=&quot;contentId-2314309352&quot; data-file-name=&quot;image-20221229-021708.png&quot; data-file-size=&quot;149769&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20221229-021708.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;68&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;70&quot; data-ke-size=&quot;size16&quot;&gt;여러 공장에서 여러개 장비에 대한 buffer 데이터를 cluster에 전송한다. 이 때, 보낸 데이터에 대한 식별자가 필요한데 이를 topic으로 관리한다. topic convention에 대해 고민하는 도중 아래와 같은 고민이 들었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장비별로 topic을 생성한다&lt;/li&gt;
&lt;li&gt;공장별로 topic을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;248&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;250&quot; data-ke-size=&quot;size16&quot;&gt;이외에도 convention을 지정하기 위한 고민을 하며 들었던 생각 전체를 이 글에 적어놓고자 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;311&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-기본적인-Kafka-Topic-Convention&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;313&quot;&gt;2. 기본적인 Kafka Topic Convention&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;345&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;347&quot; data-ke-size=&quot;size16&quot;&gt;여러가지 요소들을 고민한 결과, 공통적으로 사용 할 수 있는 topic은 아래와 같이 정하기로 하였다.&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #172b4d; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span style=&quot;background-color: #000000; color: #000000;&quot; data-code-lang=&quot;&quot; data-ds--code--code-block=&quot;&quot;&gt;&lt;span&gt;&amp;lt;company&amp;gt;.&amp;lt;project|application&amp;gt;.&amp;lt;dataSource&amp;gt;.&amp;lt;eventType&amp;gt;.&amp;lt;etc&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;470&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;472&quot; data-ke-size=&quot;size16&quot;&gt;그 이유는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최대한 변하지 않는 속성이어야 한다. kafka topic을 한번 만들게 되면 수정이 불가능하기 때문에 맨 처음부터 불변하는 속성값이어야 한다.&lt;/li&gt;
&lt;li&gt;계층 구조를 선택하였다. url pattern처럼 한눈에 어떤 속성인지 볼 수 있어야 하며 정리되지 않을 경우 한번에 속성을 알아 차릴 수 없을 가능성이 높다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 대한민국-인천-국제공항 / 국제공항-대한민국-인천 어느 것이 더 가독성이 좋은지에 대해 생각해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;적당한 카디널리티를 유지할 수 있는 패턴이어야 한다. 너무 많은 카디널리티가 형성되게 되면 그만큼 topic과 broker 별로 replication factor에 따라서 다량으로 복제 될 수 있으며 불필요한 영역이 많이 형성되어 관리에 어려움을 겪을 수 있다고 생각하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;882&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;884&quot; data-ke-size=&quot;size16&quot;&gt;위 convention을 이용하여 향후 acelo grid에 사용 될 topic을 만들면 아래와 같은 형태가 될 것으로 보인다.&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #172b4d; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1684736608360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;company&amp;gt;.&amp;lt;project|application&amp;gt;.&amp;lt;dataSource&amp;gt;.&amp;lt;eventType&amp;gt;.&amp;lt;etc&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1021&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-개인적으로-cut-한-생각들&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1023&quot;&gt;3. 개인적으로 cut 한 생각들&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1043&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;topic에 mongodb _id 값을 이용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 사용하게 되면 이 id가 어떤 값인지 mongodb를 이용하지 않게 되면 알 수 없으며 다른 프로젝트에서 이종의 db를 사용하게 될 경우 문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;topic 생성시 device 단위까지 고려하여 모든 기기별로 별도의 topic을 가진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우, device를 생성하게 되면 topic을 같이 생성해야하는 이슈가 발생 할 수 있다. 이 경우 만약 topic 자동 생성이 불가한 구조에서는 관리자의 load가 커질 가능성이 있으며, topic 자동생성이 가능한 구조에서는 개발단계에서 생성한 쓰레기 topic이 많이 생겨나 관리에 어려움을 겪게 될 것이라고 생각을 하여 기기별 topic이 아닌 datasource별 topic으로 관리하는 것으로 생각했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/OpenSource</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/48</guid>
      <comments>https://coding-deer.tistory.com/48#entry48comment</comments>
      <pubDate>Mon, 22 May 2023 15:27:08 +0900</pubDate>
    </item>
    <item>
      <title>[Apache Kafka] Kafka Data Ordering issue (2023.01.26)</title>
      <link>https://coding-deer.tistory.com/47</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;a66879f2ab35337e3df6c80f71dcdb58&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;a66879f2ab35337e3df6c80f71dcdb58&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-서론&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 서론&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-문제점&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 문제점&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.1.-partition이-하나일-경우의-데이터-ordering-test&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.1. partition이 하나일 경우의 데이터 ordering test&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.-partition이-복수개의-경우&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2. partition이 복수개의 경우&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.1.-kafka-clustering&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2.1. kafka clustering&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.2.-data-ordering-test&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2.2. data ordering test&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-문제점-분석&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 문제점 분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.-해결법&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 해결법&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.1.-partitioning-key&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.1. partitioning key&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.2.-sticky-partition&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2. sticky partition&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-서론&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;26&quot;&gt;1. 서론&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;현재 기획중인 데이터 파이프라인의 경우 아래와 같은 특성을 지니고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 source가 아닌 다양한 환경의 source에서 데이터를 받는다.&lt;/li&gt;
&lt;li&gt;현장 local server가 존재하지만, 통계 및 분석을 local server에서 지원하게되면 disk 및 성능 문제가 발생 할 수 있으며 network가 단절될 시 큰 문제가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;여러 현장의 데이터를 중앙에서 관리하고 주기적으로 아카이빙 하기 용이해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;275&quot; data-ke-size=&quot;size16&quot;&gt;위의 이유로 인해 kafka를 도입하게 되었다. 그러나 kafka를 도입하면서 partition을 늘릴경우, data consume 하였을 때 data sorting이 의도된 대로 동작을 하지 않는 것을 보게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;398&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-문제점&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;400&quot;&gt;2. 문제점&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;523&quot; data-ke-size=&quot;size16&quot;&gt;partition이 하나의 경우를 시뮬레이션 하였을 때 아래와 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;563&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;565&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2.1.-partition이-하나일-경우의-데이터-ordering-test&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;567&quot; data-ke-size=&quot;size26&quot;&gt;2.1. partition이 하나일 경우의 데이터 ordering test&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1684735230569&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1 --topic order-test-single-partition
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/tools/build/dependant-libs-2.13.8/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/trogdor/build/dependant-libs-2.13.8/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/connect/runtime/build/dependant-libs/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/connect/mirror/build/dependant-libs/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Reload4jLoggerFactory]
Created topic order-test-single-partition.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2104&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1684735247438&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-console-producer.sh --topic order-test-single-partition --bootstrap-server 127.0.0.1:9092 
&amp;gt;1
&amp;gt;2
&amp;gt;3
&amp;gt;4
&amp;gt;5
&amp;gt;6
&amp;gt;7
&amp;gt;8
&amp;gt;9
&amp;gt;0&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1684735258698&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-console-consumer.sh --topic testtest11 --bootstrap-server 127.0.0.1:9092 --from-beginning        
1
2
3
4
5
6
7
8
9
0&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;2.2.-partition이-복수개의-경우&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2108&quot; data-ke-size=&quot;size26&quot;&gt;2.2. partition이 복수개의 경우&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2133&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2.2.1.-kafka-clustering&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2135&quot; data-ke-size=&quot;size23&quot;&gt;2.2.1. kafka clustering&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2160&quot; data-ke-size=&quot;size16&quot;&gt;clustering을 하기 위해 하나의 zookeeper과 3개의 kafka broker를 구성 할 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2222&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;2224&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 server.properties를 3개 작성해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1684735301649&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;############################# Server Basics #############################

# 각 브로커마다 id값이 달라야 하며 유일한 값이어야 한다.
broker.id=0

############################# Socket Server Settings #############################
#각 브로커가 사용할 port를 명시해주어야한다.
listeners=PLAINTEXT://:9092

# Listener name, hostname and port the broker will advertise to clients.
# If not set, it uses the value for &quot;listeners&quot;.
# 외부에 공개할 별칭
advertised.listeners=PLAINTEXT://127.0.0.1:9092

# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details
#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL

# The number of threads that the server uses for receiving requests from the network and sending responses to the network
num.network.threads=3

# The number of threads that the server uses for processing requests, which may include disk I/O
num.io.threads=8

# The send buffer (SO_SNDBUF) used by the socket server
socket.send.buffer.bytes=102400

# The receive buffer (SO_RCVBUF) used by the socket server
socket.receive.buffer.bytes=102400

# The maximum size of a request that the socket server will accept (protection against OOM)
socket.request.max.bytes=104857600


############################# Log Basics #############################
# log dir의 경우 각 kafka server마다 달라야한다.
log.dirs=/tmp/kafka-logs1

# The default number of log partitions per topic. More partitions allow greater
# parallelism for consumption, but this will also result in more files across
# the brokers.
num.partitions=1

# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown.
# This value is recommended to be increased for installations with data dirs located in RAID array.
num.recovery.threads.per.data.dir=1

############################# Internal Topic Settings  #############################
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1

############################# Log Flush Policy #############################
#log.flush.interval.messages=10000

#log.flush.interval.ms=1000

############################# Log Retention Policy #############################
log.retention.hours=168


log.retention.check.interval.ms=300000

############################# Zookeeper #############################
# 연결할 zookeeper node정보
zookeeper.connect=localhost:2181

# Timeout in ms for connecting to zookeeper
zookeeper.connection.timeout.ms=18000

group.initial.rebalance.delay.ms=0&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4824&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;4826&quot; data-ke-size=&quot;size16&quot;&gt;listeners, advertised.listeners, broker.id를 각 broker별로 다르게 구성한 다음, 아래 명령어를 각 broker를 아래 명령어를 이용하여 실행시켜준다.&lt;/p&gt;
&lt;pre id=&quot;code_1684735328633&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-server-start.sh {{YOUR_CONFIG_FILE_PATH}}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.2.2.-data-ordering-test&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;5028&quot; data-ke-size=&quot;size23&quot;&gt;2.2.2. data ordering test&lt;/h3&gt;
&lt;pre id=&quot;code_1684735365024&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --partitions 1 --replication-factor 10 --topic order-test-multi-partition
Created topic order-test-single-partition.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6756&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1684735383440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-console-producer.sh --topic order-test-single-partition --bootstrap-server 127.0.0.1:9092 
&amp;gt;1
&amp;gt;2
&amp;gt;3
&amp;gt;4
&amp;gt;5
&amp;gt;6
&amp;gt;7
&amp;gt;8
&amp;gt;9
&amp;gt;0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1684735475896&quot; class=&quot;dts&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --partitions 4 --replication-factor 1 --topic order-test
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/tools/build/dependant-libs-2.13.8/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/trogdor/build/dependant-libs-2.13.8/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/connect/runtime/build/dependant-libs/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/superdev/Downloads/kafka-3.3.1-src/connect/mirror/build/dependant-libs/slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Reload4jLoggerFactory]
Created topic order-test.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1684735431985&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;superdev@dev-Jhonason kafka-3.3.1-src % bin/kafka-console-consumer.sh --topic order-test-single-partition --bootstrap-server 127.0.0.1:9092 --from-beginning        
0
9
6
2
7
6
3
8
4
1&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6758&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6758&quot; data-ke-size=&quot;size16&quot;&gt;파티션이 1개일 경우와 확연한 차이를 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6788&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.--문제점-분석&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6790&quot;&gt;3. 문제점 분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6802&quot; data-ke-size=&quot;size16&quot;&gt;partition 전략이 round robin(or spraying) 전략일 경우에 파티션에 돌아가면서 데이터가 적재되게 된다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2325119008&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;554&quot; data-height=&quot;594&quot; data-id=&quot;5799f78e-1e64-4e05-85dd-87528ed8680f&quot; data-collection=&quot;contentId-2325119008&quot; data-file-name=&quot;image-20230126-024813.png&quot; data-file-size=&quot;81838&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230126-024813.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OCNGa/btsg0NXydC9/uniFXlBc4luiQ7oRrDfN20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OCNGa/btsg0NXydC9/uniFXlBc4luiQ7oRrDfN20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OCNGa/btsg0NXydC9/uniFXlBc4luiQ7oRrDfN20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOCNGa%2Fbtsg0NXydC9%2FuniFXlBc4luiQ7oRrDfN20%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;1218&quot; height=&quot;1306&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6880&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;6882&quot; data-ke-size=&quot;size16&quot;&gt;또한, 파티션별로 실시간 접근 속도가 다르기 때문에 가져올 때 빠른 파티션의 데이터를 먼저 가지고 온다. 따라서 위 그림과 같은 데이터 출력이 이루어지게 된다. 이로 인해 우리가 기대하던 데이터 ordering이 되지 않는 상황이 발생하게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7022&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7024&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-해결법&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7026&quot;&gt;4. 해결법&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 id=&quot;4.1.-partitioning-key&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7034&quot; data-ke-size=&quot;size26&quot;&gt;4.1. partitioning key&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7057&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 입력할 시, 파티셔닝 키를 참조하여 데이터를 insert 할 수 있다. kafka는 파티셔닝 키를 이용하여 해쉬를 계산한 다음 kafka 자체 알고리즘에 의해 파티션을 결정하여 데이터를 produce 하게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7182&quot; data-ke-size=&quot;size16&quot;&gt;그러나 이 방법은 해쉬값에 의해 파티션이 고정되기 때문에 최적화를 개발자가 하지 않으면 한 파티션으로 데이터가 몰릴 수 있기 때문에 조심해야한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7265&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7267&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7269&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;4.2.-sticky-partition&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7271&quot; data-ke-size=&quot;size26&quot;&gt;4.2. sticky partition&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;참고로, kafka 2.4부터 sticky session을 기본 파티셔닝 전략으로 채택했다고 한다.&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7348&quot; data-ke-size=&quot;size16&quot;&gt;partition 전략 관련 kafka parameter 공식 문서를 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lycOY/btsgMbyNPbC/MBkwyD266l7xCD76sxIttk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lycOY/btsgMbyNPbC/MBkwyD266l7xCD76sxIttk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lycOY/btsgMbyNPbC/MBkwyD266l7xCD76sxIttk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlycOY%2FbtsgMbyNPbC%2FMBkwyD266l7xCD76sxIttk%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;1244&quot; height=&quot;1236&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2325119008&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;565&quot; data-height=&quot;562&quot; data-id=&quot;fcbe6315-931e-4ea8-813b-ead20475d562&quot; data-collection=&quot;contentId-2325119008&quot; data-file-name=&quot;image-20230126-025929.png&quot; data-file-size=&quot;226288&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230126-025929.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7402&quot; data-ke-size=&quot;size16&quot;&gt;partitioner.class가 설정되지 않으면 기본 파티셔너가 사용된다. 이 전략은 batch.size 만큼의 데이터가 파티션에 차기 전까지 특정 파티션에 데이터를 고정시키는 전략이다. 이 전략이 동작하는 세부적인 사항은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션이 명시되지 않았으나 키가 존재한다면, 키의 해쉬 값에 의해 파티션이 선택된다.&lt;/li&gt;
&lt;li&gt;파티션과 키 둘다 존재하지 않는다면 batch.size 만큼의 데이터가 특정 파티션에 고정되어 입력되고, 이를 넘길 시 다른 파티션에 고정되어 입력된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7679&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7681&quot; data-ke-size=&quot;size16&quot;&gt;이를 이용하게 되면 아래 그림과 같이 파티션에 데이터들이 프로듀싱 될 것이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2325119008&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1001&quot; data-height=&quot;666&quot; data-id=&quot;031d43f1-a2ee-4b45-b1db-86ef3434ad1b&quot; data-collection=&quot;contentId-2325119008&quot; data-file-name=&quot;image-20230126-074904.png&quot; data-file-size=&quot;60057&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20230126-074904.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba7ekR/btsgTiqvrfd/gHCSPigBoBu3HGmi3unQj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba7ekR/btsgTiqvrfd/gHCSPigBoBu3HGmi3unQj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba7ekR/btsgTiqvrfd/gHCSPigBoBu3HGmi3unQj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba7ekR%2FbtsgTiqvrfd%2FgHCSPigBoBu3HGmi3unQj1%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;1001&quot; height=&quot;666&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7731&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7731&quot; data-ke-size=&quot;size16&quot;&gt;kafka 세팅시 batch.size, linger.ms 설정을 배치 서비스에 맞게 잘 설정하였다면, 배치 데이터가 하나의 파티션에 묶이기 때문에 최소 하나의 배치에 대해서는 데이터 순서를 보장받을 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-renderer-start-pos=&quot;7731&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/OpenSource</category>
      <category>dataordering</category>
      <category>Kafka</category>
      <category>partitioning</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/47</guid>
      <comments>https://coding-deer.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 22 May 2023 15:07:58 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 불필요한 index 정리 및 최적화 (2022.09.01)</title>
      <link>https://coding-deer.tistory.com/46</link>
      <description>&lt;div&gt;
&lt;div id=&quot;content&quot; data-inline-comments-target=&quot;true&quot; data-testid=&quot;page-content-only&quot;&gt;
&lt;div id=&quot;main-content&quot; data-testid=&quot;pageContentRendererTestId&quot; data-test-appearance=&quot;full-page&quot;&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;h1 id=&quot;0.-Table-Of-Contents&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;031251d64058309ae575c836deabe183&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot; data-testid=&quot;printable-wrapper&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;031251d64058309ae575c836deabe183&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#1.-개요&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 개요&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.-배경-지식&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 배경 지식&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.1.-인덱스&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.1. 인덱스&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.2.-B-Tree&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.2. B-Tree&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;2.3.-B-Tree에 최적화된-mongodb-index-설계&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.3. B-Tree에 최적화된 mongodb index 설계&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.3.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.3.1.-ESR-Rule&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.3.1. ESR Rule&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;3.3.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#2.3.2.-왜 ESR-Rule을-따라야-하는가?&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2.3.2. 왜 ESR Rule을 따라야 하는가?&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.-개선 전0DB-문제점-분석&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 개선 전 DB 문제점 분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.1.-불필요한-인덱스&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.1. 불필요한 인덱스&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.2.-인덱스-카디널리티-분석&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.2. 인덱스 카디널리티 분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;4.3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#3.3.-ESR-Rule-분석&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.3. ESR Rule 분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.-인덱스-튜닝-결과&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 인덱스 튜닝 결과&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-testid=&quot;list-style-toc-level-container&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.1&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.1.-Index-Access&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.1. Index Access&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.2&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.2.-카디널리티-분석-ESR-Rule-분석을-통한-개선&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2. 카디널리티 분석 ESR Rule 분석을 통한 개선&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;5.3&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#4.3.-총평&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.3. 총평&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-testid=&quot;list-style-toc-item-container&quot;&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;#5.-후기-및-다음-작업에-대한-예고&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5. 후기 및 다음 작업에 대한 예고&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-개요&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;26&quot;&gt;1. 개요&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;여러 서비스로 쪼개져 있는 환경에서는 각 마이크로서비스가 필요한 인덱스를 build 할 수 있다. 그러나, 인덱스를 개발자 간 상호 협의 없이 각자 필요한 인덱스를 수시로 빌드하여 사용하다보니 불필요한 인덱스가 생기면서 비용적인 낭비가 발생하게 되었다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;176&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이에 mongodb 튜닝을 진행하여 최적화 시키는 작업을 진행하고자 한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;225&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-배경-지식&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;227&quot;&gt;2. 배경 지식&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h3 id=&quot;2.1.-인덱스&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;237&quot; data-ke-size=&quot;size23&quot;&gt;2.1. 인덱스&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 저장된 위치를 key-value 형태로 표현하며 검색속도를 향상시키기 위해 존재하는 데이터이다. 인덱스는 새로운 데이터가 기록될 때 마다 새로운 순서로 계속 정렬을 하기 때문에 쓰기가 느리지만, 이미 정렬되어있기 때문에, 특정 데이터를 찾아오는 작업에 대해서는 매우 빠른 속도를 보장한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;416&quot; data-ke-size=&quot;size16&quot;&gt;그러나 인덱스가 DB의 성능을 무조건 향상시키는 것은 아니다. 인덱스는 위에서 언급한 것 처럼 새로운 순서로 지속적으로 계속 정렬을 하기 때문에 쓰기가 많이 일어나는 환경에서 불필요한 인덱스를 생성하나 복잡한 인덱스가 존재하면 오히려 DB의 성능을 떨어뜨릴 수 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;567&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2.2.-B-Tree&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;569&quot; data-ke-size=&quot;size23&quot;&gt;2.2. B-Tree&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;582&quot; data-ke-size=&quot;size16&quot;&gt;기존 이진트리는 노드 하나에 하나의 데이터밖에 저장할 수 없지만, 이를 개선하여 노드 하나에 여러개의 데이터를 저장할 수 있도록 개선한 자료구조이다. 노드에 자료가 몇개 있으냐에 따라 n차 B-Tree라고 부른다.&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;2880&quot; data-height=&quot;801&quot; data-id=&quot;a48dc479-ddac-48d1-8fcf-e5521ca4db47&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220819-125904.png&quot; data-file-size=&quot;128553&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220819-125904.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HyYGd/btsgYiczmdj/6OaiMMdPXiKmKx2hHwe6N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HyYGd/btsgYiczmdj/6OaiMMdPXiKmKx2hHwe6N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HyYGd/btsgYiczmdj/6OaiMMdPXiKmKx2hHwe6N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHyYGd%2FbtsgYiczmdj%2F6OaiMMdPXiKmKx2hHwe6N1%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;1848&quot; height=&quot;514&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-renderer-start-pos=&quot;706&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;출처 : By CyHawk - Own work based on [1]., CC BY-SA 3.0 (https://commons.wikimedia.org/w/index.php?curid=11701365)&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;765&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2.3.-B-Tree에-최적화된-mongodb-index-설계&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;767&quot; data-ke-size=&quot;size23&quot;&gt;2.3. B-Tree에 최적화된 mongodb index 설계&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;803&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.mongodb.com/blog/post/performance-best-practices-indexing&quot; data-testid=&quot;link-with-safety&quot; data-renderer-mark=&quot;true&quot;&gt;MongoDB 공식 document&lt;/a&gt;에 따르면, index는 ESR Rule를 따르는 것이 가장 좋다고 설명되어있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;870&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;2.3.1.-ESR-Rule&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;872&quot; data-ke-size=&quot;size20&quot;&gt;2.3.1. ESR Rule&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-renderer-start-pos=&quot;889&quot; data-ke-size=&quot;size16&quot;&gt;ESR은 각각 Equality, Sort, Range를 의미하며, 아래의 rule을 따라야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Query에서 Equality에 대한 부분을 먼저 배치한다.&lt;/li&gt;
&lt;li&gt;그 다음 query의 order를 반영하는 필드가 반영이 되어야 하며,&lt;/li&gt;
&lt;li&gt;마지막에 range를 고려한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;1048&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;2.3.2.-왜-ESR-Rule을-따라야-하는가?&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;1050&quot; data-ke-size=&quot;size20&quot;&gt;2.3.2. 왜 ESR Rule을 따라야 하는가?&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-renderer-start-pos=&quot;1079&quot; data-ke-size=&quot;size16&quot;&gt;위 B-Tree 데이터를 보면서 생각을 해보자. 우리는 단순 사람이름과 시험 점수에 대한 데이터를 저장하고 있는 exam collection에 대해 compound index를 만들어야 하는 상황이다. 그러면 당신은 어떻게 index를 설계할 것인가&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1220&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;2.3.2.1. E와 R의 관계&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1241&quot; data-ke-size=&quot;size16&quot;&gt;단순 find를 할 시, 우리는 아래와 같은 케이스를 생각 할 수 있을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1684734327100&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;exam.find({&quot;score&quot;:{$gte:60}}, studentName:&quot;dummy&quot;)
exam.find({studentName:&quot;B&quot;, &quot;score&quot;:{$gt:80}})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;1241&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1389&quot; data-ke-size=&quot;size16&quot;&gt;첫번째 케이스에 대해 탐색하는 것을 모식도로 그려보면 다음과 같다.&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-width=&quot;80&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;369&quot; data-height=&quot;191&quot; data-id=&quot;e2ea71e9-56ea-46ec-8b6a-9404c7f9027c&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220824-025628.png&quot; data-file-size=&quot;65435&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220824-025628.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oCyTr/btsgNXfPBHa/2rBdK9TJqYdccT4DTj0p21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oCyTr/btsgNXfPBHa/2rBdK9TJqYdccT4DTj0p21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oCyTr/btsgNXfPBHa/2rBdK9TJqYdccT4DTj0p21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoCyTr%2FbtsgNXfPBHa%2F2rBdK9TJqYdccT4DTj0p21%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;811&quot; height=&quot;421&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;1431&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1433&quot; data-ke-size=&quot;size16&quot;&gt;위 find query를 따라가면, 먼저 60점 이상의 점수를 찾아야 하므로 4개의 노드를 참조 할 것이다. 그다음 B라는 value를 가진 studentName을 찾아야 하기 때문에, 또 4개의 key를 더 탐색해야하므로 총 8개의 key를 탐색 할 것이다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1580&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1580&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1582&quot; data-ke-size=&quot;size16&quot;&gt;두번째 케이스에 대해 탐색하는 것을 모식도로 그려보면 다음과 같다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LqrFV/btsgEKPnjzV/3wktPGMQICyUusK1GMB37K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LqrFV/btsgEKPnjzV/3wktPGMQICyUusK1GMB37K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LqrFV/btsgEKPnjzV/3wktPGMQICyUusK1GMB37K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLqrFV%2FbtsgEKPnjzV%2F3wktPGMQICyUusK1GMB37K%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;1414&quot; height=&quot;820&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;643&quot; data-height=&quot;373&quot; data-id=&quot;39cafb9c-ba72-4cfc-8f6f-510b9217f362&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220824-045858.png&quot; data-file-size=&quot;101445&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220824-045858.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;1624&quot; data-ke-size=&quot;size16&quot;&gt;두번째 케이스 query를 분석하게 되면 A,B,C 중 해당 되는 노드만 참조하면 되기 때문에 위 케이스와 비교하였을 때 훨씬 참조하는 노드가 적어진다. 따라서 참조하는 노드가 적어지는 만큼 search 시간도 빨라진다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1749&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1774&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1793&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1795&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-개선-전-DB-문제점-분석&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;1797&quot;&gt;3. 개선 전 DB 문제점 분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;547&quot; data-height=&quot;327&quot; data-id=&quot;b3117ddc-4fca-404b-98b0-512bfe764015&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220827-175711.png&quot; data-file-size=&quot;36929&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220827-175711.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRVbHT/btsg1cW1PO9/3FdofDodLDTysMmNoUGTk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRVbHT/btsg1cW1PO9/3FdofDodLDTysMmNoUGTk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRVbHT/btsg1cW1PO9/3FdofDodLDTysMmNoUGTk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRVbHT%2Fbtsg1cW1PO9%2F3FdofDodLDTysMmNoUGTk1%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;438&quot; height=&quot;262&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;1819&quot; data-ke-size=&quot;size16&quot;&gt;서로가 필요한 인덱스를 협의없이 생성을 하다보니 비슷한 형태의 인덱스도 많고 쓰지 않는데 선언이 되어있다보니 프로젝트가 실행될때마다 불필요한 인덱스를 만드는 비효율적인 루틴을 지속해서 실행하고 있었다. 인덱스가 선언된 부분과 기타 외적인 부분을 분석한 결과 아래와 같은 문제점을 도출해 낼 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;index prefix를 사용하지 않아 불필요한 인덱스가 있음.&lt;/li&gt;
&lt;li&gt;비슷한 유형의 인덱스는 협의하에 통일 할 수 있었으나 그러지 못한 인덱스가 존재&lt;/li&gt;
&lt;li&gt;특정 인덱스의 경우 카디널리티가 작은 key에 대해서도 인덱스가 생성이 되어있어 비효율적인 부분이 발생할 수 있음&lt;/li&gt;
&lt;li&gt;ESR rule에 맞지 않는 인덱스가 존재하였다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;2175&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2177&quot; data-ke-size=&quot;size16&quot;&gt;위 문제점들이 맞는지에 대한 검증과 추가적인 문제점이 있는지 분석을 해보기로 하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2226&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.1.-불필요한-인덱스&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;2228&quot; data-ke-size=&quot;size23&quot;&gt;3.1. 불필요한 인덱스&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;2243&quot; data-ke-size=&quot;size16&quot;&gt;아래 query결과는 현재 aggregation 대상이 된 collection의 index가 얼마나 참조되고 있는지에 대한 현황을 나타낸다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2323&quot; data-ke-size=&quot;size16&quot;&gt;아래 결과에서 access부분을 유심히 관찰해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1684734412697&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.items.aggregate([{$indexStats:{}},{$project:{name:1, accesses:1}}])

/* 1 */
{
    &quot;name&quot; : &quot;device_-1_name_-1_statisticPeriod_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 2 */
{
    &quot;name&quot; : &quot;device_1_isExported_1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 3 */
{
    &quot;name&quot; : &quot;device_-1_name_-1_statisticPeriod_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(604388),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 4 */
{
    &quot;name&quot; : &quot;isExported_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 5 */
{
    &quot;name&quot; : &quot;_id_&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(604039),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 6 */
{
    &quot;name&quot; : &quot;createdAt_-1_isExported_-1_statisticPeriod_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 7 */
{
    &quot;name&quot; : &quot;isExported_-1_statisticPeriod_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(604388),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 8 */
{
    &quot;name&quot; : &quot;isExported_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 9 */
{
    &quot;name&quot; : &quot;createdAt_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 10 */
{
    &quot;name&quot; : &quot;device_-1_name_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(3444352),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 11 */
{
    &quot;name&quot; : &quot;name_1_value_1_isExported_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(604388),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 12 */
{
    &quot;name&quot; : &quot;factoryName_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;4467&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4467&quot; data-ke-size=&quot;size16&quot;&gt;accesses.ops가 0인 부분들이 많이 보인다. 즉, 한번도 사용 안되는 인덱스들이 존재하며, insert 될 때마다 이 인덱스들이 정렬되고 있다는 증거이다.&lt;/p&gt;
&lt;pre id=&quot;code_1684734452935&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 1 */
{
    &quot;name&quot; : &quot;device_-1_name_-1_createdAt_-1&quot;,
    &quot;key&quot; : {
        &quot;device&quot; : -1,
        &quot;name&quot; : -1,
        &quot;createdAt&quot; : -1
    },
    &quot;host&quot; : &quot;DESKTOP-TN7G775:27017&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(3456588),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    },
    &quot;spec&quot; : {
        &quot;v&quot; : 2,
        &quot;key&quot; : {
            &quot;device&quot; : -1,
            &quot;name&quot; : -1,
            &quot;createdAt&quot; : -1
        },
        &quot;name&quot; : &quot;device_-1_name_-1_createdAt_-1&quot;,
        &quot;background&quot; : true
    }
}

/* 2 */
{
    &quot;name&quot; : &quot;device_-1_name_-1_statisticPeriod_-1&quot;,
    &quot;key&quot; : {
        &quot;device&quot; : -1,
        &quot;name&quot; : -1,
        &quot;statisticPeriod&quot; : -1
    },
    &quot;host&quot; : &quot;DESKTOP-TN7G775:27017&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(606924),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    },
    &quot;spec&quot; : {
        &quot;v&quot; : 2,
        &quot;key&quot; : {
            &quot;device&quot; : -1,
            &quot;name&quot; : -1,
            &quot;statisticPeriod&quot; : -1
        },
        &quot;name&quot; : &quot;device_-1_name_-1_statisticPeriod_-1&quot;,
        &quot;background&quot; : true
    }
}

/* 3 */
{
    &quot;name&quot; : &quot;name_1_value_1_isExported_1&quot;,
    &quot;key&quot; : {
        &quot;name&quot; : 1,
        &quot;value&quot; : 1,
        &quot;isExported&quot; : 1
    },
    &quot;host&quot; : &quot;DESKTOP-TN7G775:27017&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(606925),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    },
    &quot;spec&quot; : {
        &quot;v&quot; : 2,
        &quot;key&quot; : {
            &quot;name&quot; : 1,
            &quot;value&quot; : 1,
            &quot;isExported&quot; : 1
        },
        &quot;name&quot; : &quot;name_1_value_1_isExported_1&quot;,
        &quot;background&quot; : true
    }
}

/* 4 */
{
    &quot;name&quot; : &quot;_id_&quot;,
    &quot;key&quot; : {
        &quot;_id&quot; : 1
    },
    &quot;host&quot; : &quot;DESKTOP-TN7G775:27017&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(606575),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    },
    &quot;spec&quot; : {
        &quot;v&quot; : 2,
        &quot;key&quot; : {
            &quot;_id&quot; : 1
        },
        &quot;name&quot; : &quot;_id_&quot;
    }
}

/* 5 */
{
    &quot;name&quot; : &quot;isExported_-1_statisticPeriod_-1_createdAt_-1&quot;,
    &quot;key&quot; : {
        &quot;isExported&quot; : -1,
        &quot;statisticPeriod&quot; : -1,
        &quot;createdAt&quot; : -1
    },
    &quot;host&quot; : &quot;DESKTOP-TN7G775:27017&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(606924),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    },
    &quot;spec&quot; : {
        &quot;v&quot; : 2,
        &quot;key&quot; : {
            &quot;isExported&quot; : -1,
            &quot;statisticPeriod&quot; : -1,
            &quot;createdAt&quot; : -1
        },
        &quot;name&quot; : &quot;isExported_-1_statisticPeriod_-1_createdAt_-1&quot;,
        &quot;background&quot; : true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;7101&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7103&quot; data-ke-size=&quot;size16&quot;&gt;그러나 이제 눈에 보이는 것은 MongoDB의 ESR Rule에 따르지 않은 것들이 눈에 보인다. 이를 개선하기 위해 몇개의 쿼리를 더 실행하여보자.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7188&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7190&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.2.-인덱스-카디널리티-분석&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;7192&quot; data-ke-size=&quot;size23&quot;&gt;3.2. 인덱스 카디널리티 분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;7211&quot; data-ke-size=&quot;size16&quot;&gt;카디널리티란, 간단히 말하여 데이터의 중복도를 의미한다. 즉, distinct를 하였을 때 더 많은 컨텐츠가 나오면 카디널리티가 높다고 말할 수 있다.&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;547&quot; data-height=&quot;327&quot; data-id=&quot;b3117ddc-4fca-404b-98b0-512bfe764015&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220827-175711.png&quot; data-file-size=&quot;36929&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220827-175711.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220827-175711.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF8qoh/btsgEKu5yRl/nkcY3U1f7kmWmWJVNxiUzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF8qoh/btsgEKu5yRl/nkcY3U1f7kmWmWJVNxiUzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF8qoh/btsgEKu5yRl/nkcY3U1f7kmWmWJVNxiUzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF8qoh%2FbtsgEKu5yRl%2FnkcY3U1f7kmWmWJVNxiUzk%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;438&quot; height=&quot;262&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;7300&quot; data-ke-size=&quot;size16&quot;&gt;카디널리티가 낮은 키들을 추린 결과 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isExported : true or false&lt;/li&gt;
&lt;li&gt;factoryName : 해당 사이트이름&lt;/li&gt;
&lt;li&gt;seconds : 1~60 고정&lt;/li&gt;
&lt;li&gt;statisticPeriod : 10&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;7433&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7435&quot; data-ke-size=&quot;size16&quot;&gt;이 키들을 대상으로 인덱스를 만들게되면 애초에 카디널리티가 매우 낮기 때문에, B-Tree 구조를 가진 인덱스를 만들더라도 효과가 거의 없는 수준이다. 따라서 이 인덱스를 정말로 필요하지 않는 한 제거하기로 결정을 하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7561&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7563&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7565&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7567&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7569&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.3.-ESR-Rule-분석&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;7571&quot; data-ke-size=&quot;size23&quot;&gt;3.3. ESR Rule 분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;7589&quot; data-ke-size=&quot;size16&quot;&gt;Equality-Sort-Range의 기준으로 인덱스가 작성이 되어야 가장 효율적인 인덱스를 빌드 할 수 있다. 그러나 위 명시된 key 순서에서 해당 조건을 만족시키지 못하는 인덱스 들이 많이 보인다. 대표적으로 createdAt 키가 가장 앞에 오는 인덱스이다. 일반적으로 sort나 range 검색에 자주 쓰이는 key지만 맨 앞에 위치하고 있기 때문에, B-Tree 구조를 가진 인덱스 동작을 생각해보면 불필요한 노드가 여러개 생길 수 있으므로 기존보다 용량을 더욱 많이 차지할 것으로 예상된다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7872&quot; data-ke-size=&quot;size16&quot;&gt;ESR Rule에 어긋나는 인덱스를 추려보면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;{name:-1, createdAt:-1, seconds:-1}&lt;/li&gt;
&lt;li&gt;{name:-1, device-1, createdAt:-1}&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;7985&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7987&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7989&quot; data-ke-size=&quot;size16&quot;&gt;먼저 첫번째 인덱스를 살펴보면&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8007&quot; data-ke-size=&quot;size16&quot;&gt;name의 경우에는 equality 속성을, createdAt은 range + sort 속성, second는 backend service에서 $in operator 를 사용하고 있었기 때문에 equality속성을 지니고 있다. ESR rule에 따르면 equality가 먼저 취급되어야 하지만, 이 인덱스는 그러하지 못하였다. 또한, second 의 경우 카티널리티가 낮은 인덱스이기도 하기 때문에 인덱스 효과를 보기 미미하다고 판단하였다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLm8Ns/btsgKpKqRLM/5dOQyZZIJoCFn1eO6TCe9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLm8Ns/btsgKpKqRLM/5dOQyZZIJoCFn1eO6TCe9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLm8Ns/btsgKpKqRLM/5dOQyZZIJoCFn1eO6TCe9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLm8Ns%2FbtsgKpKqRLM%2F5dOQyZZIJoCFn1eO6TCe9k%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;1346&quot; height=&quot;666&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;612&quot; data-height=&quot;303&quot; data-id=&quot;9983d852-f15e-428b-b162-21a861256281&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220831-015739.png&quot; data-file-size=&quot;108163&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220831-015739.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;8260&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8262&quot; data-ke-size=&quot;size16&quot;&gt;실제로 위와 같은 mongodb find query가 사용되고 있었으며, 위 쿼리가 의도하지 않은 인덱스인 {name:-1, createdAt:-1, seconds:-1}를 타고 있었기 때문에 이를 개편하기로 결정하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8388&quot; data-ke-size=&quot;size16&quot;&gt;쿼리에서 사용한 key는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;device : 데이터를 받는 장비의 objectId&lt;/li&gt;
&lt;li&gt;name : 데이터의 이름&lt;/li&gt;
&lt;li&gt;createdAt : 데이터가 만들어진 시점 (실시간성이 있기 때문에 쿼리시, 가장 최근 기준으로 데이터를 많이 불러옴.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;8536&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8538&quot; data-ke-size=&quot;size16&quot;&gt;위 key 중 createdAt는 sort 및 range에 해당하는 key이기 때문에 맨 뒤로 인덱스 우선순위를 미뤘으며, 현재는 비슷한 카디널리티의 분포이지만 향후 수집하는 기기의 수가 더 많아지면 device key가 name보다 더 카디널리티가 높게 나타날 것이다. 높은 카디널리티 key를 인덱스에 우선 배치를 하게 되면 그만큼 사전에 filter 되는 데이터의 수가 더 많아지게 되기 때문에 index scan을 줄이는 효과를 얻을 수 있다고 판단하여 쿼리때 제공되는 key와 동일한 index를 아래와 같이 설계하기로 결정하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1684734501627&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{device:1, name:1, createdAt:-1}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;8878&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8880&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8882&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8884&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-인덱스-튜닝-결과&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;8886&quot;&gt;4. 인덱스 튜닝 결과&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h3 id=&quot;4.1.-Index-Access&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;8900&quot; data-ke-size=&quot;size23&quot;&gt;4.1. Index Access&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;8919&quot; data-ke-size=&quot;size16&quot;&gt;이전 설계를 그대로 가져갈 경우, 아래와 같이 access를 하지 않는 인덱스를 많이 볼 수 있었다. 따라서 인덱스가 빌드된 만큼의 computing resource를 차지하고 있기 때문에 비효율을 야기시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1684734530268&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.items.aggregate([{$indexStats:{}},{$project:{name:1, accesses:1}}])

/* 1 */
{
    &quot;name&quot; : &quot;device_-1_name_-1_statisticPeriod_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 2 */
{
    &quot;name&quot; : &quot;device_1_isExported_1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}
.
.
.

/* 6 */
{
    &quot;name&quot; : &quot;createdAt_-1_isExported_-1_statisticPeriod_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 7 */
{
    &quot;name&quot; : &quot;isExported_-1_statisticPeriod_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(604388),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

/* 8 */
{
    &quot;name&quot; : &quot;isExported_-1_createdAt_-1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}

.
.
.

/* 12 */
{
    &quot;name&quot; : &quot;factoryName_1&quot;,
    &quot;accesses&quot; : {
        &quot;ops&quot; : NumberLong(0),
        &quot;since&quot; : ISODate(&quot;2022-07-05T10:52:12.533Z&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;10184&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;10186&quot; data-ke-size=&quot;size16&quot;&gt;불필요한 인덱스를 제거한 결과, 아래와 같이 access가 0인 인덱스가 사라졌다. 그러나, access는 하고 있지만 쿼리가 의도한 인덱스를 타고 있지 않는 경우도 있기 때문에 이 사항도 같이 개선하기로 하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;10307&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;10309&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4.2.-카디널리티-분석-ESR-Rule-분석을-통한-개선&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;10311&quot; data-ke-size=&quot;size23&quot;&gt;4.2. 카디널리티 분석 ESR Rule 분석을 통한 개선&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;10345&quot; data-ke-size=&quot;size16&quot;&gt;3.2에서 내린 결론을 토대로 인덱스를 튜닝하고, 의도한 인덱스를 사용하게 한 결과 아래와 같은 결과를 얻을 수 있었다.&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-width=&quot;65&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;343&quot; data-height=&quot;212&quot; data-id=&quot;b9de5a7a-e4c0-44d2-86a2-086cb4514967&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220831-015853.png&quot; data-file-size=&quot;85392&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220831-015853.png&quot; data-test-progress=&quot;1&quot;&gt;
&lt;pre id=&quot;code_1684734554931&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.getCollection('items')
  .find(
          {
					&quot;device&quot;:ObjectId(&quot;6188cda829424d2f3bf38cf0&quot;),
					&quot;name&quot;:&quot;Tap Position&quot;,
					&quot;createdAt&quot;:{
						$gt:ISODate(&quot;2021-11-08 07:13:40.000Z&quot;),
						$lt:ISODate(&quot;2022-11-08 07:13:40.000Z&quot;)
					},
					&quot;second&quot;:{
						$in:[0, 10, 20, 30, 40, 50]
					}
          }
  )
  ._addSpecial('$hint', {
                            &quot;name&quot; : -1,
                            &quot;createdAt&quot; : -1,
                            &quot;seconds&quot; : -1
                        }
               )
  .explain(&quot;executionStats&quot;)​&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cql8ba/btsgJZ6mm15/4WcWlsgcbqtvuJQpHFF3Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cql8ba/btsgJZ6mm15/4WcWlsgcbqtvuJQpHFF3Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cql8ba/btsgJZ6mm15/4WcWlsgcbqtvuJQpHFF3Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcql8ba%2FbtsgJZ6mm15%2F4WcWlsgcbqtvuJQpHFF3Xk%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;466&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1684734582547&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.getCollection('items')
  .find(
          {
					&quot;device&quot;:ObjectId(&quot;6188cda829424d2f3bf38cf0&quot;),
					&quot;name&quot;:&quot;Tap Position&quot;,
					&quot;createdAt&quot;:{
						$gt:ISODate(&quot;2021-11-08 07:13:40.000Z&quot;),
						$lt:ISODate(&quot;2022-11-08 07:13:40.000Z&quot;)
					}
          }
  )
  ._addSpecial('$hint', {
                            &quot;device&quot; : 1,
                            &quot;name&quot; : 1,
                            &quot;createdAt&quot; : -1
                        }
               )
  .explain(&quot;executionStats&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;div data-layout=&quot;center&quot; data-width=&quot;65&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;346&quot; data-height=&quot;418&quot; data-id=&quot;44e8b9b2-9f14-4d06-9732-33da358bf3e3&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220831-024639.png&quot; data-file-size=&quot;168056&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220831-024639.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;919&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/croNfn/btsgTj3WE2F/6ZJUSEo5S6KoD5AuyqtSk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/croNfn/btsgTj3WE2F/6ZJUSEo5S6KoD5AuyqtSk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/croNfn/btsgTj3WE2F/6ZJUSEo5S6KoD5AuyqtSk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcroNfn%2FbtsgTj3WE2F%2F6ZJUSEo5S6KoD5AuyqtSk0%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;761&quot; height=&quot;919&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;919&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;11465&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11467&quot; data-ke-size=&quot;size16&quot;&gt;주요 지표만 정리를 하면 다음과 같다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11490&quot; data-ke-size=&quot;size16&quot;&gt;totalKeyExamined : 13771515 &amp;rarr; 49&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11524&quot; data-ke-size=&quot;size16&quot;&gt;executionTimeMillis : 37461 &amp;rarr; 163&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11559&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11561&quot; data-ke-size=&quot;size16&quot;&gt;더 적은 수의 key를 examine하고, 같은 값을 구하는데 더 적은 실행시간을 사용하게 되었다. 따라서 인덱스 튜닝을 함으로써 얻을 수 있는 효과를 얻었다고 할 수 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11660&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11662&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4.3.-총평&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;11664&quot; data-ke-size=&quot;size23&quot;&gt;4.3. 총평&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;11673&quot; data-ke-size=&quot;size16&quot;&gt;위 분석을 통하여 얻어낸 결과 뿐만아니라, 다른 인덱스에 대해서도 전체적인 튜닝을 진행한 결과 전체적으로 많은 computing resource를 줄일 수 있었다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11766&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11768&quot; data-ke-size=&quot;size16&quot;&gt;item collection 개선 전 stat&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-width=&quot;100&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1496&quot; data-height=&quot;1418&quot; data-id=&quot;a1ee7613-7714-457d-91e3-1d1b68473c01&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220901-085744.png&quot; data-file-size=&quot;285639&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220901-085744.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;1418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XVJLx/btsgEK9HxCV/9KoZkZEyQbjIv8VuHfsrL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XVJLx/btsgEK9HxCV/9KoZkZEyQbjIv8VuHfsrL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XVJLx/btsgEK9HxCV/9KoZkZEyQbjIv8VuHfsrL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXVJLx%2FbtsgEK9HxCV%2F9KoZkZEyQbjIv8VuHfsrL0%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;1496&quot; height=&quot;1418&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;1418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;11798&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11800&quot; data-ke-size=&quot;size16&quot;&gt;item collection 개선 후 stat&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;894&quot; data-height=&quot;412&quot; data-id=&quot;09569973-e93f-4da0-8ebe-97da31272fea&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220901-090202.png&quot; data-file-size=&quot;157817&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220901-090202.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBvAFM/btsg0mMEhmu/3em4DdrPBWanKPkCBdPInk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBvAFM/btsg0mMEhmu/3em4DdrPBWanKPkCBdPInk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBvAFM/btsg0mMEhmu/3em4DdrPBWanKPkCBdPInk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBvAFM%2Fbtsg0mMEhmu%2F3em4DdrPBWanKPkCBdPInk%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;1848&quot; height=&quot;852&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;11830&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11832&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11834&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11836&quot; data-ke-size=&quot;size16&quot;&gt;statisticItem 개선 전 stat&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1594&quot; data-height=&quot;1436&quot; data-id=&quot;72323fb9-e508-4e90-b83f-19c562a71f46&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220901-090505.png&quot; data-file-size=&quot;274381&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220901-090505.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;1436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/udR3q/btsgGlO7vnO/9ia5xJm4fwpZuEXafMM1Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/udR3q/btsgGlO7vnO/9ia5xJm4fwpZuEXafMM1Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/udR3q/btsgGlO7vnO/9ia5xJm4fwpZuEXafMM1Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FudR3q%2FbtsgGlO7vnO%2F9ia5xJm4fwpZuEXafMM1Fk%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;1594&quot; height=&quot;1436&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;1436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;11864&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11866&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11868&quot; data-ke-size=&quot;size16&quot;&gt;statisticItem collection 개선 후 stat&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2269806638&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1616&quot; data-height=&quot;1402&quot; data-id=&quot;2c3ffd00-e765-4964-9e4b-fede537f5b9e&quot; data-collection=&quot;contentId-2269806638&quot; data-file-name=&quot;image-20220901-090738.png&quot; data-file-size=&quot;248200&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div id=&quot;newFileExperienceWrapper&quot; data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220901-090738.png&quot; data-test-progress=&quot;1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n71Ch/btsg0Wz4ajq/EPY6CaQi2Jhi4HRlBo3JDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n71Ch/btsg0Wz4ajq/EPY6CaQi2Jhi4HRlBo3JDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n71Ch/btsg0Wz4ajq/EPY6CaQi2Jhi4HRlBo3JDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn71Ch%2Fbtsg0Wz4ajq%2FEPY6CaQi2Jhi4HRlBo3JDK%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;1616&quot; height=&quot;1402&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2번째 인덱스는 4번째 인덱스로 튜닝되어 사라질 예정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;11945&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;11947&quot; data-ke-size=&quot;size16&quot;&gt;item collection (기존 대비 약 28.4% 수준으로 개선)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개선 전 index size : 9932357632 bytes&lt;/li&gt;
&lt;li&gt;개선 후 index size : 2829393920 bytes&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;12066&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12068&quot; data-ke-size=&quot;size16&quot;&gt;statistic collection (기존대비 약 76.7% 수준으로 개선, 사라질 예정인 index 제거시, 약 57.6% 까지 개선 가능)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개선 전 index size : 175649067008 bytes&lt;/li&gt;
&lt;li&gt;개선 후 index size : 134853246976 bytes (사라질 예정인 index 제거시, 101268742144 bytes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;12272&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12276&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12278&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;5.-후기-및-다음-작업에-대한-예고&quot; style=&quot;color: #000000;&quot; data-renderer-start-pos=&quot;12280&quot;&gt;5. 후기 및 다음 작업에 대한 예고&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;12302&quot; data-ke-size=&quot;size16&quot;&gt;인덱스는 db query 성능을 dramatic하게 향상시켜주는 요소이다. 그러나 이를 효율적이지 못한 방향을 쓰고, 정리를 지속적으로 하지 않다 보니 이에 대한 비용이 많이 발생하는 상황이 발생하였다. 작업 후에 상당한 자원이 삭제가 된 것을 겪고나서는 운영중인 서비스에 대한 인덱스에 대해서 지속적으로 리뷰를 하고, 새로운 인덱스를 생성시 개발자 간 소통을 통해서 효율적으로 설계하는 쪽으로 규칙을 정했다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12533&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12535&quot; data-ke-size=&quot;size16&quot;&gt;다음 작업의 경우, 샤딩이다. 현재 Mongo Atlas의 경우, stard는 primary 1, secondary 2로 나누어져 있으며 단순 데이터 복제만 이루어져 있는 것을 확인하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12642&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;12644&quot; data-ke-size=&quot;size16&quot;&gt;시계열 데이터기 때문에 지속적으로 대량의 데이터가 쌓이다보면 인덱스가 있더라도 성능이 저하된다. 따라서 shard key를 데이터 생성시간 기준으로 해서 적절히 수평 샤딩을 수행하여 속도를 개선해야할거같다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>IT/NOSQL</category>
      <category>index</category>
      <category>MongoDB</category>
      <category>인덱스튜닝</category>
      <category>최적화</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/46</guid>
      <comments>https://coding-deer.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 22 May 2023 14:51:42 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] Timeseries Collection에 대한 연구</title>
      <link>https://coding-deer.tistory.com/45</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Content&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Content&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-fabric-macro=&quot;9590feb371e6ba184f8bae94f9d3875e&quot; data-macro-body=&quot;true&quot; data-macro-parameters=&quot;{&amp;quot;minLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;maxLevel&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;7&amp;quot;},&amp;quot;style&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;none&amp;quot;}}&quot;&gt;
&lt;div data-cssliststyle=&quot;none&quot; data-headerelements=&quot;H1,H2,H3,H4,H5,H6&quot; data-hasbody=&quot;false&quot; data-macro-name=&quot;toc&quot; data-macro-id=&quot;9590feb371e6ba184f8bae94f9d3875e&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a href=&quot;#0.-Table-Of-Content&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Content&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#1.-Time-Series-Collection을-연구하게-된-계기&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. Time Series Collection을 연구하게 된 계기&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#2.-Time-Series-Data란-무엇인가?&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. Time Series Data란 무엇인가?&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#3.-일반-Collection과-Time-Series-Collection의-동작-방식&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 일반 Collection과 Time Series Collection의 동작 방식&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.1&quot;&gt;&lt;a href=&quot;#3.1.-일반-Collection&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.1. 일반 Collection&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.2&quot;&gt;&lt;a href=&quot;#3.2.-TimeSeries-Collection&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.2. TimeSeries Collection&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a href=&quot;#4.-적용-방법&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 적용 방법&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.1&quot;&gt;&lt;a href=&quot;#4.1.-MongoDB-설치&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.1. MongoDB 설치&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.2&quot;&gt;&lt;a href=&quot;#4.2.-NestJS-초기-설정&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2. NestJS 초기 설정&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.2.1&quot;&gt;&lt;a href=&quot;#4.2.1.-mongoose-설치&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2.1. mongoose 설치&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.2.2&quot;&gt;&lt;a href=&quot;#4.2.2.-mongoose-connection-설정&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2.2. mongoose connection 설정&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.2.3&quot;&gt;&lt;a href=&quot;#4.2.3.-Schema-작성&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2.3. Schema 작성&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.2.4&quot;&gt;&lt;a href=&quot;#4.2.4.-Schema를-injection-받아서-사용할-수-있도록-선언해준다.&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.2.4. Schema를 injection 받아서 사용할 수 있도록 선언해준다. &lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5.3&quot;&gt;&lt;a href=&quot;#4.3.-MongoDB-data-auto-delete-by-ttl-설정-(Optional)&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4.3. MongoDB data auto delete by ttl 설정 (Optional)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;a href=&quot;#5.-%데이터-생성&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5. 데이터 생성&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;7&quot;&gt;&lt;a href=&quot;#6.-일반-collection과-Timeseries-collection-비교분석&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6. 일반 collection과 Timeseries collection 비교분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;7.1&quot;&gt;&lt;a href=&quot;#6.1.-%수집된-데이터의-크기와-인덱스의-크기&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6.1. 수집된 데이터의 크기와 인덱스의 크기&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;7.1.1&quot;&gt;&lt;a href=&quot;#6.1.1.-일반-Collection&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6.1.1. 일반 Collection&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;7.1.2&quot;&gt;&lt;a href=&quot;#6.1.2.-Timeseries-Collection&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6.1.2. Timeseries Collection&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;7.1.3&quot;&gt;&lt;a href=&quot;#6.1.3.-비교&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6.1.3. 비교&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;7.2&quot;&gt;&lt;a href=&quot;#6.2.-쿼리-성능&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6.2. 쿼리 성능&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;8&quot;&gt;&lt;a href=&quot;#7.-직접-겪으며-정리한-TimeSeries-Collection-사용시-유의해야-할-&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;7. 직접 겪으며 정리한 TimeSeries Collection 사용시 유의해야 할 점&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;8.1&quot;&gt;&lt;a href=&quot;#7.1.-delete,-update&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;7.1. delete, update &lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;8.2&quot;&gt;&lt;a href=&quot;#7.2.-metaField&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;7.2. metaField&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;8.3&quot;&gt;&lt;a href=&quot;#7.3.-timeField&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;7.3. timeField&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;9&quot;&gt;&lt;a href=&quot;#8.-그-이외에-유의해야-할-점&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;8. 그 이외에 유의해야 할 점&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;10&quot;&gt;&lt;a href=&quot;#9.-Reference&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;9. Reference&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-Time-Series-Collection을-연구하게-된-계기&quot; data-renderer-start-pos=&quot;25&quot;&gt;1. Time Series Collection을 연구하게 된 계기&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;63&quot; data-ke-size=&quot;size16&quot;&gt;각 현장의 장비에 대해 데이터를 수집하기 위해 현재 mongodb를 도입하여 운영중이다. 그러나 성능이 좋지 않은 환경에서 초 단위의 데이터를 binary read, binary parsing, mongodb document create 및 validation까지 한꺼번에 하다보니 자원의 한계가 있었으며, 또한 한정된 ssd(512GB)에 데이터를 적재하다보니 최대한 local storage를 효율적으로 사용해야하는 상황에 직면하였다. 이러한 이유로 데이터 크기 자체를 경량화 하고, 이를 필요할 때만 binary를 cloud 환경에서 parsing 하는 환경을 생각하게 되었다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;392&quot; data-ke-size=&quot;size16&quot;&gt;이 생각의 시작으로 로컬 db의 경량화를 먼저 생각하던 중, Mongodb 5.0에 새로 등장한 Time Series Collection을 보게되었다. 대표적인 특징은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시계열 데이터에 대해 일반 collection data 보다 차지하는 데이터의 크기가 작다.&lt;/li&gt;
&lt;li&gt;자동으로 시간에 대해 index를 제공한다.&lt;/li&gt;
&lt;li&gt;TTL을 이용하여 원하는 시간이 지나면 삭제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;615&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;617&quot; data-ke-size=&quot;size16&quot;&gt;위 3가지 특징이 현재 상황에서 필요한 부분이었기 때문에 MongoDB의 Time Series Collection을 연구해보게 되었다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;694&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;696&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;698&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Time-Series-Data란-무엇인가?&quot; data-renderer-start-pos=&quot;700&quot;&gt;2. Time Series Data란 무엇인가?&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간에 따라 변하며, 일정시간 간격으로 배치된 데이터의 수열&lt;/li&gt;
&lt;li&gt;시간에 따라 변하는 object property외에는 일반적으로 metaData성으로, update가 거의 일어나지 않는다.&lt;/li&gt;
&lt;li&gt;대표적으로 sensor 등에서 나오는 실시간 데이터가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;877&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;879&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;881&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-일반-Collection과-Time-Series-Collection의-동작-방식&quot; data-renderer-start-pos=&quot;883&quot;&gt;3. 일반 Collection과 Time Series Collection의 동작 방식&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h3 id=&quot;3.1.-일반-Collection&quot; data-renderer-start-pos=&quot;932&quot; data-ke-size=&quot;size23&quot;&gt;3.1. 일반 Collection&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;952&quot; data-ke-size=&quot;size16&quot;&gt;일반 Collection의 경우, 각 block에 sequencial하게 데이터가 쌓인다. 그리고 이러한 데이터들을 쉽게 찾고, 삭제하기 위해 하나 또는 그 이상의 인덱스를 사용하기도 한다. 또한 필요시 replication을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;36a91311-f16b-4f33-87b2-729d4144a413.gif&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lMIcP/btrv8cp190s/URLeaOgOgcY3Pu2UyTq24K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lMIcP/btrv8cp190s/URLeaOgOgcY3Pu2UyTq24K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lMIcP/btrv8cp190s/URLeaOgOgcY3Pu2UyTq24K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lMIcP/btrv8cp190s/URLeaOgOgcY3Pu2UyTq24K/img.gif&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;504&quot; height=&quot;500&quot; data-filename=&quot;36a91311-f16b-4f33-87b2-729d4144a413.gif&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1086&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;504&quot; data-height=&quot;500&quot; data-id=&quot;d37d3913-2252-4595-8915-77a7c62fc849&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;1.gif&quot; data-file-size=&quot;2855181&quot; data-file-mime-type=&quot;image/gif&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;1.gif&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;1091&quot; data-ke-size=&quot;size16&quot;&gt;document를 받게되면 해당 document들은 순차적으로 disk의 block에 쌓이게 된다. 우리는 block에 쌓은 데이터에 access 하기 위해 인덱스를 생성하곤 한다. 그러나 이는 장기적으로 보았을 때, document를 read하는 데에서 문제가 발생한다. 특정 시간대의 device data를 보기 위해서는 여러 작은 document들을 fetch 해야하며, 이를 위해 컴퓨터는 넓게 분산된 disk block를 뒤지기 시작한다. 결론적으로, 읽어야 하는 block에 대해 동일한 DB의 cache를 사용하게 되는 비효율성이 생긴다. 이 과정에서 많은 resource가 낭비가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;845b5195-dc4a-44cf-b923-bca6c8d8442c.gif&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfTilq/btrwdks0JWE/oPESXJH1HYWJDunhK0YJuk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfTilq/btrwdks0JWE/oPESXJH1HYWJDunhK0YJuk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfTilq/btrwdks0JWE/oPESXJH1HYWJDunhK0YJuk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bfTilq/btrwdks0JWE/oPESXJH1HYWJDunhK0YJuk/img.gif&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;504&quot; height=&quot;500&quot; data-filename=&quot;845b5195-dc4a-44cf-b923-bca6c8d8442c.gif&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1431&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1431&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.2.-TimeSeries-Collection&quot; data-renderer-start-pos=&quot;1433&quot; data-ke-size=&quot;size23&quot;&gt;3.2. TimeSeries Collection&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;504&quot; data-height=&quot;500&quot; data-id=&quot;1c5e2004-b702-413e-8a47-6f5b404c8474&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;2.gif&quot; data-file-size=&quot;1709323&quot; data-file-mime-type=&quot;image/gif&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;2.gif&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;1464&quot; data-ke-size=&quot;size16&quot;&gt;TimeSeries Collection을 사용하게되면, MongoDB는 같은 source의 데이터를 비슷한 시간대의 다른 데이터와 함께 같은 block에 저장하도록 프로세스를 진행한다. 블록은 디스크에 종속적이기 떄문에 가득차면, 자동적으로 또다른 블록을 생성한다. 여기서 가장 중요한 것은 각 블록이 하나의 source와 특정 시간대를 다루며 이 범위를 find 하는데 도움이 되는 index 또한 가지고있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1696&quot; data-ke-size=&quot;size16&quot;&gt;즉, 하나의 블록에 대해 source, time range에 대한 인덱스를 가지고 있기 때문에, 100배 이상으로 index size를 줄일 수 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1782&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1784&quot; data-ke-size=&quot;size16&quot;&gt;데이터 저장 측면에서 뿐만 아니라, 압축에 대한 측면에서도 훨씬 좋은 측면이 있다. 시간이 지남에 따라 거의 변하지 않기 대문에, MongoDB는 비슷한 위치에 있는 데이터들을 한꺼번에 압축 할 수 있다. 이는 최소 3배에서 5배 사이의 데이터 사이즈 향상을 야기시킨다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1937&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1939&quot; data-ke-size=&quot;size16&quot;&gt;그리고, 읽기의 측면에서 원하는 데이터를 읽기 위해 필요없는 인접한 query에 해당하는 데이터들을 읽을 필요가 없기 때문에 최소 3~5배 빠른 읽기 성능을 제공한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2034&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2036&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-적용-방법&quot; data-renderer-start-pos=&quot;2038&quot;&gt;4. 적용 방법&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h3 id=&quot;4.1.-MongoDB-설치&quot; data-renderer-start-pos=&quot;2048&quot; data-ke-size=&quot;size23&quot;&gt;4.1. MongoDB 설치&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;2065&quot; data-ke-size=&quot;size16&quot;&gt;아래 링크에서 5.0 버전 이상의 os에 맞는 mongodb를 로컬에 설치한다. 취향에 따라서 docker container를 선택해도 무방하다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2148&quot; data-ke-size=&quot;size16&quot;&gt;Install Stand alone link : &lt;a href=&quot;https://docs.mongodb.com/manual/administration/install-community/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.mongodb.com/manual/administration/install-community/&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647452502426&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add mongoose @nestjs/mongoose
npm install mongoose @nestjs/mongoose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;2148&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2179&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB Docker Image link : &lt;a href=&quot;https://hub.docker.com/_/mongo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hub.docker.com/_/mongo&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647452526372&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import { MongooseModuleOptions, MongooseOptionsFactory } from '@nestjs/mongoose';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class MongooseLocalConfigService implements MongooseOptionsFactory {
  constructor(private readonly configService: ConfigService) {}
  createMongooseOptions(): MongooseModuleOptions {
    /**
     * Mongodb Connection Configuration을 불러온다.
     */
    const mongoHost = this.configService.get&amp;lt;string&amp;gt;('MONGO_HOST');
    const mongoPort = this.configService.get&amp;lt;string&amp;gt;('MONGO_PORT');
    const mongoDatabase = this.configService.get&amp;lt;string&amp;gt;('MONGO_DATABASE');
    const mongoUsername = this.configService.get&amp;lt;string&amp;gt;('MONGO_USERNAME');
    const mongoPassword = this.configService.get&amp;lt;string&amp;gt;('MONGO_PASSWORD');

    const mongooseUrl: string =
      'mongodb://' + mongoUsername + ':' + mongoPassword + '@' + mongoHost + ':' + mongoPort + '/' + mongoDatabase;
    return {
      uri: mongooseUrl,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;2179&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2211&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2213&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4.2.-NestJS-초기-설정&quot; data-renderer-start-pos=&quot;2215&quot; data-ke-size=&quot;size23&quot;&gt;4.2. NestJS 초기 설정&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;2234&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/&quot; data-renderer-mark=&quot;true&quot;&gt;공식 문서&lt;/a&gt;로 초기 설정을 대체합니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2256&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;4.2.1.-mongoose-설치&quot; data-renderer-start-pos=&quot;2258&quot; data-ke-size=&quot;size20&quot;&gt;4.2.1. mongoose 설치&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-renderer-start-pos=&quot;2278&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같은 패키지를 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1647452500645&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add mongoose @nestjs/mongoose
npm install mongoose @nestjs/mongoose​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;2372&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;4.2.2.-mongoose-connection-설정&quot; data-renderer-start-pos=&quot;2374&quot; data-ke-size=&quot;size20&quot;&gt;4.2.2. mongoose connection 설정&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-renderer-start-pos=&quot;2405&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 mongoose option을 return 해주는 Class를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1647452524706&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import { MongooseModuleOptions, MongooseOptionsFactory } from '@nestjs/mongoose';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class MongooseLocalConfigService implements MongooseOptionsFactory {
  constructor(private readonly configService: ConfigService) {}
  createMongooseOptions(): MongooseModuleOptions {
    /**
     * Mongodb Connection Configuration을 불러온다.
     */
    const mongoHost = this.configService.get&amp;lt;string&amp;gt;('MONGO_HOST');
    const mongoPort = this.configService.get&amp;lt;string&amp;gt;('MONGO_PORT');
    const mongoDatabase = this.configService.get&amp;lt;string&amp;gt;('MONGO_DATABASE');
    const mongoUsername = this.configService.get&amp;lt;string&amp;gt;('MONGO_USERNAME');
    const mongoPassword = this.configService.get&amp;lt;string&amp;gt;('MONGO_PASSWORD');

    const mongooseUrl: string =
      'mongodb://' + mongoUsername + ':' + mongoPassword + '@' + mongoHost + ':' + mongoPort + '/' + mongoDatabase;
    return {
      uri: mongooseUrl,
    };
  }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;2488&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3548&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3550&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3552&quot; data-ke-size=&quot;size16&quot;&gt;이 클래스를 mongoose를 initialize 해주는 forRootAsync에 사용할 수 있도록 선언해준다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1647452615625&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { MongooseModule } from '@nestjs/mongoose';
import { MongooseLocalConfigService } from '@/database/config/mongooseConfig.service';
import { AppController } from './app.controller';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    MongooseModule.forRootAsync({
      useClass: MongooseLocalConfigService,
    }),
  ],
  controllers: [AppController],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span style=&quot;background-color: #000000; color: #172b4d;&quot; data-code-lang=&quot;typescript&quot; data-ds--code--code-block=&quot;&quot;&gt;&lt;span style=&quot;color: #505f79;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;4025&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;4.2.3.-Schema-작성&quot; data-renderer-start-pos=&quot;4027&quot; data-ke-size=&quot;size20&quot;&gt;4.2.3. Schema 작성&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1647452638119&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Device } from '@/device/device.schema';
import * as mongoose from 'mongoose';

@Schema({
  timeseries: {
    timeField: 'createdAt',
    metaField: 'device',
    granularity: 'seconds',
  },
})
export class RawItem {
  /**
   * Item Document Unique Id
   */
  @Prop()
  _id: string;

  /**
   * 데이터를 생상한 장비
   */
  @Prop({ required: true, type: mongoose.Schema.Types.ObjectId, ref: 'Device' })
  device: Device;
  /**
   * Item 생성일시
   */
  @Prop({ type: mongoose.Schema.Types.Date })
  createdAt: Date;

  /**
   * 장비로 부터 받은 raw data
   */
  @Prop()
  buffer: Buffer;
}

export const RawItemSchema = SchemaFactory.createForClass(RawItem);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span style=&quot;background-color: #000000; color: #172b4d;&quot; data-code-lang=&quot;typescript&quot; data-ds--code--code-block=&quot;&quot;&gt;&lt;span style=&quot;color: #505f79;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;4762&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;4.2.4.-Schema를-injection-받아서-사용할-수-있도록-선언해준다.&quot; data-renderer-start-pos=&quot;4764&quot; data-ke-size=&quot;size20&quot;&gt;4.2.4. Schema를 injection 받아서 사용할 수 있도록 선언해준다. &lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-renderer-start-pos=&quot;4812&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 프로젝트에서 사용할 schema를 선언하고, injection 받는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1647452666610&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { Device, DeviceSchema } from '@/device/device.schema';
import { Item, ItemSchema } from '@/item/item.schema';
import { StatisticItem, StatisticItemSchema } from '@/statistic-item/statistic-item.schema';
import { RawItem, RawItemSchema } from '@/raw-item/raw-item.schema';

@Module({
  imports: [
    MongooseModule.forFeature([
      { name: RawItem.name, schema: RawItemSchema },
      .
      .
      .
    ]),
  ],
  exports: [MongooseModule],
})
export class DatabaseModule {
  constructor() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;5465&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4.3.-MongoDB-data-auto-delete-by-ttl-설정-(Optional)&quot; data-renderer-start-pos=&quot;5467&quot; data-ke-size=&quot;size23&quot;&gt;4.3. MongoDB data auto delete by ttl 설정 (Optional)&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;5519&quot; data-ke-size=&quot;size16&quot;&gt;Nestjs Schema Option으로 만들 수 있는 방법이 나오면 update 예정.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5570&quot; data-ke-size=&quot;size16&quot;&gt;현재는 찾지 못해 아래와 같이 공식 문서대로 세팅하였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1647452686006&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.runCommand({
   collMod: &quot;&amp;lt;YOUR_COLLECTION_NAME&amp;gt;&quot;,
   expireAfterSeconds: &amp;lt;YOUR_EXPIRE_TIME&amp;gt;})&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;5702&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5704&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 세팅한 다음, 아래 명령어를 입력하고나서 아래 화면같이 결과가 나오게 되면 정상적으로 timeseries collection이 생성 된 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1647452712868&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.runCommand( { listCollections: 1 } )&lt;/code&gt;&lt;/pre&gt;
&lt;div data-layout=&quot;center&quot; data-width=&quot;100&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;988&quot; data-height=&quot;222&quot; data-id=&quot;a522ba8b-380e-4fed-8b0b-681d63d40441&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;image-20220203-011038.png&quot; data-file-size=&quot;38484&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220203-011038.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;5836&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;37c6d7e9-03da-45f5-8de0-e1e3504c8286.png&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rSKXM/btrv8EzJQNk/kryWL7DYApcvuWBe3EkAw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rSKXM/btrv8EzJQNk/kryWL7DYApcvuWBe3EkAw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rSKXM/btrv8EzJQNk/kryWL7DYApcvuWBe3EkAw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrSKXM%2Fbtrv8EzJQNk%2FkryWL7DYApcvuWBe3EkAw0%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;988&quot; height=&quot;222&quot; data-filename=&quot;37c6d7e9-03da-45f5-8de0-e1e3504c8286.png&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5840&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5842&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5844&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5846&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;5848&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;5.-데이터-생성&quot; data-renderer-start-pos=&quot;5850&quot;&gt;5. 데이터 생성&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;5861&quot; data-ke-size=&quot;size16&quot;&gt;modbus-serial를 이용하여 binary 형태의 buffer stream을 받아 객체로 만든 뒤, mongodb에 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1647452749606&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;createVoltageRawItemModel(connection: ModbusConnection) {
    const { modbusClient } = connection;
    return modbusClient
      .readHoldingRegisters(VOLTAGE_MAP.startAddress, VOLTAGE_MAP.addressRange)
      .then(readRegisterResult =&amp;gt; {
        const { buffer } = readRegisterResult;
        return this.bufferToDocument(buffer, connection.deviceId);
      })
      .then(async rawItem =&amp;gt; {
        await this.itemModel.insertMany(rawItem);
      });
  }
  
  
bufferToDocument(buffer: Buffer, deviceId: ObjectId) {
    const createdAt = getNowDate();
    const rawItemDocument = new this.rawItemModel({
      device: deviceId,
      buffer: buffer,
      createdAt,
    });
    return rawItemDocument;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;6649&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;6651&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;6653&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 위해 데이터를 2/2 ~ 2/7까지 실시간 데이터를 수집하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;6695&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;6.-일반-collection과-Timeseries-collection-비교분석&quot; data-renderer-start-pos=&quot;6697&quot;&gt;6. 일반 collection과 Timeseries collection 비교분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;6743&quot; data-ke-size=&quot;size16&quot;&gt;위 3번의 스키마와 동일한 조건으로 비교하기 위해 실제로 데이터를 수집하고, 분석해보고자 한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;6798&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;6.1.-수집된-데이터의-크기와-인덱스의-크기&quot; data-renderer-start-pos=&quot;6800&quot; data-ke-size=&quot;size23&quot;&gt;6.1. 수집된 데이터의 크기와 인덱스의 크기&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-renderer-start-pos=&quot;6827&quot; data-ke-size=&quot;size20&quot;&gt;6.1.1. 일반 Collection&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;411840ae-6e77-437e-a140-2f5fec7dea17.png&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jx4yv/btrwb5bzh6T/9bm3nqK26MMtEicX2meyh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jx4yv/btrwb5bzh6T/9bm3nqK26MMtEicX2meyh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jx4yv/btrwb5bzh6T/9bm3nqK26MMtEicX2meyh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJx4yv%2Fbtrwb5bzh6T%2F9bm3nqK26MMtEicX2meyh0%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;2056&quot; height=&quot;1018&quot; data-filename=&quot;411840ae-6e77-437e-a140-2f5fec7dea17.png&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;1018&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1285&quot; data-height=&quot;636&quot; data-id=&quot;6673724b-46da-4d93-a8c9-4289e42ca4bd&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;image-20220210-091042.png&quot; data-file-size=&quot;201163&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220210-091042.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쌓은 object count : 13,626,819 개&lt;/li&gt;
&lt;li&gt;데이터의 size : 2,657,229,705 byte (2.6 GB)&lt;/li&gt;
&lt;li&gt;압축된 데이터의 size : 479,129,600 byte (0.4 GB)&lt;/li&gt;
&lt;li&gt;인덱스의 size : 542,916,608 byte (0.54 GB)
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;{_id:-1} : 255,627,264 byte (0.25 GB)&lt;/li&gt;
&lt;li&gt;{createdAt:-1} : 139,464,704 (0.14 GB)&lt;/li&gt;
&lt;li&gt;{device:-1, createdAt:-1} : 147,824,640 (0.15 GB)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;7156&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7158&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;6.1.2.-Timeseries-Collection&quot; data-renderer-start-pos=&quot;7160&quot; data-ke-size=&quot;size20&quot;&gt;6.1.2. Timeseries Collection&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1264&quot; data-height=&quot;574&quot; data-id=&quot;f03e0515-f02b-461c-ab94-d368f88e1c48&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;image-20220210-091940.png&quot; data-file-size=&quot;188681&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220210-091940.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;567&quot; data-height=&quot;187&quot; data-id=&quot;3ccfa1b6-9bc0-4fc3-8079-3df967794ee5&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;image-20220210-092004.png&quot; data-file-size=&quot;35516&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220210-092004.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;30476ed1-3bb4-4287-ae44-cbe2591cf8d8.png&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXEThL/btrwdj8I79t/NM7UagWn4g7evLQeIRvKV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXEThL/btrwdj8I79t/NM7UagWn4g7evLQeIRvKV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXEThL/btrwdj8I79t/NM7UagWn4g7evLQeIRvKV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXEThL%2Fbtrwdj8I79t%2FNM7UagWn4g7evLQeIRvKV0%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;2022&quot; height=&quot;918&quot; data-filename=&quot;30476ed1-3bb4-4287-ae44-cbe2591cf8d8.png&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buBpHl/btrv6X0T33f/sjfpwLkXKdPXpn0bQkCjs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buBpHl/btrv6X0T33f/sjfpwLkXKdPXpn0bQkCjs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buBpHl/btrv6X0T33f/sjfpwLkXKdPXpn0bQkCjs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuBpHl%2Fbtrv6X0T33f%2FsjfpwLkXKdPXpn0bQkCjs1%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;908&quot; height=&quot;260&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쌓은 object count : 13,626,819 개&lt;/li&gt;
&lt;li&gt;데이터의 size : 2,194,723,580byte (2.1 GB)&lt;/li&gt;
&lt;li&gt;압축된 데이터의 size : 350,892,032 byte (0.35 GB)&lt;/li&gt;
&lt;li&gt;인덱스의 size : 798,720byte (약 0.80 MB)
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;{device:-1, createdAt:-1} : 798,720byte (약 0.80 MB)&lt;/li&gt;
&lt;li&gt;{_id:1} : 생성되지 않음&lt;/li&gt;
&lt;li&gt;{createdAt:-1} : 기본으로 timeField를 선언하면 제공된다고 문서상에서 언급하여 만들지 않음, 이 가려진 인덱스에 대해서 크기를 알 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;7537&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1642&quot; data-height=&quot;320&quot; data-id=&quot;f27acdb1-b8f7-4788-8e11-2576ba00aae3&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;image-20220304-080553.png&quot; data-file-size=&quot;192480&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220304-080553.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;7542&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7544&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7546&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;6.1.3.-비교&quot; data-renderer-start-pos=&quot;7548&quot; data-ke-size=&quot;size20&quot;&gt;6.1.3. 비교&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div data-layout=&quot;default&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-17 오전 2.48.26.png&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dENwuw/btrv8ENietJ/N1jDA0Hn5KaV0zFElPpxr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dENwuw/btrv8ENietJ/N1jDA0Hn5KaV0zFElPpxr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dENwuw/btrv8ENietJ/N1jDA0Hn5KaV0zFElPpxr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdENwuw%2Fbtrv8ENietJ%2FN1jDA0Hn5KaV0zFElPpxr1%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;631&quot; height=&quot;203&quot; data-filename=&quot;스크린샷 2022-03-17 오전 2.48.26.png&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-renderer-start-pos=&quot;7566&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;7812&quot; data-ke-size=&quot;size16&quot;&gt;위 정리된 표를 보았을 때, 데이터 크기 및 압축률에 대해서는약 0.8배였으며, 인덱스에 대해서는 0.0015배라는 극강의 효율을 보였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7861&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7869&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7869&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;7869&quot; 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-filename=&quot;fd23959e-66c8-483d-820c-f557d879e558.png&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cokE1D/btrv4yT54K0/dcmQACTqNKrWrwfwyYVwH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cokE1D/btrv4yT54K0/dcmQACTqNKrWrwfwyYVwH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cokE1D/btrv4yT54K0/dcmQACTqNKrWrwfwyYVwH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcokE1D%2Fbtrv4yT54K0%2FdcmQACTqNKrWrwfwyYVwH1%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;806&quot; height=&quot;143&quot; data-filename=&quot;fd23959e-66c8-483d-820c-f557d879e558.png&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2227109897&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;1062&quot; data-height=&quot;150&quot; data-id=&quot;6e970046-7f16-4ea2-ba27-b67d2a6e4e97&quot; data-collection=&quot;contentId-2227109897&quot; data-file-name=&quot;image-20220202-174322.png&quot; data-file-size=&quot;91164&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;
&lt;div data-testid=&quot;media-file-card-view&quot; data-test-status=&quot;complete&quot; data-test-media-name=&quot;image-20220202-174322.png&quot; data-test-progress=&quot;1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-renderer-start-pos=&quot;7890&quot; data-ke-size=&quot;size16&quot;&gt;time series collection에서 index를 운영할 때 timeField, metaField에 명시된 field를 이용하여 compound index를 생성하는 것을 추천한다고 되어 있는데, 이에 대해 위와 같이 어느정도 효과가 있는 듯 하다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8035&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-renderer-start-pos=&quot;8037&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 id=&quot;6.2.-쿼리-성능&quot; data-renderer-start-pos=&quot;8037&quot; data-ke-size=&quot;size23&quot;&gt;6.2. 쿼리 성능&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;8049&quot; data-ke-size=&quot;size16&quot;&gt;Comming soon&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8063&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;8065&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;7.-직접-겪으며-정리한-TimeSeries-Collection-사용시-유의해야-할-점&quot; data-renderer-start-pos=&quot;8067&quot;&gt;7. 직접 겪으며 정리한 TimeSeries Collection 사용시 유의해야 할 점&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h3 id=&quot;7.1.-delete,-update&quot; data-renderer-start-pos=&quot;8117&quot; data-ke-size=&quot;size23&quot;&gt;7.1. delete, update &lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 방법으로 data delete 또는 deleteMany를 할 수 없으며 아래와 같은 방법으로 해야 데이터가 지워졌었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;update 및 delete 조건문은 무조건 metaField를 이용해야했다. 첫 설계시 잘 설계를 해야한다.&lt;/li&gt;
&lt;li&gt;오직 metaField만 update가 가능했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터를 지울 때는 TTL를 이용하여 지우는 것을 공식문서에서 권장하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;8359&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;7.2.-metaField&quot; data-renderer-start-pos=&quot;8361&quot; data-ke-size=&quot;size23&quot;&gt;7.2. metaField&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;metaField는 한번 설정하면 어떠한 방법으로도 바꿀 수 없다. 따라서 첫 설계시 잘 설계를 할 필요가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경을 하기 위해서는 기존 collection을 drop 후 새로운 time series collection을 생성해야만한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;8522&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;7.3.-timeField&quot; data-renderer-start-pos=&quot;8524&quot; data-ke-size=&quot;size23&quot;&gt;7.3. timeField&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 timeField 그 자체만으로 눈에보이지 않는 인덱스를 지원하여 find시에는 유리하다. 그러나, sort를 지원해주지 않기 때문에 sort를 자주 쓰는 환경에서는 이에 대한 index를 따로 만들어야 한다. sort 실행시 아래와 같은 에러가 출력되니 참고한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647453184007&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ &quot;ok&quot; : 0,
  &quot;errmsg&quot; : &quot;PlanExecutor error during aggregation :: caused by 
              :: Sort exceeded memory limit of 104857600 bytes,
              but did not opt in to external sorting.&quot;,
  &quot;code&quot; : 292,&quot;codeName&quot; : &quot;QueryExceededMemoryLimitNoDiskUseAllowed&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;8.-그-이외에-유의해야-할-점&quot; data-renderer-start-pos=&quot;8971&quot;&gt;8. 그 이외에 유의해야 할 점&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 방법으로 document delete, index delete가 되지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;delete를 하려면 아예 collection을 drop 시켜야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;data ttl을 mongoose로 setting 하는 방법을 아직 찾지 못하였음. console 상에서만 작업했다.&lt;/li&gt;
&lt;li&gt;index에 대한 여러 기능이 제공되지 않기 때문에 설계가 자주 바뀌는 환경에서는 취약하며, 아래와 같은 기능이 제공되지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;reindex 기능을 제공하지 않음&lt;/li&gt;
&lt;li&gt;Partial, Unique, TTL index를 지원하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;metaField로 아래와 같은 type을 지원하지 않는다
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2d&lt;/li&gt;
&lt;li&gt;2dsphere&lt;/li&gt;
&lt;li&gt;text&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;grularity를 더 큰 것으로는 바꿀 수 있으나, 더 작은 것으로 수정할 수 없으며, 한번에 두단계로 큰 것으로 바꿀 수 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) second &amp;rarr; minute (O), minute &amp;rarr; second &amp;rarr; hour(x), hour &amp;rarr; minute(x)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;샤딩을 할 수 있으나, 샤딩된 timeseries collection의 granularity 및 일부 admin commands를 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;9598&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;9600&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;9.-Reference&quot; data-renderer-start-pos=&quot;9602&quot;&gt;9. Reference&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;3 3 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.mongodb.com/&quot;&gt;https://docs.mongodb.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/developer/how-to/new-time-series-collections/&quot;&gt;https://www.mongodb.com/developer/how-to/new-time-series-collections/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/blog/post/building-with-patterns-the-bucket-pattern&quot;&gt;https://www.mongodb.com/blog/post/building-with-patterns-the-bucket-pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/developer/article/Paginations_Time_Series_Collections_in_five_minutes/?tck=feathome&quot;&gt;https://www.mongodb.com/developer/article/Paginations_Time_Series_Collections_in_five_minutes/?tck=feathome&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/NOSQL</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/45</guid>
      <comments>https://coding-deer.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 17 Mar 2022 03:05:19 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB Performance를 향상시키는 전략</title>
      <link>https://coding-deer.tistory.com/44</link>
      <description>&lt;h1 data-renderer-start-pos=&quot;1&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;0.-Table-Of-Contents&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#1.-많은-인덱스를-생성하지-않는다.&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 많은 인덱스를 생성하지 않는다.&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#2.-Index-Prefix를-적극적으로-이용하자.&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. Index Prefix를 적극적으로 이용하자.&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#3.-Multi-Sorting의-경우-sort-방향-신경써서-index를-설계하자&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. Multi Sorting의 경우 sort 방향 신경써서 index를 설계하자&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a href=&quot;#4.-하나의-collection을-여러개의-collection으로-분리하자&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 하나의 collection을 여러개의 collection으로 분리하자&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;a href=&quot;#5.-MongoDB를-4.0이상-버전으로-유지하자.&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5. MongoDB를 4.0이상 버전으로 유지하자.&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;6.1&quot;&gt;&lt;a href=&quot;#5.1.-Non-blocking-Secondary-Read&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5.1. Non blocking Secondary Read&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;6.2&quot;&gt;&lt;a href=&quot;#5.2.-Multi-Transaction&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5.2. Multi Transaction&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;7&quot;&gt;&lt;a href=&quot;#6.-Reference&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6. Reference&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-많은-인덱스를-생성하지-않는다.&quot; data-renderer-start-pos=&quot;28&quot;&gt;1. 많은 인덱스를 생성하지 않는다.&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;다음 사진과 같이 인덱스를 하나의 사전이라고 생각해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;391&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/davwGc/btrftoz12iq/aATf6nPjb7f87kcNdzNxF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/davwGc/btrftoz12iq/aATf6nPjb7f87kcNdzNxF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/davwGc/btrftoz12iq/aATf6nPjb7f87kcNdzNxF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdavwGc%2Fbtrftoz12iq%2FaATf6nPjb7f87kcNdzNxF1%2Fimg.png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;391&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;87&quot; data-ke-size=&quot;size16&quot;&gt;위 같은 구조에서 collection에 name이라는 property가 &amp;ldquo;AB&amp;ldquo;라는 document를 추가한다고 가정하자. 현재 collection에 등록된 document의 name property에 의한 순서는 &amp;ldquo;A-B-C-D&amp;rdquo;로 되어있지만, 추가가 되게 되면 &amp;ldquo;A-AB-B-C-D&amp;ldquo;로 변경이 되어야 한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;263&quot; data-ke-size=&quot;size16&quot;&gt;먄약에 위처럼 데이터가 엄청 많이 추가되는 상황이라면 위 사전의 목차는 계속해서 업데이트 되어야 할 것이다. 즉, collection의 업데이트가 많은 구조의 index 또한 업데이트가 많이 일어날 것이며 index가 많을 수록 이러한 현상이 많아질 가능성이 높아진다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;416&quot; data-ke-size=&quot;size16&quot;&gt;또한 인덱스는 시스템 메모리에 상주하고 있는데, 인덱스가 많아져서 메모 디스크 공간을 메모리 처럼 사용하기 위해 가상메모리를 형성하게 된다. 이러한 상태가 지속되게되면 mongodb의 전체적인 퍼포먼스에 부정적인 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;548&quot; data-ke-size=&quot;size16&quot;&gt;이러한 이유로 index를 많이 만드는 것을 지양하는 것이 좋다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;588&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Index-Prefix를-적극적으로-이용하자.&quot; data-renderer-start-pos=&quot;590&quot;&gt;2. Index Prefix를 적극적으로 이용하자.&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;620&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB를 이용해본 개발자라면 compound index의 개념을 알 것이다. 모르는 분은 &lt;a href=&quot;https://docs.mongodb.com/manual/core/index-compound/&quot; data-renderer-mark=&quot;true&quot;&gt;여기(MongoDB Official Document)&lt;/a&gt;에서 개념을 참고한다. 아래와 같은 compound index를 예를 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632247717481&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.students.createIndex({name:1, grade:1, createdAt:1})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;806&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;808&quot; data-ke-size=&quot;size16&quot;&gt;위같은 compound index를 해석하면 다음과 같다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;842&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;name으로 asc 정렬한 다음 grade를 asc정렬하고, 마지막으로 createdAt으로 asc 정렬한 index&amp;ldquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;911&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;913&quot; data-ke-size=&quot;size16&quot;&gt;위 처럼 해석된 compound index가 존재하려면 다음과 같은 조건이 필요하다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;962&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;name으로 asc 정렬한 다음 grade를 asc 정렬한 index&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1004&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1006&quot; data-ke-size=&quot;size16&quot;&gt;또한, 위처럼 해석된 compound index가 존재하려면 다음과 같은 조건이 필요하다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1058&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;name으로 asc 정렬한 index&amp;ldquo;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1083&quot; data-ke-size=&quot;size16&quot;&gt;즉, compound index의 경우에 순서를 지킨 (compound)index가 자동으로 적용된다는 것을 알 수 있으며, &lt;a href=&quot;https://docs.mongodb.com/manual/core/index-compound/#prefixes&quot; data-renderer-mark=&quot;true&quot;&gt;공식문서&lt;/a&gt;에도 이에 대한 내용을 볼 수 있다. 정리하면, 위 index를 생성함으로써 부가적으로 사용가능해진 index는 다음과 같다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1230&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632247732756&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.students.createIndex({name:1, grade:1, createdAt:1})
db.students.createIndex({name:1, grade:1})
db.students.createIndex({name:1})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;1366&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1368&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1370&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1372&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-Multi-Sorting의-경우-sort-방향-신경써서-index를-설계하자&quot; data-renderer-start-pos=&quot;1374&quot;&gt;3. Multi Sorting의 경우 sort 방향 신경써서 index를 설계하자&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;1421&quot; data-ke-size=&quot;size16&quot;&gt;single index의 경우에는 sort 방향이 필요 없다. 위에서 언급한 사전을 예로 들면, 단순히 사전의 목차를 처음부터 보느냐 마지막꺼부터 역방향으로 보느냐 차이기 때문이다. 그러나 compound index의 경우, 정렬된 것을 다른 것을 기준으로 정렬하기 때문에 sorting 방향을 엄격하게 지킬 필요가 있다. 이를 지키지 않을 시, index를 찾는 것이 아닌 collection 상에서 full scan이 일어나게 된다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1667&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1669&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1671&quot; data-ke-size=&quot;size16&quot;&gt;아래 처럼 index를 만들었다고 가정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632247744645&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.students.createIndex({name:1, grade:1})
db.students.createIndex({name:1, grade:-1})
db.students.createIndex({grade:1, name:1})
db.students.createIndex({grade:1, name:-1})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1872&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;첫 번째 라인의 index를 이용하면 아래와 같은 index들을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632247756924&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.students.createIndex({name:1, grade:1})    -&amp;gt; original index
db.students.createIndex({name:1})             -&amp;gt; can be used by index prefix
db.students.createIndex({name:-1})            -&amp;gt; can be used inverse of index prefix
db.students.createIndex({name:-1, grade:-1}). -&amp;gt; can be used by inverse of original index&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2237&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2239&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2241&quot; data-ke-size=&quot;size16&quot;&gt;나머지 index들도 위 케이스와 같이 동일하게 적용하면 아래와 같은 find method에 대해 full scan을 할 필요 없이 index만으로 sorting 처리할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632247767541&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.find().sort({name:1, grade:1})
db.find().sort({name:1, grade:-1})
db.find().sort({name:-1, grade:1})
db.find().sort({name:-1, grade:-1})
db.find().sort({grade:1, name:1})
db.find().sort({grade:1, name:-1})
db.find().sort({grade:-1, name:1})
db.find().sort({grade:-1, name:-1})
db.find().sort({grade:1})
db.find().sort({grade:-1})
db.find().sort({name:1})
db.find().sort({name:-1})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2731&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2733&quot; data-ke-size=&quot;size16&quot;&gt;이 외의 sort를 이용하기 위해서는 추가적인 index를 설계해야할 것이며, 현재 가지고 있는 index가 얼마만큼의 sorting을 cover할 수 있는지 잘 판단하여야 한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2835&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2837&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-하나의-collection을-여러개의-collection으로-분리하자&quot; data-renderer-start-pos=&quot;2839&quot;&gt;4. 하나의 collection을 여러개의 collection으로 분리하자&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;2882&quot; data-ke-size=&quot;size16&quot;&gt;하나의 collection 내부에 많은 document를 가지게 되면 아래와 같은 현상을 자연스럽게 수반하게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;index의 size가 증가한다.&lt;/li&gt;
&lt;li&gt;index의 카디널리티가 증가한다&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #172b4d;&quot;&gt;위 상황은 아래와 같은 상황에서 인덱스를 이용하여 query result를 return 할 때, query processor가 불필요한 index 를 참조하기 때문에 퍼포먼스가 낮아질 수 있다. 따라서, 여러 collection으로 분리하는 전략이 하나의 선택이 될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3182&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3184&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;5.-MongoDB를-4.0이상-버전으로-유지하자.&quot; data-renderer-start-pos=&quot;3186&quot;&gt;5. MongoDB를 4.0이상 버전으로 유지하자.&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;3216&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;5.1.-Non-blocking-Secondary-Read&quot; data-renderer-start-pos=&quot;3218&quot; data-ke-size=&quot;size26&quot;&gt;5.1. Non blocking Secondary Read&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;3252&quot; data-ke-size=&quot;size16&quot;&gt;4.0 이전 버전에는 write가 primary에 완전히 commit 된 데이터들을 secondary에 전달완료 하기 전까지는 read operation을 block한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3350&quot; data-ke-size=&quot;size16&quot;&gt;이를 mongodb는 WiredTiger timestamp를 이용하여 update에 대한 order를 보장하고, consistency snapshot를 이용하여 secondary 복제가 일어나는 순간에는 secondary가 아닌 snapshot를 읽게 함으로써 non blocking secondary read를 구현하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3533&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3535&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;5.2.-Multi-Transaction&quot; data-renderer-start-pos=&quot;3537&quot; data-ke-size=&quot;size26&quot;&gt;5.2. Multi Transaction&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;3561&quot; data-ke-size=&quot;size16&quot;&gt;RDB의 경우, 서로 관계가 있는 데이터의 경우는 정규화를 해서 데이터를 insert하지만, mongodb의 경우 이를 정규화 하지 않고 하나의 document에 저장하는 성질을 가지고 있다. 이 이론에 기반하여 MongoDB에서 제시하는 이상적인 collection 설계에 따르면 document 하나로 데이터의 무결성이 보장이 된다. 그러나 모든것이 이상적일 수가 없기 때문에 이를 개발자들은 관계를 가진 여러개의 document에 대해 무결성을 보장하기 위해 pending, rollback을 모두 직접 구현한 2-Phase-Commit 패턴으로 해결하였다. 그러나 4.0 이상의 버전에는 multi trransaction을 지원하므로 이를 사용하는 것을 권장한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3940&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3942&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;6.-Reference&quot; data-renderer-start-pos=&quot;3944&quot;&gt;6. Reference&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://tv.naver.com/v/11267386&quot; data-renderer-mark=&quot;true&quot;&gt;DEVIEW2019 - 속도의, 속도에의한, 속도를 위한 몽고DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@marchpig/mongodb-multi-document-transactions-d51e047f811d&quot; data-renderer-mark=&quot;true&quot;&gt;SangWoo님의 multi transaction에 대한 medium 글&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.mongodb.com/v3.6/tutorial/perform-two-phase-commits/&quot; data-renderer-mark=&quot;true&quot;&gt;MongoDB Official Document - 2 Phase Commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/blog/post/secondary-reads-mongodb-40&quot; data-renderer-mark=&quot;true&quot;&gt;MongoDB Blog - Non Blocking Secondary Read&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/presentations/wiredtiger-timestamps-enforcing-correctness-in-operation-ordering-across-the-distributed-storage-layer&quot; data-renderer-mark=&quot;true&quot;&gt;MongoDB Presentation - WiredTiger Timestamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.mongodb.com/manual/core/read-preference/&quot; data-renderer-mark=&quot;true&quot;&gt;MongoDB Official Document - Read Preference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/NOSQL</category>
      <category>MongoDB</category>
      <category>mongodb index</category>
      <category>mongodb performance</category>
      <category>mongodb전략</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/44</guid>
      <comments>https://coding-deer.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 22 Sep 2021 03:19:51 +0900</pubDate>
    </item>
    <item>
      <title>[AWS S3] AWS S3 Key path 설계</title>
      <link>https://coding-deer.tistory.com/43</link>
      <description>&lt;h1 id=&quot;toc&quot; data-pm-slice=&quot;0 0 []&quot;&gt;0. Table Of Contents&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a href=&quot;#toc&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#설계배경&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. 설계 배경&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#고려해야할항목&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. 고려해야할 항목&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#awsReference&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. AWS S3 Reference 분석&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.1&quot;&gt;&lt;a href=&quot;#문제상황예시&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.1. 문제상황 예시&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.2&quot;&gt;&lt;a href=&quot;#문제상황해결&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3.2. 문제상황 해결&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a href=&quot;#실전적용&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;4. 실전 적용&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;a href=&quot;#결론&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;5. 결론 및 느낀점&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;7&quot;&gt;&lt;a href=&quot;#reference&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;6. Reference&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;설계배경&quot;&gt;1. 설계 배경&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트를 진행하던 도중 업로드한 개인 프로필 사진을 저장해야하는 기능을 개발하게 되었다. 프로필 사진을 저장하고 불러오는 단계에서 최대한의 성능을 내게 하고자 고민하게 되었다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;고려해야할항목&quot;&gt;2. 고려해야할 항목&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;사진 파일이 올라간 aws s3경로를 보고 이 사진이 어떤 사진인지 확실하게 알면 좋다.&lt;/li&gt;
&lt;li&gt;AWS S3도 내부적으로 파일을 찾을 때 성능적인 문제가 생긴다고 하던데, 이를 고려한 설계가 들어가면 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;awsReference&quot;&gt;3. AWS S3 Reference 분석&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 내부적으로 S3 Bucket에서 파일을 어떻게 분포시키느냐에 따라 search 성능이 떨어질 수 있다고 들은게 있기 때문에 이에 대해서 성능적인 이슈를 하나씩 찾아본 결과, aws official blog를 찾을 수 있었고, 이를 아래에 요약을 해보았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3에는 splitting이 필요한 keyspace를 지속적으로 모니터링하는 자동화기능이 있습니다. 내부적으로 high rate request, 너무 많은 key(aws s3 object) 등의 요소에 따라 파티션을 새로 생성하여 key를 이동시킵니다. 이러한 작업이 성능적으로 큰 영향을 미치진 않지만, 단일 파티션에 많은 key에 대해 request rate가 증가하면 이러한 작업이 많이 발생하기 때문에 사전에 내부적으로 파티셔닝이 많이 일어나지 않는 key path 설계를 하는 것이 중요합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;문제상황예시&quot; data-ke-size=&quot;size26&quot;&gt;3.1. 문제상황 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 요약 내용을 설명하기 위해 아래에 예시를 통해 알아보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당신이 쓰기 및 읽기가 많이 이루어지는 파일들을 다음과 같은 형식으로 Service라는 bucket안에 s3 key로 만들었다고 가정하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bucket/user/schedule/&amp;lt;userId&amp;gt;&lt;br /&gt;bucket/user/info/&amp;lt;userId&amp;gt;&lt;br /&gt;bucket/user/secret/&amp;lt;userId&amp;gt;&lt;br /&gt;bucket/user/company/&amp;lt;userId&amp;gt;&lt;br /&gt;bucket/user/log/&amp;lt;userId&amp;gt;&lt;/p&gt;
&lt;/blockquote&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는 아래 그림과 같이 aws key를 탐색한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;901&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oPNU8/btrdONfTAzC/KKBfFBgAabLACrCX606Z1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oPNU8/btrdONfTAzC/KKBfFBgAabLACrCX606Z1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oPNU8/btrdONfTAzC/KKBfFBgAabLACrCX606Z1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoPNU8%2FbtrdONfTAzC%2FKKBfFBgAabLACrCX606Z1k%2Fimg.png&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;901&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;위 그림과 같은 아키텍처에서 엄청나게 많은 요청이 들어왔을 때, user 디렉토리에 부하가 많이 걸릴 것이다. 이 결과로 aws s3 내부적으로는 부하를 해결하기 위해 파티셔닝이 일어날 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;문제상황해결&quot; data-ke-size=&quot;size26&quot;&gt;3.2. 문제상황 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3.1 그림에서 한 디렉토리로 request가 몰리지 않도록해보자. 가장 분포가 넓은 것을 위주로 key path를 설계를 한다면 기존 user라는 디렉토리로 과부하가 read/write 요청이 몰리지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진에서 가장 분포가 넓고 공통점을 많이 가진 key path를 재설계해보았을 때 아래와 같은 구조를 생각해볼 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bucket/&amp;lt;userId&amp;gt;/schedule&lt;br /&gt;bucket/&amp;lt;userId&amp;gt;/info&lt;br /&gt;bucket/&amp;lt;userId&amp;gt;/secret&lt;br /&gt;bucket/&amp;lt;userId&amp;gt;/etc&lt;br /&gt;bucket/&amp;lt;userId&amp;gt;/log&lt;br /&gt;.&lt;br /&gt;.&lt;br /&gt;.&lt;/p&gt;
&lt;/blockquote&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1171&quot; data-origin-height=&quot;671&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF6Egp/btrdIMQm0nZ/PL1dj9XtNRJ6uKCCweoY9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF6Egp/btrdIMQm0nZ/PL1dj9XtNRJ6uKCCweoY9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF6Egp/btrdIMQm0nZ/PL1dj9XtNRJ6uKCCweoY9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF6Egp%2FbtrdIMQm0nZ%2FPL1dj9XtNRJ6uKCCweoY9K%2Fimg.png&quot; data-origin-width=&quot;1171&quot; data-origin-height=&quot;671&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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 data-ke-size=&quot;size16&quot;&gt;1. 기존 user 디렉토리를 여러명의 유저 개개인 관점으로 쪼개어 하나의 디렉토리로 몰리지 않게 함.&lt;br /&gt;2. 유저의 속성으로 쪼갤 수 있었으나, 유저의 속성(schedule, info, secret&amp;hellip;)보다는 userId 값이 분포가 훨씬 넓기 때문에 userId로 쪼갠 것이 부하분산에 유리하다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;실전적용&quot;&gt;4. 실전 적용&lt;/h1&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;유저 프로필 사진을 업로드하고, 경로를 보고 이 사진이 어떤 사진인지 알 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;S3 key 이름에 특정인이 암시되지 않아야 한다.&lt;/li&gt;
&lt;li&gt;key를 가지고 버전관리가 되어야하며, 지난 사진은 삭제시켜야한다.&lt;/li&gt;
&lt;li&gt;프로필 사진의 크기가 여러개 존재할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사항들과 위 내용을 고려한 결과, 다음과 같은 s3 key path를 설계할 수 있었다.&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;b&gt;bucket/&amp;lt;USER_ID&amp;gt;/profile/&amp;lt;IMG_SIZE&amp;gt;/&amp;lt;TIMESTAMP&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;USER_ID&amp;gt; : 유저의 고유 id&lt;/li&gt;
&lt;li&gt;profile : 유저의 프로필 사진이라는 뜻의 path, 유저의 다른 부분들이 저장될 수 있기 때문에 이 부분을 유저의 어떤 자원인지 명시해주기로 함.&lt;/li&gt;
&lt;li&gt;&amp;lt;IMG_SIZE&amp;gt; : 200x200, 400x400같이 해당 사진의 크기가 몇인지 표시해주는 path&lt;/li&gt;
&lt;li&gt;&amp;lt;TIMESTAMP&amp;gt; : 원본 사진 파일 이름을 알 필요가 없으며, 주기적으로 aws lambda를 이용해 timestamp가 오래된것들은 삭제시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;결론&quot;&gt;5. 결론 및 느낀점&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cloud 자원 역시 근본은 데이터센터 자원을 사용하는 것이기 때문에, 최대한의 효율을 낼 수 있는 방법을 충분히 리서치를 하여 적용시키는 방법이 더 바람직하다.&lt;/li&gt;
&lt;li&gt;위 aws s3 key설계가 현재 학습 중인 mongoDB의 get 부하분산이랑 되게 비슷하다는 느낌을 받았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;reference&quot;&gt;6. Reference&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/aws/amazon-s3-performance-tips-tricks-seattle-hiring-event/&quot;&gt;https://aws.amazon.com/ko/blogs/aws/amazon-s3-performance-tips-tricks-seattle-hiring-event/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/object-keys.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/object-keys.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Cloud</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/43</guid>
      <comments>https://coding-deer.tistory.com/43#entry43comment</comments>
      <pubDate>Thu, 2 Sep 2021 01:54:18 +0900</pubDate>
    </item>
    <item>
      <title>[FaaS Service] FaaS Service를 사용시 주의해야할 점</title>
      <link>https://coding-deer.tistory.com/42</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a href=&quot;#0.-Table-Of-Contents&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;0. Table Of Contents&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#1.-Coldstart&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;1. Coldstart&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#2.-Stateless-Environment&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;2. Stateless Environment &lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#3.-%ED%95%9C%EB%8F%84&quot; data-testid=&quot;toc-item-body&quot;&gt;&lt;span&gt;&lt;span&gt;3. 한도&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;1.-Coldstart&quot; data-renderer-start-pos=&quot;24&quot;&gt;1. Coldstart&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;FaaS Service는 쓰지 않는 상태일 때는 function instance 대기 상태가 아닌 생성되지 않은 상태로 유지되다가 request가 들어올 경우, function instance가 생성되며 이에 대한 요청을 핸들링하기 시작한다. 이 때, request를 handling할 instance가 없으면 delay가 생길 수 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;231&quot; data-ke-size=&quot;size16&quot;&gt;cloud function은 다음과 같은 경우 새로운 함수 인스턴스가 생성된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cloud function을 새로 배포할 경우&lt;/li&gt;
&lt;li&gt;auto scaling으로 인해 확장되는 경우&lt;/li&gt;
&lt;li&gt;긴 시간동안 function이 호출이 되지 않았을 경우&lt;/li&gt;
&lt;li&gt;내부적인 오류로 인스턴스를 대체할 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;396&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;398&quot; data-ke-size=&quot;size16&quot;&gt;위의 경우에 대해 대책을 세우지 않으면 무거운 FaaS를 설계하였을 때, delay가 길어질 수 있으니 주의해야한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;465&quot; data-ke-size=&quot;size16&quot;&gt;이를 극복할 수 있는 방법으로 해당 function instance가 cold start가 되지 않도록 지속적으로 health check를 하는 방법이 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;555&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;557&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Stateless-Environment&quot; data-renderer-start-pos=&quot;559&quot;&gt;2. Stateless Environment &lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;function이 실행되더라도 각 요청마다 다른 함수 인스턴스에서 요청처리를 할 수 있기 때문에, 전역변수를 이용한 출력은 매번 달라질 수 있다. 그렇기 때문에 이러한 데이터는 db, cloud storage 등의 서비스를 이용하여 제어를 해야하는 것이 바람직하다. 그에 대한 이유는 아래 코드 및 실행결과를 보도록 하자.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;642&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/orAO2/btq9KoeB4Ni/6k7lN8vl0q3vyRUyyhJkwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/orAO2/btq9KoeB4Ni/6k7lN8vl0q3vyRUyyhJkwk/img.png&quot; data-alt=&quot;cloud function sample code&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/orAO2/btq9KoeB4Ni/6k7lN8vl0q3vyRUyyhJkwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2ForAO2%2Fbtq9KoeB4Ni%2F6k7lN8vl0q3vyRUyyhJkwk%2Fimg.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;642&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;cloud function sample code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;196&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IxQop/btq9KvjZC3U/u8Nmk4gEYndsPYtfpA741k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IxQop/btq9KvjZC3U/u8Nmk4gEYndsPYtfpA741k/img.png&quot; data-alt=&quot;7월 15일 오후 2시 37분 실행했을 때의 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IxQop/btq9KvjZC3U/u8Nmk4gEYndsPYtfpA741k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIxQop%2Fbtq9KvjZC3U%2Fu8Nmk4gEYndsPYtfpA741k%2Fimg.png&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;196&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;7월 15일 오후 2시 37분 실행했을 때의 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;169&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmnkTd/btq9Owh5GPB/Iwt8ZDvwoyU1JZZhuMX7Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmnkTd/btq9Owh5GPB/Iwt8ZDvwoyU1JZZhuMX7Ck/img.png&quot; data-alt=&quot;7월 15일 오후 6시 8분 실행했을 때의 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmnkTd/btq9Owh5GPB/Iwt8ZDvwoyU1JZZhuMX7Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmnkTd%2Fbtq9Owh5GPB%2FIwt8ZDvwoyU1JZZhuMX7Ck%2Fimg.png&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;169&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;7월 15일 오후 6시 8분 실행했을 때의 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;836&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;838&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;838&quot; data-ke-size=&quot;size16&quot;&gt;2시 37분 cloud function 을 invoke하고 나서, 약 3시간 30분 정도를 실행하고 있지 않다가 실행한 결과, 전역변수가 초기화 된 것을 볼 수 있다. 즉, function instance를 cold start를 하였기 때문에 전역변수가 초기화 된 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;998&quot; data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 상태값을 가진 변수 및 객체는 DB를 이용하여 다른 곳에서 캐싱 또는 저장이 되어있어야 한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1063&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1065&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-renderer-start-pos=&quot;1067&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;3.-한도&quot; data-renderer-start-pos=&quot;1067&quot;&gt;3. 한도&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;1074&quot; data-ke-size=&quot;size16&quot;&gt;GCP Cloud Function의 한도의 경우, 아래 사진을 참고하도록 한다. 자유자재로 customizing 한 다른 resource와 달리, FaaS Service는 경량화된 서비스이기 때문에 이러한 부분에서 약점을 지닌다. 서비스 설계시 정말로 FaaS를 사용해서 설계를 해도 문제가 없는 아키텍처인지 분석이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;557&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgSWRh/btq9O6Q0UTB/rC14A2uRTiThpb9X15k581/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgSWRh/btq9O6Q0UTB/rC14A2uRTiThpb9X15k581/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgSWRh/btq9O6Q0UTB/rC14A2uRTiThpb9X15k581/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgSWRh%2Fbtq9O6Q0UTB%2FrC14A2uRTiThpb9X15k581%2Fimg.png&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;557&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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-renderer-start-pos=&quot;1264&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Cloud</category>
      <category>aws lambda</category>
      <category>FaaS</category>
      <category>FaaS사용시 주의점</category>
      <category>gcp cloudfunction</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/42</guid>
      <comments>https://coding-deer.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 16 Jul 2021 02:54:24 +0900</pubDate>
    </item>
    <item>
      <title>[Docker-Compose]Docker-Compose env parsing error로 인한 삽질기록</title>
      <link>https://coding-deer.tistory.com/41</link>
      <description>&lt;h1 id=&quot;0.-Table-Of-Contents&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Contents&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a href=&quot;#0.-Table-Of-Contents&quot;&gt;0. Table Of Contents&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#1.-문제-현황-분석&quot;&gt;1. 문제 현황 분석&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;2.1&quot;&gt;&lt;a href=&quot;#1.1.-문제-상황&quot;&gt;1.1. 문제 상황&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2.2&quot;&gt;&lt;a href=&quot;#1.2.-기존-docker-version과-upgrade한-docker-version&quot;&gt;1.2. 기존 docker version과 upgrade한 docker version&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2.3&quot;&gt;&lt;a href=&quot;#1.3.-프로젝트-폴더-구조&quot;&gt;1.3. 프로젝트 폴더 구조&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2.4&quot;&gt;&lt;a href=&quot;#1.4.-명령어-call-순서&quot;&gt;1.4. 명령어 call 순서&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2.5&quot;&gt;&lt;a href=&quot;#1.5.-주요-파일-구성-확인&quot;&gt;1.5. 주요 파일 구성 확인&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#2.-문제-해결-삽질&quot;&gt;2. 문제 해결 삽질&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;3.1&quot;&gt;&lt;a href=&quot;#2.1.-문제-해결을-위한-searching&quot;&gt;2.1. 문제 해결을 위한 searching&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3.2&quot;&gt;&lt;a href=&quot;#2.2.-문제-디버깅&quot;&gt;2.2. 문제 디버깅&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3.3&quot;&gt;&lt;a href=&quot;#2.3.-디버깅내용이-맞는지에-대한-검증&quot;&gt;2.3. 디버깅내용이 맞는지에 대한 검증&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3.4&quot;&gt;&lt;a href=&quot;#2.4.-최종-문제-해결-flow&quot;&gt;2.4. 최종 문제 해결 flow&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#3.-삽질을-통해-얻은-결론-및-개인적인-프로젝트-구조에-대한-회고&quot;&gt;3. 삽질을 통해 얻은 결론 및 개인적인 프로젝트 구조에 대한 회고&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a href=&quot;#4.-Reference&quot;&gt;4. Reference&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-문제-현황-분석&quot; data-renderer-start-pos=&quot;28&quot;&gt;1. 문제 현황 분석&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-renderer-start-pos=&quot;41&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;1.1.-문제-상황&quot; data-renderer-start-pos=&quot;41&quot; data-ke-size=&quot;size26&quot;&gt;1.1. 문제 상황&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;264&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq6RBk/btq74CRPJ2M/449aew0SWWc7RQrP2bqtS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq6RBk/btq74CRPJ2M/449aew0SWWc7RQrP2bqtS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq6RBk/btq74CRPJ2M/449aew0SWWc7RQrP2bqtS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq6RBk%2Fbtq74CRPJ2M%2F449aew0SWWc7RQrP2bqtS0%2Fimg.png&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;264&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;docker version을 업그레이드 하고 나서 다음과 같이 잘 되던 .env file parsing이 잘 되지 않는 오류가 발생하였다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;136&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;136&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1.2.-기존-docker-version과-upgrade한-docker-version&quot; data-renderer-start-pos=&quot;138&quot; data-ke-size=&quot;size26&quot;&gt;1.2. 기존 docker version과 upgrade한 docker version&lt;/h2&gt;
&lt;pre id=&quot;code_1624524568160&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Mac Docker Desktop 기준으로 작성되었습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 Docker version : 3.0.0
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker compose version : 1.27.4&lt;/li&gt;
&lt;li&gt;Docker version : ?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;업그레이드 된 Docker version : 3.4.0
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker compose version : 1.29.0&lt;/li&gt;
&lt;li&gt;Docker version : 20.10.7&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;412&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1.3.-프로젝트-폴더-구조&quot; data-renderer-start-pos=&quot;414&quot; data-ke-size=&quot;size26&quot;&gt;1.3. 프로젝트 폴더 구조&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;1250&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY2H44/btq73bOc0VW/FkmkjGA6HmiwKW38xL6FAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY2H44/btq73bOc0VW/FkmkjGA6HmiwKW38xL6FAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY2H44/btq73bOc0VW/FkmkjGA6HmiwKW38xL6FAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY2H44%2Fbtq73bOc0VW%2FFkmkjGA6HmiwKW38xL6FAk%2Fimg.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;1250&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-renderer-start-pos=&quot;436&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;1.4.-명령어-call-순서&quot; data-renderer-start-pos=&quot;436&quot; data-ke-size=&quot;size26&quot;&gt;1.4. 명령어 call 순서&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-renderer-mark=&quot;true&quot;&gt;docker.sh&lt;/span&gt;를 이용하여 &lt;span data-renderer-mark=&quot;true&quot;&gt;docker-start-local.sh&lt;/span&gt; 실행&lt;/li&gt;
&lt;li&gt;&lt;span data-renderer-mark=&quot;true&quot;&gt;docker-start-local.sh&lt;/span&gt;가 &lt;span data-renderer-mark=&quot;true&quot;&gt;./docker-compose/docker-compose.postgres.yml&lt;/span&gt;를 참조하여 아래와 같은 명령어를 실행시킨다.&lt;/li&gt;
&lt;li&gt;업그레이드 전 아래 명령어는 정상적으로 동작하여 .env파일을 정상적으로 파싱하고 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1624524676610&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose \
  -p {PROJECT_NAME} \
  -f ./docker-compose/docker-compose.postgres.yml \
  -f ./docker-compose/api/docker-compose.base.yml \
  -f ./docker-compose/api/docker-compose.local.yml \
  up $build --remove-orphans&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;876&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;878&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1.5.-주요-파일-구성-확인&quot; data-renderer-start-pos=&quot;880&quot; data-ke-size=&quot;size26&quot;&gt;1.5. 주요 파일 구성 확인&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;898&quot; data-ke-size=&quot;size16&quot;&gt;혹시나 싶어서 &lt;span data-renderer-mark=&quot;true&quot;&gt;docker-compose.postgres.yml&lt;/span&gt; 파일과 &lt;span data-renderer-mark=&quot;true&quot;&gt;.env&lt;/span&gt;가 제대로 구성이 안되어있는지 확인을 해본 결과 다음과 같았다.&lt;/p&gt;
&lt;pre id=&quot;code_1624524724262&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3'

services:
  postgres:
    image: &amp;lt;PROJECT_NAME&amp;gt;/postgres
    container_name: &quot;&amp;lt;PROJECT_NAME&amp;gt;-postgres&quot;
    build:
      context: ../docker/postgres
      dockerfile: Dockerfile
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ../data/postgres/data:/var/lib/postgresql/data
      - ../docker/postgres/init/:/docker-entrypoint-initdb.d/
    ports:
      - &quot;${POSTGRES_PORT}:${POSTGRES_PORT}&quot;
    restart: unless-stopped
    ulimits:
      nproc: 65535
      nofile:
        soft: 65535
        hard: 65535
    healthcheck:
      test: [&quot;CMD&quot;, &quot;docker-healthcheck&quot;]
      interval: 30s
      timeout: 10s
      retries: 3
    logging:
      driver: &quot;json-file&quot;
      options:
        max-size: &quot;10m&quot;
        max-file: &quot;5&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624524745004&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# .env
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres&lt;/code&gt;&lt;/pre&gt;
&lt;p data-renderer-start-pos=&quot;1944&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1946&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;span data-renderer-mark=&quot;true&quot;&gt;.env&lt;/span&gt;와 &lt;span data-renderer-mark=&quot;true&quot;&gt;docker-comspose.postgres.yml&lt;/span&gt;의 환경변수가 잘 매핑이 되어 있었기 때문에 parsing error가 날리가 없다고 생각했다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2041&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2043&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-renderer-start-pos=&quot;2045&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-문제-해결-삽질&quot; data-renderer-start-pos=&quot;2045&quot;&gt;2. 문제 해결 삽질&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;2058&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2.1.-문제-해결을-위한-searching&quot; data-renderer-start-pos=&quot;2060&quot; data-ke-size=&quot;size26&quot;&gt;2.1. 문제 해결을 위한 searching&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;2086&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 어깨 너머로 배워서 docker-dompose를 사용하였지만, 문서를 상세히 보고 사용한 적이 없기 때문에 공식 document를 보면서 제일 먼저 정리하기로 했다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2185&quot; data-ke-size=&quot;size16&quot;&gt;Docker-Compose Command로 env를 정의하고, 실행했기 때문에, env file configuration 쪽을 찾아보는 도중 다음과 같은 문구를 발견 할 수 있었다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2288&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;389&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H6R6g/btq72jlpUSg/29Jxa9A5VUytao4keSejxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H6R6g/btq72jlpUSg/29Jxa9A5VUytao4keSejxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H6R6g/btq72jlpUSg/29Jxa9A5VUytao4keSejxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH6R6g%2Fbtq72jlpUSg%2F29Jxa9A5VUytao4keSejxk%2Fimg.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;389&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2294&quot; data-ke-size=&quot;size16&quot;&gt;출처 : &lt;a href=&quot;https://docs.docker.com/compose/env-file/&quot; data-renderer-mark=&quot;true&quot;&gt;Docker-compose Document&lt;/a&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2324&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2326&quot; data-ke-size=&quot;size16&quot;&gt;위 문서 중 내가 겪은 문제와 관련된 부분을 한글화하여 요약하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;text-align: left;&quot; data-renderer-start-pos=&quot;2372&quot; data-ke-size=&quot;size16&quot;&gt;1.28 미만의 Docker Compose의 경우, command가 실행된 현재 작업중인 directory에서 .env파일을 가지고 오거나, --project-directory argument에서 설장된 path에서 .env 파일을 가지고 옵니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-renderer-start-pos=&quot;2512&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이러한 모호함을 1.28 이상의 버전에서는 .env의 default path를 project directory로 제한하는 것으로 개선하였습니다. --env-file 옵션을 이용하여 기본 .env default path를 override 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-renderer-start-pos=&quot;2656&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;project directory는 다음 순서로 정의됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-renderer-start-pos=&quot;2691&quot; data-ke-size=&quot;size16&quot;&gt;1. --project-directory flag에 정의된 path&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-renderer-start-pos=&quot;2730&quot; data-ke-size=&quot;size16&quot;&gt;2. 첫번째 --file (-f)로 flag에 정의된 directory&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-renderer-start-pos=&quot;2771&quot; data-ke-size=&quot;size16&quot;&gt;3. 현재 directory&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-renderer-start-pos=&quot;2789&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2791&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2.2.-문제-디버깅&quot; data-renderer-start-pos=&quot;2793&quot; data-ke-size=&quot;size26&quot;&gt;2.2. 문제 디버깅&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;2806&quot; data-ke-size=&quot;size16&quot;&gt;기존에 사용한 docker-compose version은 1.27.4였기 때문에 위 사항에 일치하였다. 위 사항을 이용하여 docker desktop 업데이트 이후 발생한 오류를 디버깅 해보면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 루트 폴더에서 shell script를 이용하여 docker-compose command를 실행시켰다.&lt;/li&gt;
&lt;li&gt;위에서 정의된 docker-compose command에는 project directory가 정의되어 있지 않다.&lt;/li&gt;
&lt;li&gt;docker compose에 이용할 yml파일로 &lt;span data-renderer-mark=&quot;true&quot;&gt;./docker-compose/docker-compose.postgres.yml&lt;/span&gt;가 입력되었다.&lt;/li&gt;
&lt;li&gt;project directory 정의 순서에 따라, &lt;span data-renderer-mark=&quot;true&quot;&gt;./docker-compose/&lt;/span&gt;가 project directory로 정의된다.&lt;/li&gt;
&lt;li&gt;&lt;span data-renderer-mark=&quot;true&quot;&gt;./docker-compose/&lt;/span&gt;에는 .env 파일이 없다.&lt;/li&gt;
&lt;li&gt;없는 파일을 참조하였기 때문에 yaml파일 내부에서 ${}로 정의한 변수들이 전부 빈 string으로 대체된다.&lt;/li&gt;
&lt;li&gt;postgresql port는 필수로 필요하지만, 입력이 되지 않았기 때문에 에러가 발생하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;3377&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3379&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2.3.-디버깅내용이-맞는지에-대한-검증&quot; data-renderer-start-pos=&quot;3381&quot; data-ke-size=&quot;size26&quot;&gt;2.3. 디버깅내용이 맞는지에 대한 검증&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;3405&quot; data-ke-size=&quot;size16&quot;&gt;project directory인 &lt;span data-renderer-mark=&quot;true&quot;&gt;./docker-compose/&lt;/span&gt;에 루트 프로젝트 디렉토리에 있는 .env 파일을 다음 사진과 같이 복붙하여 .env파일을 하나 더 만든 다음 기존에 만들어 놓은 쉘스크립트를 이용하여 docker-compose를 실행시킨 결과, 정상적으로 docker가 빌드가 되어 정상적으로 실행까지 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;1192&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q8A26/btq74izkkzz/qhesJto3K6Wc5MebrYqZM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q8A26/btq74izkkzz/qhesJto3K6Wc5MebrYqZM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q8A26/btq74izkkzz/qhesJto3K6Wc5MebrYqZM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq8A26%2Fbtq74izkkzz%2FqhesJto3K6Wc5MebrYqZM1%2Fimg.png&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;1192&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2850&quot; data-origin-height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by1k8O/btq70DraLAw/lydcrbj0JR6jErNtPj9OIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by1k8O/btq70DraLAw/lydcrbj0JR6jErNtPj9OIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by1k8O/btq70DraLAw/lydcrbj0JR6jErNtPj9OIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby1k8O%2Fbtq70DraLAw%2Flydcrbj0JR6jErNtPj9OIk%2Fimg.png&quot; data-origin-width=&quot;2850&quot; data-origin-height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-renderer-start-pos=&quot;3599&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;2.4.-최종-문제-해결-flow&quot; data-renderer-start-pos=&quot;3599&quot; data-ke-size=&quot;size26&quot;&gt;2.4. 최종 문제 해결 flow&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;3620&quot; data-ke-size=&quot;size16&quot;&gt;docker-compose 1.28.6 version release note를 보면 &lt;span data-renderer-mark=&quot;true&quot;&gt;--env-file&lt;/span&gt; flag는 현재 작업중인 directory를 참조하도록 고쳐졌다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1944&quot; data-origin-height=&quot;786&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGhyrE/btq745sHmbl/EYw0npc3GOvqkKWprmSyL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGhyrE/btq745sHmbl/EYw0npc3GOvqkKWprmSyL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGhyrE/btq745sHmbl/EYw0npc3GOvqkKWprmSyL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGhyrE%2Fbtq745sHmbl%2FEYw0npc3GOvqkKWprmSyL0%2Fimg.png&quot; data-origin-width=&quot;1944&quot; data-origin-height=&quot;786&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3727&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3727&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3727&quot; data-ke-size=&quot;size16&quot;&gt;env file을 제대로 인식 시켜주지 못하고 있기 때문에 이에 대해 명확히 설정을 해 줄 필요가 있다. compose에 사용되는 파일이 root project directory에 존재하기 때문에, docker-compose command 를 실행시킬 때, &lt;span data-renderer-mark=&quot;true&quot;&gt;--env-file&lt;/span&gt; flag를 이용하여 명확하게 내가 사용하고자 하는 .env를 아래와 같이 명시해주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI25Rb/btq70DxVtgd/oYSUhWhgXTKAY0tGrhLKpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI25Rb/btq70DxVtgd/oYSUhWhgXTKAY0tGrhLKpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI25Rb/btq70DxVtgd/oYSUhWhgXTKAY0tGrhLKpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI25Rb%2Fbtq70DxVtgd%2FoYSUhWhgXTKAY0tGrhLKpk%2Fimg.png&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;3939&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-renderer-start-pos=&quot;3941&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 data-renderer-start-pos=&quot;3941&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;3.-삽질을-통해-얻은-결론-및-개인적인-프로젝트-구조에-대한-회고&quot; data-renderer-start-pos=&quot;3941&quot;&gt;3. 삽질을 통해 얻은 결론 및 개인적인 프로젝트 구조에 대한 회고&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;3980&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 실행환경에 대해서 정의할 때에는 최대한 사용할 파일들에 대해 command 또는 yaml, script에 정확히 명시를 하여 이러한 오류를 피해가게 하고, 다른 사람이 script를 읽었을 때 어떤 파일을 사용하는지 이해하기 쉽게 하는 것이 중요하다고 생각이 되었다. 향후, 위 문제처럼 다른 파일을 참조하고 있지만 묵시적인 방식으로 파일을 참조하고 있다면 명시적으로 나는 이걸 쓸거다라는 코드를 추가를 해야겠다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4217&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4219&quot; data-ke-size=&quot;size16&quot;&gt;+ env에 docker compose에서 쓰는 environment와 Dockerfile에서 사용하는 environment가 혼재하고있는데 향후 체계적으로 environment를 관리하기 위해서는 이를 분리하여 명확하게 어디서 쓰는 environment인지 정의하면 훨씬 더 좋을거같다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4382&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4384&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-renderer-start-pos=&quot;4386&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;4.-Reference&quot; data-renderer-start-pos=&quot;4386&quot;&gt;4. Reference&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;4400&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-inline-card=&quot;true&quot; data-card-url=&quot;https://docs.docker.com/compose/environment-variables/&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://docs.docker.com/compose/environment-variables/&quot; data-testid=&quot;inline-card-resolved-view&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Environment variables in Compose&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4404&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-inline-card=&quot;true&quot; data-card-url=&quot;https://docs.docker.com/compose/release-notes/#1286&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://docs.docker.com/compose/release-notes/#1286&quot; data-testid=&quot;inline-card-resolved-view&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Docker Compose release notes&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;4404&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Cloud</category>
      <category>.env</category>
      <category>docker</category>
      <category>docker-compose</category>
      <category>env parsing</category>
      <category>삽질</category>
      <category>컨테이너</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/41</guid>
      <comments>https://coding-deer.tistory.com/41#entry41comment</comments>
      <pubDate>Thu, 24 Jun 2021 18:01:18 +0900</pubDate>
    </item>
    <item>
      <title>[Middleware] ASGI(Asynchronous Server Gateway Interface) vs WSGI(WebSErver Gateway Interface)</title>
      <link>https://coding-deer.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;0.-Table-Of-Content&quot; data-renderer-start-pos=&quot;1&quot;&gt;0. Table Of Content&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;1&quot;&gt;&lt;a href=&quot;#0.-Table-Of-Content&quot;&gt;0. Table Of Content&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#1.-Server-Gateway-Interface가-왜-필요한가?&quot;&gt;1. Server Gateway Interface가 왜 필요한가?&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#2.-WSGI-(Web-Server-Gateway-Interface)&quot;&gt;2. WSGI (Web Server Gateway Interface)&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#3.-ASGI-(Asynchronous-Server-Gateway-Interface)&quot;&gt;3. ASGI (Asynchronous Server Gateway Interface)&lt;/a&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.1&quot;&gt;&lt;a href=&quot;#3.1.-기존-WSGI에는-어떤-문제점이-있었는가?&quot;&gt;3.1. 기존 WSGI에는 어떤 문제점이 있었는가?&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.2&quot;&gt;&lt;a href=&quot;#3.2.-ASGI는-어떤-방식으로-WSGI의-문제점을-해결했는가?&quot;&gt;3.2. ASGI는 어떤 방식으로 WSGI의 문제점을 해결했는가?&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;4.3&quot;&gt;&lt;a href=&quot;#3.3.-간단한-ASGI-Application-예제&quot;&gt;3.3. 간단한 ASGI Application 예제&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a href=&quot;#4.-Reference&quot;&gt;4. Reference&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;1.-Server-Gateway-Interface가-왜-필요한가?&quot; data-renderer-start-pos=&quot;29&quot;&gt;1. Server Gateway Interface가 왜 필요한가?&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;67&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 우리가 보고 있는 웹 서비스는 브라우저를 통해서 흘러나온 웹서버의 내용들이다.&lt;br /&gt;대부분의 어플리케이션의 경우 웹과 소통하는 미들웨어를 가장 널리 사용되는 tomcat, apache를 채택하여 사용하고 있다. 아쉽게도 Tomcat, Apache는 Java기반으로 만들어졌기 때문에, Python기반의 프레임워크에서는 가장 널리 사용되는 웹 서버를 사용하기 위해서는 중간에서 Java기반 미들웨어가 말하는 것을 해석해 줄 또다른 미들웨어가 필요하게 된 것이다. 물론, 파이썬 기반의 미들웨어를 사용해도 괜찮지만, 이미 검증된 것을 포기할만큼 매력이 없거나 큰 리스크를 동반해야하기 때문에 Apache, Tomcat을 그대로 사용하고 이를 중간에서 번역해주는 python framework 전용 미들웨어를 하나 만들게 되었다. 그것이 바로 Server Gateway Interface이다. Python Server Gateway Interface의 경우, 널리 사용되는 것이 WSGI와 ASGI가 있는데, 상세 설명은 다음 챕터부터 진행 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;603&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;605&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;607&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-WSGI-(Web-Server-Gateway-Interface)&quot; data-renderer-start-pos=&quot;609&quot;&gt;2. WSGI (Web Server Gateway Interface)&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;649&quot; data-ke-size=&quot;size16&quot;&gt;기존 python 기반 framework가 Java Middleware와 통신하기 위해서는 Medusa(Python으로 작성된 middleware), mod_python(embed Python), CGI / FastCGI(invoke Python via a gateway protocol) 같은 API를 사용해야 했었다. 그러나 위에서 명시된 API들은 특정 요소만을 고려해서 제작된 API기 때문에, 해당 API에 맞는 부분만을 개발자가 바라보게 했기 때문에, 개발자들이 선호하는 특정 영역에만 시야가 한정되었다. &lt;br /&gt;그러나, 범용으로 쓰일 수 있는 WSGI의 등장으로 위의 문제점들이 사라지게 되었으며 WSGI는 &lt;a href=&quot;https://www.python.org/dev/peps/pep-3333&quot; data-renderer-mark=&quot;true&quot;&gt;PEP3333&lt;/a&gt;에 정식으로 채용(?)이 되었다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1020&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1022&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1024&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-ASGI-(Asynchronous-Server-Gateway-Interface)&quot; data-renderer-start-pos=&quot;1026&quot;&gt;3. ASGI (Asynchronous Server Gateway Interface)&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-renderer-start-pos=&quot;1075&quot; data-ke-size=&quot;size16&quot;&gt;그러나 WSGI도 시간이 지나면서 문제점이 발생하기 시작했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1112&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.1.-기존-WSGI에는-어떤-문제점이-있었는가?&quot; data-renderer-start-pos=&quot;1114&quot; data-ke-size=&quot;size23&quot;&gt;3.1. 기존 WSGI에는 어떤 문제점이 있었는가?&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;1144&quot; data-ke-size=&quot;size16&quot;&gt;WSGI가 개발 중일 당시, WSGI는 오직 웹개발을 위한 공통 기반을 제공하는 프로토콜을 만드는 것이었다. 이 덕분에 파이썬 기반 웹개발자는 프레임워크 세부사항에 신경 쓰지 않고 여러 프레임워크에서 쉽게 작업을 할 수 있었다. 그러나, WebSocket 개념이 웹 개발자 사이에서 인기를 얻기 시작했을 때, WSGI는 single, synchoronous callable한 특성을 가지고 있었기 때문에, 다음과 같은 특성을 지니고 있어 webSocket과는 맞지 않았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 Connection이 짧게 유지되는 특성을 지니고 있었기 때문에, Long-Polling HTTP와 WebSocket 같이 상대적으로 connection이 긴 특성을 지닌 Protocol과는 맞지 않았다.&lt;/li&gt;
&lt;li&gt;HTTP Request는 application내부에서 오직 하나의 path를 가질 수 있기 때문에, 여러개의 path를 통해 이벤트를 수신하는 WebSocket의 이벤트를 처리할 수 없었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;1646&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;3.2.-ASGI는-어떤-방식으로-WSGI의-문제점을-해결했는가?&quot; data-renderer-start-pos=&quot;1649&quot; data-ke-size=&quot;size23&quot;&gt;3.2. ASGI는 어떤 방식으로 WSGI의 문제점을 해결했는가?&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;1687&quot; data-ke-size=&quot;size16&quot;&gt;ASGI의 구성요소와 책임은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소켓을 종료하고 이를 connection에 매핑하는 프로토콜 서버&lt;/li&gt;
&lt;li&gt;포로토콜 서버 내부에서 실행되는 어플리케이션 연결을 인스턴스화(per 1 connection) 하며, 이벤트 메시지의 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;1826&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;WSGI와 비슷하게 ASGI도 기능이 비슷한 것처럼 보인다. 그러나, 다음요소에서 차이가 난다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection의 lifetime과 protocol을 정의하는 Connection Scope&lt;/li&gt;
&lt;li&gt;Application으로 보내지는 Connection 동안 일어날 사건에 대한 명세 Event&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1997&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;476&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/spqN3/btqVCckgO9i/YkSJ6Ykd9KX2s8YlAZBA91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/spqN3/btqVCckgO9i/YkSJ6Ykd9KX2s8YlAZBA91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/spqN3/btqVCckgO9i/YkSJ6Ykd9KX2s8YlAZBA91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FspqN3%2FbtqVCckgO9i%2FYkSJ6Ykd9KX2s8YlAZBA91%2Fimg.png&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;476&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2004&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2006&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2008&quot; data-ke-size=&quot;size16&quot;&gt;ASGI Application은 단일, 비동기 callable 속성을 지니고 있다. 수신 요청에 대한 정보를 포함하는 scope를 accept하고, 클라이언트에 이벤트를 보내고 받을 수 있는 awaitable를 보내고 받을 수 있다. 이 덕분에, ASGI Application은 WSGI의 한계점을 뛰어넘는 수신 / 발신 event를 허영할 수 있다. 그 뿐만아니라 ASGI Application은 background coroutine을 허용하기 때문에 application은 요청을 처리하면서 background에서 다른 작업도 수행할 수 있게 되었다. (ex. 외부 event를 listening 하고 있는 redis queue 등)&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2366&quot; data-ke-size=&quot;size16&quot;&gt;ASGI Application을 통해서 보내거나 받는 모든 event는 Python Dictionary Type이다. 이러한 사전 정의된 event의 format은 ASGI Application가 쉽게 다른 웹 서버에서 다른 웹서버로 쉽게 전환할 수 있게 한다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2514&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2514&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;2514&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.3.-간단한-ASGI-Application-예제&quot; data-renderer-start-pos=&quot;2516&quot; data-ke-size=&quot;size23&quot;&gt;3.3. 간단한 ASGI Application 예제&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1612331595443&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async def application(scope, receive, send):
    event = await receive()    
    ....     
    await send({&quot;type&quot;: &quot;websocket.send&quot;, ...}&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-Reference&quot; data-renderer-start-pos=&quot;2693&quot;&gt;4. Reference&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.technoarchsoftwares.com/blog/asgi/&quot;&gt;https://www.technoarchsoftwares.com/blog/asgi/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://channels.readthedocs.io/en/stable/asgi.html&quot;&gt;https://channels.readthedocs.io/en/stable/asgi.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://asgi.readthedocs.io/en/latest/&quot;&gt;https://asgi.readthedocs.io/en/latest/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/미들웨어</category>
      <category>ASGI</category>
      <category>ASGI vs WSGI</category>
      <category>wsgi</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/40</guid>
      <comments>https://coding-deer.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 17 Feb 2021 10:11:04 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 순차적 프로세스의 비동기식 프로세스로의 전환 삽질</title>
      <link>https://coding-deer.tistory.com/37</link>
      <description>&lt;h1 id=&quot;id-[Spring]-1.서론및얽힌이야기&quot;&gt;1. 서론 및 얽힌 이야기&lt;/h1&gt;
&lt;p&gt;내부 시스템에 대해 지속적으로 healthCheck를 하는 Spring Boot기반 서비스를 개발한 적이 있다. DB에 등록된 시스템 리스트를 가져와 순차적으로 healthCheck를 실행하는 방식의 서비스이다.&lt;/p&gt;
&lt;p&gt;그러나, 10/12 HealthCheck Target별로 HealthCheck 시점의 정합성이 맞지 않는 문제가 발생하였다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문제 상황 서술 전에 적용중인 시스템 아키텍처 및 HealthChecker 서비스에 대한 개요를 먼저 풀어본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6kNjk/btqKNmTbxYM/d75nUEMiuWUHvGf6fkufE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6kNjk/btqKNmTbxYM/d75nUEMiuWUHvGf6fkufE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6kNjk/btqKNmTbxYM/d75nUEMiuWUHvGf6fkufE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6kNjk%2FbtqKNmTbxYM%2Fd75nUEMiuWUHvGf6fkufE1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Group의 WAS에 대해 배포를 진행하였다.&lt;/li&gt;
&lt;li&gt;배포가 진행되면, 해당 인스턴스로의 직접적인 호출에 대해서는 동작하지 않게 된다.&lt;/li&gt;
&lt;li&gt;static file을 제외한 모든 경로에 대해서 reverse proxy를 통하여 데이터가 was로 전달된다.&lt;/li&gt;
&lt;li&gt;healthCheck를 하는 경로 /api/healthcheck는 was에 설계되어 있기 때문에 was가 동작하지 않으면 web도 동작하지 않는다.&lt;/li&gt;
&lt;li&gt;WAS에 APP이 deploy되기 까지 약 25초의 시간이 소모되며 WEB이 배포되기 까지 5초의 시간이 소모된다.&lt;/li&gt;
&lt;li&gt;최초의 healthCheck가 실패하면, 5초 간격으로 추가 5회의 재시도를 진행하며 모두 실패하여야만 최종 실패로 간주된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 서술된 환경에서 발생한 문제를 시간 순서대로 다이어그램과 함께 아래에 서술하겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/niRo0/btqKRNoNIZD/k6QVyKtVwCmr8XkdLRuQfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/niRo0/btqKRNoNIZD/k6QVyKtVwCmr8XkdLRuQfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/niRo0/btqKRNoNIZD/k6QVyKtVwCmr8XkdLRuQfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FniRo0%2FbtqKRNoNIZD%2Fk6QVyKtVwCmr8XkdLRuQfK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 같이 같은 시점에서 healthCheck를 진행했으면 같은 시점의 결과가 출력되어야 하나, 순차적으로 실행하는 도중 healthCheck Error로 인해 재시도를 함으로써 다음 healthCheck가 미뤄졌다.&lt;/p&gt;
&lt;p&gt;그 결과, WAS와 WEB의 healthCheck의 시점의 차이가 커져 시점에 대한 일관성이 맞지 않는 문제가 발생하게 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문제를 해결하기 위해 delay없이 최대한 모든 target에 대해 비슷한 시점에 실행하여야 했기때문에, thread를 이용함으로써 동시다발적으로 최대한 같은 시점에서 target healthCheck를 진행해보기로 하였다.&lt;/p&gt;
&lt;p&gt;그 중 @Async를 활용한 Thread를 이용하여 개선작업을 하기로 하고 문제가 되는 코드를 추려낸 결과, 다음과 같았다.&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]-2.문제의코드&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;pre id=&quot;code_1602649662642&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MonitoringSystemService {
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 
    @Autowired
    private MessageRepository messageRepo;
 
    @Autowired
    private MonitoringSystemRepository monitoringRepo;
 
 
 
    /*
     * 스케줄러 method
     *
     * @parameter
     * @return
     *          Type : void
     * @cycle period : 모든 target의 healthCheck가 끝난 후, 15초 대기
     */
 
    @Scheduled(fixedDelay = 15000)
    @Transactional
    public void scheduledMonitoringService() throws Exception {
 
 
        try {
 
            this.setPid();
 
            List&amp;lt;MonitoringSystem&amp;gt; systemInfoList = monitoringRepo.getAllMonitoringSystemList();
 
 
            //시스템별 상태 체크 및 톡 발송 실행
            for( MonitoringSystem systemInfo : systemInfoList) {
 
 
 
                switch(systemInfo.getMonFlagCd()) {
 
                    //RealTime 호출 (엔드포인트 호출형)
                    case &quot;R&quot;:
                            logger.info(&quot;모니터링 유형 : 엔드포인트 호출&quot;);
                            logger.info(&quot;타겟 엔드포인트 : &quot; + systemInfo.getMonSysUrl());
                            logger.info(&quot;타겟 엔드포인트 포트 : &quot; + systemInfo.getMonSysPort());
                            logger.info(&quot;HttpRequest Method : &quot; + systemInfo.getReqMtd());
                            boolean httpRequestResult = HealthChecker.run(systemInfo.getMonSysUrl(), systemInfo.getReqMtd());
 
 
                            systemInfo = this.sendMessage(systemInfo, httpRequestResult);
                            this.modifySystemInfo(systemInfo, httpRequestResult);
 
                        break;
 
                    //Static 호출 (직접적인 쿼리 실행)
                    case &quot;S&quot;:
                        logger.info(&quot;모니터링 유형 : 직접적인 쿼리&quot;);
                        logger.info(&quot;실행 쿼리 : &quot; + systemInfo.getMonSysQury());
 
                        boolean scheduledSystemStatus = monitoringRepo.getScheduledSystemStatus(systemInfo.getMonSysQury());
                        logger.info(&quot;쿼리 업데이트 정상 여부 : &quot; + String.valueOf(scheduledSystemStatus));
 
                        systemInfo = this.sendMessage(systemInfo, scheduledSystemStatus);
                        this.modifySystemInfo(systemInfo, scheduledSystemStatus);
                        break;
                }
 
                logger.info(&quot;=======================================================================================================&quot;);
            }
 
        }catch (Exception e) {
            logger.error(e.getMessage());
        }
 
        logger.info(&quot;#################################################################################################스케줄러 끝난 시간 : &quot; + getNowTime());
 
 
    }
 
 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1602649939972&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Http5xxErrorRetryHandler implements ServiceUnavailableRetryStrategy {
 
    private final Set&amp;lt;Integer&amp;gt; retryableErrorCodes = new HashSet&amp;lt;&amp;gt;(Arrays.asList(500, 503, 502));
 
    private static final Logger logger = LoggerFactory.getLogger(Http5xxErrorRetryHandler.class);
 
    @Override
    public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
 
 
        int statusCode = response.getStatusLine().getStatusCode();
 
 
 
        if (executionCount &amp;gt; 5) {
            logger.error(&quot;재시도 5회 초과로 에러처리 합니다.&quot;);
            return false;
        }
 
        if (retryableErrorCodes.contains(statusCode)) {
            logger.warn(statusCode + &quot;오류 발생&quot;);
            logger.info(&quot;재시도 횟수 : &quot; + executionCount);
            return true;
        }
 
        return false;
    }
 
    @Override
    public long getRetryInterval() {
        return 5000;
    }
 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 2개의 코드로 인해 최초 health check가 실패한 경우, 다음 healthCheck 대상으로 넘어가기까지 최소 30초 이상의 시간이 소모되기 때문에 동시성이 보장되지 않고 있는 상황이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]-3.개선코드및작업내용&quot;&gt;2. 개선코드 및 작업내용&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;순차적으로 healthCheck를 하는 것이 아닌 thread를 이용하여 동시다발적으로 처리할 수 있도록 하기 위해 가장 먼저 thread를 실행시킬 executor를 생성해주어야 한다.&lt;/p&gt;
&lt;p&gt;나는 아래와 같이 AsyncConfigurer를 이용하여 세부설정이 완료된 executor 객체를 bean으로 만들어 어디서든 이용 할 수 있도록 하였으며, 세부 설정은 아래 코드를 참조한다.&lt;/p&gt;
&lt;pre id=&quot;code_1602650812737&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableAsync
public class AsyncThreadConfig implements AsyncConfigurer{
 
 
 
    // 기본 thread 개수
    private static int THREAD_CORE_POOL_SIZE = 5;
 
    // 최대 thread 개수
    private int THREAD_MAX_POOL_SIZE = 10;
 
    // Thread Queue 사이즈
    private static int THREAD_QUEUE_CAPACITY = 5;
 
    private static String THREAD_NAME = &quot;healthCheckExecutor&quot;;
 
    @Resource(name = &quot;healthCheckExecutor&quot;)
    private ThreadPoolTaskExecutor healthCheckExecutor;
 
    @Override
    @Bean(name = &quot;healthCheckExecutor&quot;)
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //THREAD_MAX_POOL_SIZE = monitoringSystemRepository.getMonitoringSystemCount();
 
        executor.setCorePoolSize(THREAD_CORE_POOL_SIZE);
        executor.setMaxPoolSize(THREAD_MAX_POOL_SIZE);
        executor.setQueueCapacity(THREAD_QUEUE_CAPACITY);
        executor.setBeanName(THREAD_NAME);
        executor.initialize();
        return executor;
    }
 
 
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        // TODO Auto-generated method stub
        return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
    }
 
 
    public boolean isThreadPoolAvailable(int createCnt) {
 
        boolean threadStatus = true;
 
        if ((healthCheckExecutor.getActiveCount() + createCnt) &amp;gt; (THREAD_MAX_POOL_SIZE + THREAD_QUEUE_CAPACITY)) {
            threadStatus = false;
        }
 
        return threadStatus;
    }
 
 
    public boolean isThreadPoolAvailable() {
 
        boolean threadStatus = true;
 
        if ((healthCheckExecutor.getActiveCount()) &amp;gt; (THREAD_MAX_POOL_SIZE + THREAD_QUEUE_CAPACITY)) {
            threadStatus = false;
        }
 
        return threadStatus;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 executor를 등록하였으면, 이제 executor에 넣을 비동기프로세스를 작성할 차례이다.&lt;/p&gt;
&lt;p&gt;실질적인 처리 로직이 담긴 부분에서 비동기 처리할 메소드에&amp;nbsp;@Async&amp;nbsp; Annotation을 선언하여 비동기 스레드를 통해 처리되도록 하였다.&lt;/p&gt;
&lt;p&gt;다음과 같이 비동기식으로 처리할 메소드에&amp;nbsp;@Async Annotation과 함께 앞서 선언한 Executor Bean 이름을 명시해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1602651010662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class ThreadExecutorService {
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    private MessageRepository messageRepo;
 
    @Autowired
    private MonitoringSystemRepository monitoringRepo;
 
 
    @Async(&quot;healthCheckExecutor&quot;)
    public void threadExecutor(MonitoringSystem systemInfo) {
        //시스템별 상태 체크 및 톡 발송 실행
        this.setPid();
 
        try {
            switch(systemInfo.getMonFlagCd()) {
 
                //RealTime 호출 (엔드포인트 호출형)
                case &quot;R&quot;:
                        logger.info(&quot;모니터링 유형 : 엔드포인트 호출&quot;);
                        logger.info(&quot;타겟 엔드포인트 : &quot; + systemInfo.getMonSysUrl());
                        logger.info(&quot;타겟 엔드포인트 포트 : &quot; + systemInfo.getMonSysPort());
                        logger.info(&quot;HttpRequest Method : &quot; + systemInfo.getReqMtd());
                        boolean httpRequestResult = HealthChecker.run(systemInfo.getMonSysUrl(), systemInfo.getReqMtd());
 
 
                        systemInfo = this.sendMessage(systemInfo, httpRequestResult);
                        this.modifySystemInfo(systemInfo, httpRequestResult);
 
                    break;
 
                //Static 호출 (직접적인 쿼리 실행)
                case &quot;S&quot;:
                    logger.info(&quot;모니터링 유형 : 직접적인 쿼리&quot;);
                    logger.info(&quot;실행 쿼리 : &quot; + systemInfo.getMonSysQury());
 
                    boolean scheduledSystemStatus = monitoringRepo.getScheduledSystemStatus(systemInfo.getMonSysQury());
                    logger.info(&quot;---------------------------------------시스템 정상 여부 : &quot; + String.valueOf(scheduledSystemStatus));
 
                    systemInfo = this.sendMessage(systemInfo, scheduledSystemStatus);
                    this.modifySystemInfo(systemInfo, scheduledSystemStatus);
                    break;
            }
        }catch (Exception e) {
            logger.error(e.getMessage());
        }
            logger.info(&quot;=======================================================================================================&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 필요할때 마다 비동기식으로 처리되도록 선언한 메소드를 호출해보자.&lt;/p&gt;
&lt;p&gt;앞서 선언한 AsyncThreadConfig와 비동기 프로세스에 대해 작성한&amp;nbsp;ThreadExecutorService를 IOC Container에서 불러온다.&lt;/p&gt;
&lt;p&gt;Bean을 불러온 뒤, 다음과 같이 원하는 비동기 메소드를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1602651111637&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class HealthCheckSchedulerService {
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 
    @Autowired
    private MonitoringSystemRepository monitoringRepo;
 
    @Autowired
    private ThreadExecutorService threadExecutorService;
 
    @Autowired
    private AsyncThreadConfig asyncConfig;
 
 
    @Scheduled(fixedDelay = 15000)
    @Transactional
    public void scheduledMonitoringService() throws Exception {
 
 
        try {
            List&amp;lt;MonitoringSystem&amp;gt; systemInfoList = monitoringRepo.getAllMonitoringSystemList();
 
            for( MonitoringSystem systemInfo : systemInfoList) {
 
                 try {
                        // 등록 가능 여부 체크
                        if (asyncConfig.isThreadPoolAvailable()) {
                            // task 사용
                            threadExecutorService.threadExecutor(systemInfo);
                        } else {
                            logger.info(&quot;Thread 한도 초과&quot;);
                        }
                    } catch (TaskRejectedException e) {
                        logger.info(e.getLocalizedMessage());
                    }
 
            }
 
        }catch (Exception e) {
            logger.error(e.getMessage());
        }
 
 
    }
 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]-4.개선결과&quot;&gt;3. 개선 결과&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 개선전 서비스 로그를 보면 하나의 스레드를 이용하여 순차적으로 실행하기 때문에, 중간에 내부 서비스가 장애가 날시, 그 다음 서비스에 대해 healthCheck하는 것이 재시도 한만큼 지연되고있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;860&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brTxLI/btqKSfZKu0S/8HP3umkLAoHScX5JQyEalK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brTxLI/btqKSfZKu0S/8HP3umkLAoHScX5JQyEalK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brTxLI/btqKSfZKu0S/8HP3umkLAoHScX5JQyEalK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrTxLI%2FbtqKSfZKu0S%2F8HP3umkLAoHScX5JQyEalK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;860&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그러나 개선된 다음에는 재시도 및 여러개의 요청이 각기 다른 스레드를 통해 진행되는 것을 확인할 수 있었으며 시간의 정합성을 조금 더 개선할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;860&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb8Idf/btqKSWyRaSp/yvSjKVjVBG8tixClHJ3ao0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb8Idf/btqKSWyRaSp/yvSjKVjVBG8tixClHJ3ao0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb8Idf/btqKSWyRaSp/yvSjKVjVBG8tixClHJ3ao0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb8Idf%2FbtqKSWyRaSp%2FyvSjKVjVBG8tixClHJ3ao0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;860&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Java Spring</category>
      <category>async</category>
      <category>Spring</category>
      <category>Thread</category>
      <category>비동기식</category>
      <category>삽질</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/37</guid>
      <comments>https://coding-deer.tistory.com/37#entry37comment</comments>
      <pubDate>Wed, 14 Oct 2020 13:55:09 +0900</pubDate>
    </item>
    <item>
      <title>[OpenSource] Apache Kafka</title>
      <link>https://coding-deer.tistory.com/35</link>
      <description>&lt;p&gt;Table of content&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#1.-What-is-the-Apache-Kafka&quot;&gt;1. What is the Apache Kafka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2.-Benefits-of-Apache-Kafka&quot;&gt;2. Benefits of Apache Kafka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3.-Apache-Kafka-아키텍처&quot;&gt;3. Apache Kafka 아키텍처&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#3.1.-주체-단위-아키텍처&quot;&gt;3.1. 주체 단위 아키텍처&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#producer&quot;&gt;3.1.1. Producer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#broker&quot;&gt;3.1.2. Broker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#zookeeper&quot;&gt;3.1.3. Zookeeper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3.2.-데이터-단위의-아키텍처&quot;&gt;3.2. 데이터 단위의 아키텍처&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#topic&quot;&gt;3.2.1. Topic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#partition&quot;&gt;3.2.2. Partition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#offset&quot;&gt;3.2.3. Offset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4.-Failover-과정&quot;&gt;4. Failover 과정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5.-가장-이상적인-Kafka-아키텍처&quot;&gt;5. 가장 이상적인 Kafka 아키텍처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;1.-What-is-the-Apache-Kafka&quot;&gt;1. What is the Apache Kafka&lt;/h1&gt;
&lt;p&gt;Apache Kafka는 대량의 데이터를 처리할 수 있으며, 엔드포인트간 메시지를 전달 할 수 있는 분산 발행-구독 메시징 시스템이다. Kafka 메시지는 스토리지에 유지되고 데이터 복제를 통해 데이터 손실을 방지 할 수 있다. ZooKeeper라는 동기화 서비스 기반의 시스템이며, 실시간 데이터 스트리밍 분석 툴인 Apache Storm과 Spark와 통합되어 자주 사용된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;2.-Benefits-of-Apache-Kafka&quot;&gt;2. Benefits of Apache Kafka&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;신뢰성 : 데이터의 분산, 분할, 복제를 통해 데이터의 신뢰성을 보장한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;확장성 : topic의 발행만으로 down time 없이 쉽게 확장이 가능하다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;내구성 : 분산된 commit log를 통해 클러스터간 동기화를 하며, failure시 이 로그를 통해 빠른 복구가 가능하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;퍼포먼스 : TB단위의 메시지가 시스템에 저장되어도 안정된 퍼포먼스를 제공한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;3.-Apache-Kafka-아키텍처&quot;&gt;3. Apache Kafka 아키텍처&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfzItt/btqH4tUdVEU/MPQx1u2Oj48KKhrprO1D8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfzItt/btqH4tUdVEU/MPQx1u2Oj48KKhrprO1D8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfzItt/btqH4tUdVEU/MPQx1u2Oj48KKhrprO1D8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfzItt%2FbtqH4tUdVEU%2FMPQx1u2Oj48KKhrprO1D8k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.1.-주체-단위-아키텍처&quot;&gt;3.1. 주체 단위 아키텍처&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;producer&quot;&gt;3.1.1. Producer&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;하나 이상의 topic으로 메시지를 발행하여 broker로 전송한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;각 메시지는 key, value, timestamp로 이루어져 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;새로운 메시지를 publish 할 때, 몇개의 파티션으로 나눌 것인지 결정한다. (이미 발행 된 메시지에 대해서 도 가능)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCIteH/btqH2cyytNq/edsKS2iokmhbNdNq1IYNx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCIteH/btqH2cyytNq/edsKS2iokmhbNdNq1IYNx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCIteH/btqH2cyytNq/edsKS2iokmhbNdNq1IYNx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCIteH%2FbtqH2cyytNq%2FedsKS2iokmhbNdNq1IYNx1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;출처 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kafka-python.readthedocs.io/&quot;&gt;https://kafka-python.readthedocs.io/&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(Kafka-Python API Document Homepage)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;broker&quot;&gt;3.1.2. Broker&lt;/p&gt;
&lt;p&gt;(한 VM 안에 있는 broker 전체집합을 카프카라고 한다.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;producer에게서 받은 메시지를 세부 설정(파티션, 오프셋 정책&amp;hellip;)에 따라서 저장하는 역할&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;저장된 메시지는 정책에 따라 관리되며, consumer의 메시지 pull요청에 의해서만 메시지를 전송할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;클러스터 내부에 대표 브로커를 선정하여 controller의 역할을 부여한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Controller는 클러스터 내부 모든 브로커에 대해 partition을 관리한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1.0버전 부터는 offset 정보를 broker에서 관리한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;zookeeper&quot;&gt;3.1.3. Zookeeper&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;클러스터 내부의 브로커 간 통신하기 위해서는 zookeeper를 거쳐야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VM 내부의 브로커들이 구동되기 위해서는 zookeeper가 반드시 선행으로 실행되어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;다른 VM의 Zookeeper와 논리적으로 결합되어 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;외부에서 들어온 요청에 대해서 해당 Leader Partition이 존재하는 브로커로 연결시켜준다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.2.-데이터-단위의-아키텍처&quot;&gt;3.2. 데이터 단위의 아키텍처&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djqogC/btqIarg0mGo/6yLbZYaydOMNUpqKU2mNe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djqogC/btqIarg0mGo/6yLbZYaydOMNUpqKU2mNe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djqogC/btqIarg0mGo/6yLbZYaydOMNUpqKU2mNe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjqogC%2FbtqIarg0mGo%2F6yLbZYaydOMNUpqKU2mNe1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;topic&quot;&gt;3.2.1. Topic&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;메시지가 분류되는 키워드.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;쉽게 말하면 메시지의 주제를 의미한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;하나의 토픽은 여러개의 파티션으로 구성되어 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;partition&quot;&gt;3.2.2. Partition&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;토픽 발행 시, partitioning factor * replication factor의 수만큼 partition이 생성된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Partition에는 2가지 유형이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Leader Partition : 한 토픽의 구성에 대해 외부와 인터페이스를 담당하는 Partition이다. 데이터 조회시, 이 반드시 leader Partition을 통해 데이터를 얻을 수 있으며, Partition Factor의 수만큼 Leader Partition이 생성된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Follower Partition : Leader Partition의 failure에 대비한 예비 leader Partition이다. Leader가 failure시, 이들 중 하나가 자동으로 Leader로 승급된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Follower는 Leader의 변경사항을 pull받는 방식으로 synchronize한다. 이러한 방식을 카프카에서는 ISR(In Sync Replica)라고 명명한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Follower는 Leader에 대해 pull 방식으로 동기화를 하며, 리더는 이 동기화 요청을 일정시간 받지 못하게 되면 ISR에서 해당 partition을 제외시키게 된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Partitioning 한만큼, 데이터 병렬처리가 이루어지기 때문에 처리속도가 빨라진다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5XJI/btqH51b25MF/FuS9jFLVhYZsEc10iGes7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5XJI/btqH51b25MF/FuS9jFLVhYZsEc10iGes7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5XJI/btqH51b25MF/FuS9jFLVhYZsEc10iGes7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5XJI%2FbtqH51b25MF%2FFuS9jFLVhYZsEc10iGes7K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wbEdU/btqH3xWPEXo/mTyWgv3dxSy3daFi2pj2t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wbEdU/btqH3xWPEXo/mTyWgv3dxSy3daFi2pj2t1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wbEdU/btqH3xWPEXo/mTyWgv3dxSy3daFi2pj2t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwbEdU%2FbtqH3xWPEXo%2FmTyWgv3dxSy3daFi2pj2t1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3개의 공을 한명의 포수가 받는 시간과 3명이 나누어서 받을 때 걸리는 시간을 비교해보자.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;offset&quot;&gt;3.3.3. Offset&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Kafka의 최소 데이터 단위&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1 Message publish = 1 Offset&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Producer의 Partitioning Algorithm에 따라 데이터가 위치할 Partition이 결정된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;오프셋별로 commit log가 남아 이를 이용하여 Fail Over를 수행한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;별도의 카프카 데이터 보존 정책에 의해 데이터를 유지한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;4.-Failover-과정&quot;&gt;4. Failover 과정&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;FailOver가 어떻게 이루어지는지에 대해 아키텍처 기반으로 설명하겠다.&lt;/p&gt;
&lt;p&gt;다음 그림은 2개의 토픽에 대해 &amp;ldquo;replication factor = 3, partition factor = 2&amp;rdquo;의 설정으로 메시지가 발행되어 정상적으로 Broker서버에 저장되어 있는 상태이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cul3Tl/btqIarnNPa3/HFAh6APP3El3cfJpCDOREk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cul3Tl/btqIarnNPa3/HFAh6APP3El3cfJpCDOREk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cul3Tl/btqIarnNPa3/HFAh6APP3El3cfJpCDOREk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcul3Tl%2FbtqIarnNPa3%2FHFAh6APP3El3cfJpCDOREk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 아키텍처에서 Broker 0 과 Broker 3에서 장애가 나면 아키텍처는 다음 그림과 같아진다. 이 때, Topic1의 P0 Partition과 Topic2의 P1 Partition의 Leader Partition이 Broker가 장애가 나면서 더이상 Partition에 대해 접근을 할 수 없게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3EKKf/btqHZkDQDOH/Ignvm6foqgwZo5wil1E7kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3EKKf/btqHZkDQDOH/Ignvm6foqgwZo5wil1E7kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3EKKf/btqHZkDQDOH/Ignvm6foqgwZo5wil1E7kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3EKKf%2FbtqHZkDQDOH%2FIgnvm6foqgwZo5wil1E7kk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만, Kafka에는 다음과 같은 규칙이 있다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&amp;ldquo;모든 Partition은 Leader Partition이 될 수 있다.&amp;ldquo;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 규칙에 의해 Kafka 내부 알고리즘에 의해 기존에 존재하던 Follower Partition이 새로운 Leader로 뽑히게 되어 다음 그림과 같이 장애에 대해 자동으로 Failover가 수행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9fdQ/btqH2y9Rxy4/DrgeXKUnIsuxMqfepefql0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9fdQ/btqH2y9Rxy4/DrgeXKUnIsuxMqfepefql0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9fdQ/btqH2y9Rxy4/DrgeXKUnIsuxMqfepefql0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9fdQ%2FbtqH2y9Rxy4%2FDrgeXKUnIsuxMqfepefql0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;5.-가장-이상적인-Kafka-아키텍처&quot;&gt;5. 가장 이상적인 Kafka 아키텍처&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지금까지의 내용을 기억한다면, 데이터에 대한 접근은 오직 &amp;ldquo;Leader Partition&amp;ldquo;에 대해서만 접근이 이루어진다는 것을 알 수 있다. 그러므로, Leader Partition이 클러스터 내부의 broker에 골고루 분포가 된다면 한 broker로 트래픽이 몰려 timeout이나 다른 에러가 나는 상황을 방지 할 수 있다.&lt;/p&gt;
&lt;p&gt;다시 말해, Leader Partition을 최대한 많은 브로커에 분산시켜 트래픽에 대한 병목 현상을 방지하며, failover에 대해서도 이를 유지할 수 있는 아키텍처가 이상적인 아키텍처이다.&lt;/p&gt;
&lt;p&gt;또한 설계하기 나름이지만, offset 설계를 잘하여 데이터의 정합성까지 지켜준다면 가상 이상적인 아키텍처가 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/OpenSource</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/35</guid>
      <comments>https://coding-deer.tistory.com/35#entry35comment</comments>
      <pubDate>Mon, 7 Sep 2020 14:59:18 +0900</pubDate>
    </item>
    <item>
      <title>[HikariCP] Possibly consider using a shorter maxLifetime value</title>
      <link>https://coding-deer.tistory.com/33</link>
      <description>&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-0.서론&quot;&gt;Table Of Contents&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;2&quot;&gt;&lt;a href=&quot;#id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-0.서론&quot;&gt;0. 서론&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;3&quot;&gt;&lt;a href=&quot;#id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-1.ConnectionPool이무엇인가&quot;&gt;1. Connection Pool이 무엇인가&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;4&quot;&gt;&lt;a href=&quot;#id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-2.ConnectionPool이왜필요한가?&quot;&gt;2. Connection Pool이 왜 필요한가?&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;5&quot;&gt;&lt;a href=&quot;#id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-3.HikariCP의주요파라미터&quot;&gt;3. HikariCP의 주요 파라미터&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;6&quot;&gt;&lt;a href=&quot;#id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-4.문제분석&quot;&gt;4. 문제 분석&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;7&quot;&gt;&lt;a href=&quot;#id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-5.후기및조치&quot;&gt;5. 후기 및 조치&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-outline=&quot;7&quot;&gt;&lt;a href=&quot;#참고문헌&quot;&gt;6. 참고 문헌&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;클릭하시면 해당 content로 넘어갑니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-0.서론&quot;&gt;0. 서론&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지난주, 플랫폼 각 내부의 엔드포인트를 호출하여 각 서비스 및 데이터베이스가 살아있는지에 대해 모니터링 하는 Spring Boot Application에서 다음과 같은 에러 메시지를 보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;267&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm2cOg/btqGc8YNyY4/5hNCjnNUmGQ6LXCF3vkje1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm2cOg/btqGc8YNyY4/5hNCjnNUmGQ6LXCF3vkje1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm2cOg/btqGc8YNyY4/5hNCjnNUmGQ6LXCF3vkje1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm2cOg%2FbtqGc8YNyY4%2F5hNCjnNUmGQ6LXCF3vkje1%2Fimg.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;267&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1597126162007&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[WARN ] 2020-07-30 16:43:39 299545737 [PoolBase.java][isConnectionAlive](184) : HikariPool-1 - Failed to validate connection org.mariadb.jdbc.MariaDbConnection@23d06b10
(Connection.setNetworkTimeout cannot be called on a closed connection). Possibly consider using a shorter maxLifetime value.

[WARN ] 2020-07-30 16:43:39 299545737 [PoolBase.java][isConnectionAlive](184) : HikariPool-1 - Failed to validate connection org.mariadb.jdbc.MariaDbConnection@17387ed0
(Connection.setNetworkTimeout cannot be called on a closed connection). Possibly consider using a shorter maxLifetime value.

[WARN ] 2020-07-30 16:43:39 299545737 [PoolBase.java][isConnectionAlive](184) : HikariPool-1 - Failed to validate connection org.mariadb.jdbc.MariaDbConnection@709f57f6
(Connection.setNetworkTimeout cannot be called on a closed connection). Possibly consider using a shorter maxLifetime value.

[WARN ] 2020-07-30 16:43:39 299545737 [PoolBase.java][isConnectionAlive](184) : HikariPool-1 - Failed to validate connection org.mariadb.jdbc.MariaDbConnection@5b2ab008
(Connection.setNetworkTimeout cannot be called on a closed connection). Possibly consider using a shorter maxLifetime value.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 에러 메시지를 의역하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Failed to validate connection MariaDbConnection&lt;br /&gt;MariaDB Connection의 유효성을 검증하는데 실패하였습니다.&lt;br /&gt;&lt;br /&gt;(Connection setNetworkTimeout cannot be called on a closed connection)&lt;br /&gt;(Connection setNetworkTimeout은 닫힌 connection에서 호출할 수 없습니다.)&lt;br /&gt;&lt;br /&gt;Possibly consider usdig shorter maxLifetime value.&lt;br /&gt;현재보다 더 짧은 maxLifetime 값을 사용하는 것을 고려해보시기 바랍니다.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사실, 위 메시지대로 maxLifetime값만 바꿔주면 뜨지 않을 warning message였지만, 내가 코드를 작성하였기 때문에, 끝까지 책임지고 고도화를 하고 싶었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 warning이 왜 일어났는지에 대해 알기 앞서 Connection Pool이 무엇인지부터 알아보려고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-1.ConnectionPool이무엇인가&quot;&gt;1. Connection Pool이 무엇인가&lt;/h1&gt;
&lt;p&gt;Connection Pool은 Application(WAS, Container 등)이 실행되면서, DB와 Connection에 대해 정의된 객체를 여러개 저장하고 있다. Application이 DB와 Connection이 필요할 때, Connection Pool은 Connection 객체를 제공하며 DB Connection을 이용한 작업이 끝나게 되면 Connection 객체를 다시 회수받아 다른 작업시 Connection 객체를 사용할 수 있도록 수명 관리를 하게된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;489&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqLk3U/btqGbNt3WWO/nbdTr9z6WS7xgoIvWpBfO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqLk3U/btqGbNt3WWO/nbdTr9z6WS7xgoIvWpBfO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqLk3U/btqGbNt3WWO/nbdTr9z6WS7xgoIvWpBfO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqLk3U%2FbtqGbNt3WWO%2FnbdTr9z6WS7xgoIvWpBfO0%2Fimg.png&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;489&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-2.ConnectionPool이왜필요한가?&quot;&gt;2. Connection Pool이 왜 필요한가?&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 코드는 DB와 connection부터 시작해서 query실행, connection 종료까지의 샘플 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1597128457689&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
    sql = &quot;SELECT * FROM SOME_TABLE&quot;;

    connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

    resultSet = connection.createStatement();

    resultSet = statement.executeQuery(sql);
    } catch (Exception e) {
    	logger.info(e.getMessage())
    } finally {
        connection.close();
        statement.close();
        resultSet.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;샘플 코드를 절차적으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote&gt;1. DB Connection을 맺기 위해 JDBC Driver를 Load한다.&lt;br /&gt;2. DB Connection Parameter를 이용하여 DB Connection 객체를 생성한다.&lt;br /&gt;3. Connection 객체로부터 쿼리를 수행하기 위한 statement를 생성한다.&lt;br /&gt;4. 쿼리를 수행하여 결과로 ResultSet 객체를 받아 필요한 데이터를 파싱하여 처리한다.&lt;br /&gt;5. 처리가 끝나면 사용된 리소스를 반환한다.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 절차 중 시간적인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;cost가 가장 높은 부분은 connection 객체를 생성하는 부분&lt;/b&gt;이다. Connection을 생성할 시, DB와 파라미터 교환 뿐만 아니라 각 시스템에서 체크해야할 요소들이 많기 때문이다. 또한, 웹 어플리케이션은 클라이언트의 요청에 따라 스레드를 생성한다. 스레드를 생성할 때 마다 Connection 객체를 생성한다는 것은 물리적으로 계속하여 DB에 접근을 해야하는 것과 같으며 이는 곧 요청 수에 따라 Connection을 생성하게 되므로 서버에 과부하가 걸리기 쉽다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 상황을 방지하기 위해 미리 여유분의 Connection 객체를 만들어 Pool에 저장해놓았다가 클라이언트 요청이 생기면 Connection객체를 지원하주고, 작업 종료시 Connection 객체를 다시 반환하여 보관하는 방식으로 과도한 수의 Connection 객체가 생기는 것을 방지한다. 한번 맺은 DB Connection 객체를 작업이 끝나자마자 close하지 않고, Pool에 저장한 뒤, 다음 번에 동일한 Connection을 요청하면 바로 Pool에서 꺼내 제공을 함으로써 보다 개선된 Connection Time Cost를 보장해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-3.HikariCP의주요파라미터&quot;&gt;3. HikariCP의 주요 파라미터&lt;/h1&gt;
&lt;p&gt;서론의 에러메시지에서 문제가 된 파라미터의 기본값을 가장 먼저 찾아보기로 하였다. 이왕 이렇게 된 김에 HikariPool의 주요 파라미터들을 공부해보기로 했다.&lt;/p&gt;
&lt;p&gt;HikariPool의 주요 파라미터는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;autoCommit
&lt;ul&gt;
&lt;li&gt;풀에서 반환된 connection의 자동 커밋을 설정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;connectionTimeout
&lt;ul&gt;
&lt;li&gt;클라이언트가 connection으로부터 최대 대기할 수 있는 시간, 시간초과시 SQLException이 발생한다. 밀리세컨드 단위이며 최소 250ms, 디폴트 30000이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;idleTimeout
&lt;ul&gt;
&lt;li&gt;Pool 안의 유휴 connection의 최대 유효기간을 설정한다. 이 설정은 minimumIdle이 maximumPoolSize보다 작을때만 적용되며, minimumIdle 값보다 pool안의 connection object의 개수가 작거나 같으면 유휴 연결이 폐기되지 않는다.&lt;/li&gt;
&lt;li&gt;connection이 유휴상태로 전환되기까지 일반적으로 평균 15초에서 최대 30초까지의 시간이 추가로 걸린다. 이 시간이 초과되기 전까지는 connection이 유휴상태로 전환되지 않는다.&lt;/li&gt;
&lt;li&gt;최소 10000ms 값은 적용되어야 하며, 기본적으로 600000ms(10분)이 적용되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;maxLifetime
&lt;ul&gt;
&lt;li&gt;Pool의 Connection의 최대수명을 제어한다. 현재 사용중인 connection은 폐기되지 않으며, connection이 closed상태일 때만 제거된다. Pool안에 존재하는 connection의 순간적인 대량 삭제를 방지하기 위해 약간의 음감쇠가 적용된다.&lt;/li&gt;
&lt;li&gt;의 공식문서를 보게되면, 이 파라미터를 데이터베이스 또는 인프라가 권고하는 connection time limit보다 짧게 잡을 것을 권장하고 있다.&lt;/li&gt;
&lt;li&gt;idleTimeout에 따라 다르지만, 파라미터에 0을 setting하면 최대 생명주기는 infinite로 적용된다.&lt;/li&gt;
&lt;li&gt;최소 30000ms(30초) 값은 적용되어야 하며, 기본값으로 1800000(30분)이 적용되어 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;minimumIdle
&lt;ul&gt;
&lt;li&gt;Connection Pool에서 유지하는 최소 유휴 Connection의 개수를 설정한다. 유휴 Connection이 minimumIdle 값보다 작을 시, HikariPool은 추가 connection을 만들어 채워넣는다.&lt;/li&gt;
&lt;li&gt;HikariCP의 공식문서에서는 급격한 수요에 대해서 최상의 퍼포먼스와 반응성을 위해서 이 값을 수정하지 않고 사용하는 것을 권장하고 있다.&lt;/li&gt;
&lt;li&gt;기본 값은 maximumPoolSize와 같은 값으로 설정되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;maximumPoolSize
&lt;ul&gt;
&lt;li&gt;Pool에서 유지할 유휴 connection, 사용중인 connection을 합친 최대의 connection 개수를 설정한다. 기본적으로 이 값은 실제 DB 벡엔드와의 연결 최대수를 결정한다.&lt;/li&gt;
&lt;li&gt;Pool이 최대 사이즈에 도달했고, 유휴 Connection이 없을때 getConnection을 호출하면 connectionTimeout설정 값 전까지 getConnection이 지연되며, connectionTimeout을 초과시, SQLException이 발생한다.&lt;/li&gt;
&lt;li&gt;최적의 maximumPoolSize를 설정하는 것은 HikariPool을 적용한 환경에 대해 맞추는 것이 가장 좋으며, 이에 대해서는 &lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot;&gt;다음 링크&lt;/a&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;를 참고한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-4.문제분석&quot;&gt;4. 문제 분석&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 주요 파라미터 중 우리가 주목해야 할 것은 HikarikPool의 maxLifetime과 DB 설정값 중의 wait_timeout이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HikariPool이 적용된 환경은 따로 별도의 설정이 없었으며, connection을 맺고있는 DB의 설정은 다음과 같이 wait_timeout이 1200(20분)으로 설정되어 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;928&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0lUUy/btqGurjcEwS/MGwEsDKK7akzE5skS61BZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0lUUy/btqGurjcEwS/MGwEsDKK7akzE5skS61BZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0lUUy/btqGurjcEwS/MGwEsDKK7akzE5skS61BZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0lUUy%2FbtqGurjcEwS%2FMGwEsDKK7akzE5skS61BZK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;928&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;서론에서의 로그는 HikariPool의 로그를 찍지 않고 있었기 때문에, HikariPool의 로그가 찍힌 화면을 다시 캡처해보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;860&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbDoRS/btqGtXirK2t/VoHKwx9fMzQPFvLQkJ7wEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbDoRS/btqGtXirK2t/VoHKwx9fMzQPFvLQkJ7wEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbDoRS/btqGtXirK2t/VoHKwx9fMzQPFvLQkJ7wEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbDoRS%2FbtqGtXirK2t%2FVoHKwx9fMzQPFvLQkJ7wEK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;860&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 로그를 보고 추측한 warning 원인은 다음과 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ol&gt;
&lt;li&gt;위 로그에서는 보이지 않지만, 같은 MariaDbConnection 객체를 이용하여 commit을 하고 있었으며, 아래와 같이 Wrapping하고 있는 Proxy객체는 같지만, 본질적인 MariaDBConnection의 객체는 같은 것을 쓰고있었다.
&lt;ul&gt;
&lt;li&gt;[DEBUG] 2020-08-11&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;12:02:23&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;613936563 [DataSourceTransactionManager.java][doCommit](326) : Committing JDBC transaction on Connection [HikariProxyConnection@861338066 wrapping org.mariadb.jdbc.MariaDbConnection@17b01e2a]&lt;/li&gt;
&lt;li&gt;[DEBUG] 2020-08-11&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;12:02:08&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;390098262 [DataSourceTransactionManager.java][doCommit](326) : Committing JDBC transaction on Connection [HikariProxyConnection@205121260 wrapping org.mariadb.jdbc.MariaDbConnection@17b01e2a]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Boot의 HikariCP 설정 프로퍼티 중, maxLifetime으로 인해, 사용하던 모든 Connection은 30분 주기로 위 사진과 같이 connection을 closing 후, 다시 새로운 connection을 생성하고 있었다.&lt;/li&gt;
&lt;li&gt;사용하고 있던 connection도 closing 하면서 새로운 connection이 필요하여 HikariCP에 새로운 connection 객체를 요청한 것으로 보인다.&lt;/li&gt;
&lt;li&gt;비교적 가장 오래전에 만들어진 객체를 HikariPool에서 발급 받은 후, HikariPool의 정책에 따라 이 connection이 유효한지를 검사한다.&lt;/li&gt;
&lt;li&gt;이미 DB에서는 20분이 지나 닫힌 connection에 대해 validate하게 되므로 위와 같이 닫힌 connection에 대해서 아무것도 할수 없다고 로그가 찍혔다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 추측이 맞는지에 대해 직접 HikariCP의 코드를 뜯어보기로 하였다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;HikariPool.class&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1597126664322&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; /**
    * Get a connection from the pool, or timeout after the specified number of milliseconds.
    *
    * @param hardTimeout the maximum time to wait for a connection from the pool
    * @return a java.sql.Connection instance
    * @throws SQLException thrown if a timeout occurs trying to obtain a connection
    */
   public Connection getConnection(final long hardTimeout) throws SQLException
   {
      suspendResumeLock.acquire();
      final long startTime = currentTime();

      try {
         long timeout = hardTimeout;
         do {
            PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
            if (poolEntry == null) {
               break; // We timed out... break and throw exception
            }

            final long now = currentTime();
            if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) &amp;gt; aliveBypassWindowMs &amp;amp;&amp;amp; !isConnectionAlive(poolEntry.connection))) {
               closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
               timeout = hardTimeout - elapsedMillis(startTime);
            }
            else {
               metricsTracker.recordBorrowStats(poolEntry, startTime);
               return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
            }
         } while (timeout &amp;gt; 0L);

         metricsTracker.recordBorrowTimeoutStats(startTime);
         throw createTimeoutException(startTime);
      }
      catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new SQLException(poolName + &quot; - Interrupted during connection acquisition&quot;, e);
      }
      finally {
         suspendResumeLock.release();
      }
   }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 HikariPool.class의 코드를 보면, 중간에 closeConnection을 할지에 대해 판단하는 로직이 포함되어 있었다. 해당 로직에서 검사하는 것을 정리하면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해당 connection이 HikariCP에서 evict(직역하면 축출하다라고 함)되기로 한 객체인가?&lt;/li&gt;
&lt;li&gt;Connection이 살아있는지 Bypass할 수 있는 시간보다 최근 접근 시간이 더 크고, connection이 아직 살아있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로그 상에서 connection이 evict하다고 하지 않았으므로, 두번째 검사 조건에서 true를 반환하였기 때문에&amp;nbsp; closeConnection을 하도록 하였을 것이다.&lt;/p&gt;
&lt;p&gt;두번째 조건을 warning이 발생한 조건에 맞추에 분석해보면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 connection 객체만 사용하고 있었으므로, 500ms보다는 훨씬 더 크기 때문에 true를 출력하였을 것이다.&lt;/li&gt;
&lt;li&gt;DB에서 wait_timeout 값이 20분이므로,&amp;nbsp;getConnection하였을 때, false를 출력하였을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음, isConnectionAlive의 코드를 살펴보면 문제가 된 상황을 한눈에 볼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;PoolBase.class&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1597126705830&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;boolean isConnectionAlive(final Connection connection)
   {
      try {
         try {
            setNetworkTimeout(connection, validationTimeout);

            final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;

            if (isUseJdbc4Validation) {
               return connection.isValid(validationSeconds);
            }

            try (Statement statement = connection.createStatement()) {
               if (isNetworkTimeoutSupported != TRUE) {
                  setQueryTimeout(statement, validationSeconds);
               }

               statement.execute(config.getConnectionTestQuery());
            }
         }
         finally {
            setNetworkTimeout(connection, networkTimeout);

            if (isIsolateInternalQueries &amp;amp;&amp;amp; !isAutoCommit) {
               connection.rollback();
            }
         }

         return true;
      }
      catch (Exception e) {
         lastConnectionFailure.set(e);
         logger.warn(&quot;{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.&quot;,
                     poolName, connection, e.getMessage());
         return false;
      }
   }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드를 보면, try문 첫번째에서 setNetworkTimeout을 실행하고 있다. 그러나 connection은 DB측에서 이미 close한 상태이기 때문에, close된 connection에서 작업을 할 수 없기 때문에 catch문의 로직이 실행 되었을것이다.&lt;/p&gt;
&lt;p&gt;catch문을 보면 우리가 보았던 해당 로그가 보일 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 분석을 통해 내가 예측한 추측한 결과와 실제 코드의 로직이 일치하는 것을 볼 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;id-[Spring]HikariPoolPossiblyconsiderusingashortermaxLifetimevalue-5.후기및조치&quot;&gt;5. 후기 및 조치&lt;/h1&gt;
&lt;p&gt;HikariCP의 공식문서에서 maxLifeTime을&amp;nbsp;데이터베이스 또는 인프라가 권고하는 connection time limit보다 짧게 잡을 것을 권장하여 maxLifeTime을 기존 30분에서 15분으로 변경하고 jenkins를 통해 deploy한 결과, 그 이후로는 warning이 발생하지 않았다.&lt;/p&gt;
&lt;p&gt;그러나 30분 내내 HikariCP에서 처음 받은 단 하나의 connection을 사용하는 현상은 아직 내가 이해를 하지 못하여 이에 대해서는 조금 더 보충하여 새로 글을 작성할 계획이다. 이번 기회를 통해 Connection관리가 App안에서 어떻게 이루어 지는지에 대한 개론을 파악할 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고문헌&quot;&gt;6. 참고 문헌&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;https://github.com/brettwooldridge/HikariCP&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Java Spring</category>
      <category>Connection Pool</category>
      <category>hikari pool</category>
      <category>hikaricp</category>
      <category>maxLifeTime</category>
      <category>Spring</category>
      <author>JeongHyeongKim</author>
      <guid isPermaLink="true">https://coding-deer.tistory.com/33</guid>
      <comments>https://coding-deer.tistory.com/33#entry33comment</comments>
      <pubDate>Wed, 12 Aug 2020 02:31:47 +0900</pubDate>
    </item>
  </channel>
</rss>