<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나만의 repository</title>
    <link>https://hwan33.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 18:09:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>오렌지색 귤</managingEditor>
    <image>
      <title>나만의 repository</title>
      <url>https://tistory1.daumcdn.net/tistory/5117028/attach/d81e1d9471e44a6083211bb3de540ad3</url>
      <link>https://hwan33.tistory.com</link>
    </image>
    <item>
      <title>[카프카 핵심 가이드] Ch 13, Ch 14</title>
      <link>https://hwan33.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1) 모니터링&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;1-1. 네 가지 숫자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Throughput : 들어오고/나가는 메시지 양 (bytes/sec, records/sec)&lt;/li&gt;
&lt;li&gt;Error : 프로듀서/컨슈머 에러율&lt;/li&gt;
&lt;li&gt;Lag/Latency&lt;/li&gt;
&lt;li&gt;Saturation : 브로커가 바쁨 (Idle% 낮은 경우), 디스크가 꽉 참 (사용량 높은 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;지표 몇가지&lt;br /&gt;1. URP &amp;gt; 0 : 불완전 복제 파티션이 있으니 확인 필요&lt;br /&gt;2. RequestHandlerIdle %가 계속 0.3보다 작은 경우 : 브로커 과부화 가능성&lt;br /&gt;3. Consumer Lag이 업무 시간에 계속 증가 : 처리 병목&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-2. SLO란?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SLI (무엇을 측정하는가) : 특정 메시지가 3초 안에 처리되는 비율&lt;/li&gt;
&lt;li&gt;SLO (목표) : 영업 시간에 99.9%는 3초 안에 끝낸다&lt;/li&gt;
&lt;li&gt;알람 : 3초 넘는 구간이 5분 이상 지속되면 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-3. 클라이언트에서는 무엇을 볼까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Producer : 재시도 획수, 에러율 (재시도 성공도 포함)&lt;/li&gt;
&lt;li&gt;Consumer : 최대 랙, 리밸런스 빈도, 처리 지연&lt;/li&gt;
&lt;li&gt;Lag = 0인데 느린 경우 : 앱 내부 처리 지연일 때가 많다 -&amp;gt; E2E 시간을 애플리케이션 지표로 찍어두자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 스트림 처리&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;2-1. 시간&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;윈도우 : &quot;5분 동안 모아서 집계&quot;와 같은 개념&lt;/li&gt;
&lt;li&gt;지연 데이터 (grace) : 늦게 들어온 이벤트를 얼마나 더 받아줄지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예 : 5분 윈도우 + grace 10분 -&amp;gt; 윈도우 닫히고 10분 안에 늦게 들어와도 다시 계산한다&lt;/li&gt;
&lt;li&gt;grace가 지나면 버린다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2. 키&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인/그룹바이 전에 키가 바뀌면 카프카가 repartition 토픽을 만들어서 데이터를 다시 섞는다 -&amp;gt; I/O 비용이 크다&lt;/li&gt;
&lt;li&gt;가능하면 업스트림에서 키를 맞춰서 보낸다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-3. 상태&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EOS : 컨슘 오프셋 커밋 + state 업데이트 + 아웃풋을 한 번에 커밋&lt;/li&gt;
&lt;li&gt;단 EOS가 외부 시스템 호출의 중복까지 자동 해결해주지는 않는다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/125</guid>
      <comments>https://hwan33.tistory.com/125#entry125comment</comments>
      <pubDate>Sun, 7 Sep 2025 20:34:20 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] Ch 11, Ch 12</title>
      <link>https://hwan33.tistory.com/124</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 브로커/토픽에 누구나 접근 가능한 상태라면 어떻게 통제해야하나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 접속 통제 : 내부/외부 리스너 분리, mTLS 또는 SASL 필수, ACL 최소 권한, TLS 전체 구간, 방화벽/VPC/VPN&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 메시지 자체 보호 : 메시지 value 전체 암호화 (AES-GCM 등) + 서명 + 일련번호 + 타임스탬프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 연속 번호 아니면 버림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 서명 불일치 버림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 전용 통로 : 금융권에서는 사설망/PrivateLink/VPN으로 별도 라인&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Q. OIDC로 토큰 인증 쏠 때 갱신/만료/재연결은?&lt;/h3&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조기 갱신(만료 5 ~ 10분 전), 지수 백오프 + 지터, 프로세스 내 토큰 공유 캐시&lt;/li&gt;
&lt;li&gt;롤링 배포 시 소수씩 재시작&lt;/li&gt;
&lt;li&gt;모니터링: 토큰 발급 실패율/지연, 재연결 성공률 알람&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Q. ZooKeeper -&amp;gt; KRaft 전환 시 보안 이행은?&lt;/h3&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;gt; 재적용 -&amp;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;1. ZK 현행 ACL/주체/SCRAM 덤프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. KRaft 클러스터에 동일 리스너/보안 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 자격/ACL 재적용, 인증서/키 배포&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Mirror/리허설로 인증/인가/지연 점검, 롤백 플랜 준비&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 점진 전환(읽기 -&amp;gt; 쓰기), 안정 후 ZK 정리&lt;/p&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/124</guid>
      <comments>https://hwan33.tistory.com/124#entry124comment</comments>
      <pubDate>Sun, 31 Aug 2025 21:20:49 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] Ch 09, Ch 10</title>
      <link>https://hwan33.tistory.com/123</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Kafka Connect는 Spring Kafka로 직접 구현한 Consumer보다 뭐가 더 좋은가?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그냥 JDBC로 읽고 KafkaTemplate으로 전송하면 되는데, Kafka Connect를 왜 써야 하지?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka Connect의 핵심은 운영 편의성과 Pluggability이다.&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;직접 구현한 Producer/Consumer는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애 발생 시 재처리 로직, offset 저장, 실패 전파, 재시작 등 모든 책임이 애플리케이션에 있음&lt;/li&gt;
&lt;li&gt;운영자는 해당 서비스를 배포하고, 장애 시 디버깅과 로깅을 파고들어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, Kafka Connect는 다음과 같은 특성을 가진다:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Kafka Connect&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;직접 구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;offset 저장&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Kafka 내부 토픽 (__consumer_offsets)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;보통 DB or Kafka 자체에 수동 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;리스타트/장애 복구&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Worker 재시작으로 자동 복구&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;어플리케이션 재기동 or 별도 로직 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;구성 변경&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;REST API로 Connector 설정 변경&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;코드 수정 + 배포&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;재사용성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;JDBC, Debezium, S3 등 이미 만들어진 connecter 수백 개&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;직접 개발해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Kafka Connect 쓰면 장애났을 때 어떻게 재처리하는가?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Connector가 중간에 죽으면 실패한 레코드는 어떻게 되나?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka Connect는 기본적으로 레코드 단위의 at-least-once 처리를 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 Connector는 자체적으로 offset을 커밋하고, 실패하면 재시도 -&amp;gt; 실패 -&amp;gt; DLQ 흐름을 따른다.&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;903&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tOq6L/btsP4Twetd1/vcfl3AtHmHqDbhu0bwJrK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tOq6L/btsP4Twetd1/vcfl3AtHmHqDbhu0bwJrK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tOq6L/btsP4Twetd1/vcfl3AtHmHqDbhu0bwJrK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtOq6L%2FbtsP4Twetd1%2Fvcfl3AtHmHqDbhu0bwJrK0%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;1452&quot; height=&quot;903&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;903&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;Kafka Connect에서 errors.deadletterqueue.topic.name 옵션을 설정하면, 실패한 레코드를 Kafka 토픽으로 보내고, 수동으로 확인 및 재처리할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. MirrorMaker 2.0이면 클러스터 간 동기화는 완벽하게 되는건가?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그럼 A 클러스터 -&amp;gt; B 클러스터로 완전하게 미러링되고 offsest도 동기화되나?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 대부분의 메시지는 미러링되지만, 다음과 같은 caveat가 있다:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;이슈&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Offset translation&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;MM2는 offset을 따로 저장해서 원본 offset과 일치하지 않음.&lt;br /&gt;Consumer가 A의 offset으로 정확히 복원되지 않음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Loop 방지 필요&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;양방향 미러링할 경우 A-&amp;gt;B-&amp;gt;A로 메시지가 순환될 수 있음.&lt;br /&gt;Topic 필터링 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Config/ACL은 기본 미러링 안 됨&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;topic metadata, schema, ACL 등은 직접 동기화해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;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;h3 data-ke-size=&quot;size23&quot;&gt;4. 요즘은 MirrorMaker 대신 뭘 써?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Confluent에서는 Cluster Linking도 있고, Kafka 자체에도 diskless 나온다는데?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cluster Linking (Confluent 전용)은 MM2보다 훨씬 강력한 미러링 기능을 제공한다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;offset 유지, 동일한 topic name 유지, 정확한 시간 기반 리플레이&lt;/li&gt;
&lt;li&gt;config / ACL / topic structure까지 복제&lt;/li&gt;
&lt;li&gt;단점은 Confluent Enterprise 라이선스 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka 4.x의 diskless + tiered storage 구조와 맞물리면:&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;remote storage를 공유하거나, CDC 수준의 동기화로 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조에서 MM2보다 전사적인 운영 비용이 훨씬 낮아질 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Kafka를 log 시스템으로 쓸 때 Connect랑 Mirror는 진짜 필요할까?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그냥 Filebeat -&amp;gt; Kafka -&amp;gt; ClickHouse 이런 구조면 되지 않나?&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;&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;하지만 다음과 같은 요구가 생기면 Kafka Connect/Mirror를 고려하게 된다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka로 적재된 로그를 정기적으로 S3로 내리고 싶다 -&amp;gt; Connect Sink + HDFS/S3 connector&lt;/li&gt;
&lt;li&gt;로그 분석용으로 Region B 클러스터로도 동일하게 가져가야 한다 -&amp;gt; MirrorMaker or Cluster Linking&lt;/li&gt;
&lt;li&gt;Kafka 내부 파티션 재정렬 없이 다른 클러스터에서 복제하고 싶다 -&amp;gt; MM2 with offset.sync.enabled&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/123</guid>
      <comments>https://hwan33.tistory.com/123#entry123comment</comments>
      <pubDate>Sun, 24 Aug 2025 20:53:56 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] Ch 07, Ch 08</title>
      <link>https://hwan33.tistory.com/122</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 카프카에서 발생할 수 있는 대표적인 에러 유형은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TimeoutException&lt;/td&gt;
&lt;td&gt;브로커 응답 지연 (네트워크, 리더 불안정)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RecordTooLargeException&lt;/td&gt;
&lt;td&gt;메시지 크기가 max.request.size 초과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NotEnoughReplicasException&lt;/td&gt;
&lt;td&gt;ISR 부족 &amp;rarr; acks=all 충족 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KafkaStorageException&lt;/td&gt;
&lt;td&gt;브로커 디스크 장애 (ISR 탈락 등 발생 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;Consumer&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RebalanceInProgressException&lt;/td&gt;
&lt;td&gt;Rebalance 중 poll 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WakeupException&lt;/td&gt;
&lt;td&gt;안전한 종료 처리 도중 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OffsetOutOfRangeException&lt;/td&gt;
&lt;td&gt;커밋된 offset이 사라진 경우 (retention.ms 초과)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CommitFailedException&lt;/td&gt;
&lt;td&gt;group 재할당 중 커밋 시도 등 비정상 상황&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;브로커 장애 (Crash)&lt;/td&gt;
&lt;td&gt;리더 재선출, ISR 이동, 컨트롤러 재할당 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZK/KRaft delay&lt;/td&gt;
&lt;td&gt;메타데이터 지연 &amp;rarr; Metadata fetch 문제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 분리&lt;/td&gt;
&lt;td&gt;Leader &amp;rarr; Follower 복제 실패, 클라이언트 지연/실패&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h3 data-ke-size=&quot;size23&quot;&gt;Q. 위 에러들을 테스트할 수 있는 라이브러리/도구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1205&quot; data-start=&quot;1130&quot; data-ke-size=&quot;size23&quot;&gt;① Testcontainers + Embedded Kafka&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1347&quot; data-start=&quot;1207&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1249&quot; data-start=&quot;1207&quot;&gt;Kafka를 &lt;b&gt;Docker 컨테이너로 테스트용 클러스터로 쉽게 구동&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1306&quot; data-start=&quot;1250&quot;&gt;Spring Kafka, Kafka Java Client와 &lt;b&gt;JVM 내에서 통합 테스트 가능&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1347&quot; data-start=&quot;1307&quot;&gt;일부 장애 상황을 유도할 수 있음 (브로커 중지, 네트워크 끊김 등)&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1592&quot; data-start=&quot;1485&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1522&quot; data-start=&quot;1485&quot;&gt;브로커 1개 중단 (kafkaContainer.stop())&lt;/li&gt;
&lt;li data-end=&quot;1548&quot; data-start=&quot;1523&quot;&gt;특정 토픽에 메시지 크기 초과 메시지 전송&lt;/li&gt;
&lt;li data-end=&quot;1592&quot; data-start=&quot;1549&quot;&gt;Rebalance 유도 &amp;rarr; onPartitionsRevoked() 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1657&quot; data-start=&quot;1599&quot; data-ke-size=&quot;size23&quot;&gt;② Toxiproxy&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1789&quot; data-start=&quot;1659&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1717&quot; data-start=&quot;1659&quot;&gt;Kafka 브로커 &amp;harr; 클라이언트 사이의 네트워크에 &lt;b&gt;의도적으로 지연, 끊김, 제한을 주는 프록시&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1789&quot; data-start=&quot;1718&quot;&gt;TimeoutException, NetworkException, leader unavailable 등을 유도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1999&quot; data-start=&quot;1954&quot; data-ke-size=&quot;size23&quot;&gt;③ Spring Kafka Test + @EmbeddedKafka&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2120&quot; data-start=&quot;2001&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2027&quot; data-start=&quot;2001&quot;&gt;JVM 내 임베디드 Kafka 클러스터 구동&lt;/li&gt;
&lt;li data-end=&quot;2075&quot; data-start=&quot;2028&quot;&gt;특정 상황 (e.g., offset 유실, 재시작) 등을 코드 내에서 테스트 가능&lt;/li&gt;
&lt;li data-end=&quot;2120&quot; data-start=&quot;2076&quot;&gt;KafkaTestUtils, EmbeddedKafkaBroker 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2302&quot; data-start=&quot;2266&quot; data-ke-size=&quot;size23&quot;&gt;④ Chaos Engineering 툴 (실전 환경)&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Gremlin, Chaos Mesh, LitmusChaos&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;실환경에서 Kafka 브로커/네트워크에 장애 유도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;kcat, kafka-producer-perf-test.sh&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;메시지 전송 상태, 실패율 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;JMeter + Kafka plugin&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;부하 기반 테스트 시나리오 작성 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 테스트 케이스는?&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;브로커 다운 중 producer 전송 &amp;rarr; 예외 핸들링&lt;/td&gt;
&lt;td&gt;TimeoutException, NotLeaderForPartitionException&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;offset 커밋 실패 + 재시도&lt;/td&gt;
&lt;td&gt;CommitFailedException, 재poll 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리밸런싱 도중 메시지 재처리 여부&lt;/td&gt;
&lt;td&gt;onPartitionsRevoked(), ack.acknowledge() 누락 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최대 메시지 크기 초과&lt;/td&gt;
&lt;td&gt;RecordTooLargeException, compression.type 등과 연계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DLQ 정상 전송 여부&lt;/td&gt;
&lt;td&gt;ErrorHandler 설정 + 메시지 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>개발</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/122</guid>
      <comments>https://hwan33.tistory.com/122#entry122comment</comments>
      <pubDate>Sun, 17 Aug 2025 19:29:52 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] Ch 05, Ch 06</title>
      <link>https://hwan33.tistory.com/121</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ch 05. 프로그램 내에서 코드로 카프카 관리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 126&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;카프카의 AdminClient는 Future 객체를 Result 객체 안에 감싸는데, Result 객체는 작업이 끝날 때까지 대기하거나 작업 결과에 대해 일반적으로 뒤이어 쓰이는 작업을 수행하는 헬퍼 메서드를 가지고 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 실제 클래스 구조는 어떻게 생겼는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/clients/admin/CreateTopicsResult.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CreateTopicsResult.java&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/KafkaFuture.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KafkaFuture.java&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 137&lt;/p&gt;
&lt;pre id=&quot;code_1754828924742&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ① 커밋된 consumer group offset 조회
Map&amp;lt;TopicPartition, OffsetAndMetadata&amp;gt; offsets = admin
    .listConsumerGroupOffsets(CONSUMER_GROUP)
    .partitionsToOffsetAndMetadata().get();

// ② 다시 최신 offset 조회
Map&amp;lt;TopicPartition, OffsetSpec&amp;gt; requestLatestOffsets = new HashMap&amp;lt;&amp;gt;();
for (TopicPartition tp : offsets.keySet()) {
    requestLatestOffsets.put(tp, OffsetSpec.latest());
}

Map&amp;lt;TopicPartition, ListOffsetsResult.ListOffsetsResultInfo&amp;gt; latestOffsets = 
    admin.listOffsets(requestLatestOffsets).all().get();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. ①에서 이미 offset이 있는 것 같은데, 왜 굳이 ②에서 최신 offset을 따로 조회하는가?&lt;/h4&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncSgH/btsPO96uOzp/mGGMppzE08JrKVD7OQZhgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncSgH/btsPO96uOzp/mGGMppzE08JrKVD7OQZhgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncSgH/btsPO96uOzp/mGGMppzE08JrKVD7OQZhgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FncSgH%2FbtsPO96uOzp%2FmGGMppzE08JrKVD7OQZhgk%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;1532&quot; height=&quot;472&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;472&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;두 값은 Kafka에서 lag (지연) 분석, 누락 처리, backlog 확인, 지표 수집 등에서 핵심적으로 사용된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Ch 06. 카프카 내부 메커니즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 150&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. Ephemeral 노드란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. Ephemeral 노드는 ZooKeeper에서 클라이언트 세션이 살아있는 동안만 유지되는 임시 노드입니다.&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 150&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그렇기 때문에 만약 특정한 브로커가 완전히 유실되어 동일한 ID를 가진 새로운 브로커를 투입할 경우, 곧바로 클러스터에서 유실된 브로커의 자리를 대신해서 이전 브로커의 토픽과 파티션들을 할당받는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 동일한 ID를 갖게 만든다는 것이 의도적이냐?&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 맞다. Kafka에서 브로커 ID는 브로커의 정체성을 나타내는 핵심 식별자이며, 이전 브로커가 사라졌을 때 그 자리를 대체하려면 일부러 같은 ID로 재투입해야 한다.&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;만약 다른 ID로 띄우면 Kafka는 이 새 브로커를 전혀 다른 새 브로커로 인식한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메타데이터에는 새로운 /brokers/ids/N 으로 등록&lt;/li&gt;
&lt;li&gt;컨트롤러는 기존 파티션을 절대 이 브로커에 자동으로 매핑하지 않음&lt;/li&gt;
&lt;li&gt;명시적으로 파티션 reassignment를 해야 반영 가능&lt;/li&gt;
&lt;/ul&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;p data-ke-size=&quot;size16&quot;&gt;p. 152&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컨트롤러가 주키퍼에 메타데이터를 쓰는 작업은 동기적으로 이루어지지만, 브로커 메시지를 보내는 작업은 비동기적으로 이루어진다. 또한, 주키퍼로부터 업데이트를 받는 과정 역시 비동기적으로 이루어진다. 그렇기 때문에 브로커, 컨트롤러, 주키퍼 간에 메타데이터 불일치가 발생할 수 있으며, 잡아내기도 어렵다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 비동기에서는 어느 시점에 항상 불일치가 발생할 수 있을 것 같은데, 이것이 Zookeeper 기반 메타데이터 구조이기 때문이라고 말할 수 있는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 위 내용은 다음 구조적 특징들을 포함한다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;435&quot; data-start=&quot;406&quot;&gt;컨트롤러 &amp;rarr; ZooKeeper: &lt;b&gt;동기 쓰기&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;475&quot; data-start=&quot;436&quot;&gt;컨트롤러 &amp;rarr; 브로커들(리더 변경 알림): &lt;b&gt;비동기 메시지 전송&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;523&quot; data-start=&quot;476&quot;&gt;브로커 &amp;rarr; ZooKeeper(메타데이터 watch): &lt;b&gt;비동기 업데이트 감지&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. KRaft 모드로 전환하면 메타데이터 불일치 문제가 해결되나?&lt;/h4&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;A. Kafka 2.8+부터 ZooKeeper를 제거한 **KRaft 모드(KIP-500)**가 도입되며:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2255&quot; data-start=&quot;2123&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2163&quot; data-start=&quot;2123&quot;&gt;컨트롤러가 &lt;b&gt;Raft consensus로 직접 메타데이터를 저장&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2208&quot; data-start=&quot;2164&quot;&gt;브로커는 controller로부터 &lt;b&gt;push 기반으로 메타데이터를 받음&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2255&quot; data-start=&quot;2209&quot;&gt;&lt;b&gt;ZooKeeper의 watch + 비동기 구조에서 생기던 불일치가 줄어듦&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2360&quot; data-start=&quot;2257&quot; data-ke-size=&quot;size16&quot;&gt;즉, 불일치 문제는 ZooKeeper 탓이 아니라 구조적 설계의 한계였고, Kafka는 이를 해결하기 위해 아예 ZooKeeper를 없애는 구조(KRaft)로 진화 중이다.&lt;/p&gt;
&lt;p data-end=&quot;2360&quot; data-start=&quot;2257&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2360&quot; data-start=&quot;2257&quot; data-ke-size=&quot;size16&quot;&gt;KRaft 에서는 ZooKeeper를 제거하고, Kafka 내부에서 메타데이터를 일관되게 관리한다.&lt;/p&gt;
&lt;p data-end=&quot;2360&quot; data-start=&quot;2257&quot; data-ke-size=&quot;size16&quot;&gt;따라서 기존 ZooKeeper 기반 Kafka보다는 메타데이터 불일치 확률이 획기적으로 줄어든다.&lt;/p&gt;
&lt;p data-end=&quot;2360&quot; data-start=&quot;2257&quot; data-ke-size=&quot;size16&quot;&gt;하지만 모든 시점에서 절대적 일치가 항상 즉시 보장되지는 않는다. 다만, Raft 알고리즘 특성상 eventually consistent가 아닌 strictly consistent로 수렴한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;Q. Raft Quorum, Raft consensus, Raft 알고리즘은 무엇인가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 큰 그림 : 왜 Raft가 필요한가?&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;Kafka 같은 분산 시스템에서는 메타데이터(토픽, 파티션, 리더 등) 를 여러 노드가 같이 알고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아래와 같은 문제가 발생할 수 있다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리더가 죽으면?&lt;/li&gt;
&lt;li&gt;네트워크가 일시적으로 분리되면?&lt;/li&gt;
&lt;li&gt;여러 노드가 서로 다른 상태를 알고 있다면?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때도 시스템이 안전하게 일관된 결정을 내릴 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 Kafka는 Raft consensus 알고리즘을 사용한다.&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;2. Raft Consensus 란?&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;여러 대의 서버가 서로 다른 네트워크 환경에서도, &quot;하나의 결정&quot;에 다수의 동의를 얻어 일관된 상태를 유지하도록 만드는 알고리즘&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;3. Raft의 핵심 구성 요소&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Leader&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;유일한 쓰기 권한 보유자. 모든 변경은 리더를 통해서만 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Followers&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;리더의 명령을 따라가는 노드들&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Candidate&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;리더 선출을 위해 임시로 나선 노드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Term&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;리더 선출 주기 (선거 라운드 같은 개념)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Log&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 노드가 공유해야 할 기록 (예: 메타데이터 변경, 설정 변경 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;p data-ke-size=&quot;size16&quot;&gt;4. Raft Quorum 이란?&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;Quorum은 과반수 이상 참여자들의 동의를 의미한다&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;5. Raft는 어떤 방식으로 일관성을 보장할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1299&quot; data-start=&quot;1286&quot; data-ke-size=&quot;size16&quot;&gt;1️⃣&amp;nbsp;리더 선출&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1386&quot; data-start=&quot;1301&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1351&quot; data-start=&quot;1301&quot;&gt;모든 노드는 &lt;b&gt;일정 시간 동안 리더 응답이 없으면 후보(candidate)&lt;/b&gt; 가 됨&lt;/li&gt;
&lt;li data-end=&quot;1386&quot; data-start=&quot;1352&quot;&gt;&lt;b&gt;투표를 요청&lt;/b&gt;하고, 과반수 이상이 승인하면 리더가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1401&quot; data-start=&quot;1388&quot; data-ke-size=&quot;size16&quot;&gt;2️⃣ 로그 복제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1529&quot; data-start=&quot;1403&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1434&quot; data-start=&quot;1403&quot;&gt;클라이언트가 어떤 변경 요청(예: 토픽 생성)을 하면&lt;/li&gt;
&lt;li data-end=&quot;1481&quot; data-start=&quot;1435&quot;&gt;리더는 &lt;b&gt;자신의 로그에 먼저 추가하고&lt;/b&gt;, follower들에게 복제를 요청함&lt;/li&gt;
&lt;li data-end=&quot;1529&quot; data-start=&quot;1482&quot;&gt;Quorum(과반수) 노드가 이 로그를 &lt;b&gt;받았다고 응답하면 commit 확정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1545&quot; data-start=&quot;1531&quot; data-ke-size=&quot;size16&quot;&gt;3️⃣ 일관성 보장&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1613&quot; data-start=&quot;1547&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1576&quot; data-start=&quot;1547&quot;&gt;follower는 리더가 승인한 로그만 최종 반영&lt;/li&gt;
&lt;li data-end=&quot;1613&quot; data-start=&quot;1577&quot;&gt;로그는 &lt;b&gt;모든 노드에서 같은 순서, 같은 내용&lt;/b&gt;으로 유지됨&lt;/li&gt;
&lt;/ul&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;p. 156&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;브로커는 추후 시동 시간을 줄이기 위해 메타데이터를 디스크에 저장한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 그러면 ZooKeeper 사용할 떄는 브로커가 메타데이터를 디스크에 저장하지 않아?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 맞다. ZooKeeper 기반 Kafka에서는 브로커가 메타데이터를 디스크에 직접 저장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신, 브로커는 시동 시마다 ZooKeeper에서 메타데이터를 fetch해서 메모리에 적재한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 왜 ZooKeeper 사용 때는 디스크에 저장을 안했을까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. Kafka 2.x 구조에선:&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 data-end=&quot;1261&quot; data-start=&quot;1212&quot;&gt;메타데이터는 &lt;b&gt;전적으로 ZooKeeper가 authoritative source&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1302&quot; data-start=&quot;1262&quot;&gt;브로커는 그때그때 &lt;b&gt;ZooKeeper에서 읽어서 메모리에만 저장&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1368&quot; data-start=&quot;1303&quot;&gt;메타데이터가 자주 변경될 수 있고, ZooKeeper와의 정합성 유지가 중요해서 &lt;b&gt;디스크 캐시 유지하지 않음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡ 디스크 저장의 유익보다 &lt;b&gt;메타데이터 신선도와 consistency 확보가 더 중요했던 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/121</guid>
      <comments>https://hwan33.tistory.com/121#entry121comment</comments>
      <pubDate>Sun, 10 Aug 2025 21:54:01 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] Ch 04</title>
      <link>https://hwan33.tistory.com/120</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ch 04. 카프카 컨슈머: 카프카에서 데이터 읽기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p.88&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컨슈머는 해당 컨슈머 그룹의 그룹 코디네이터 역할을 지정받은 카프카 브로커에 하트비트를 전송함으로써 멤버십과 할당된 파티션에 대한 소유권을 유지한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. 그룹 코디네이터와 컨트롤러의 차이는?&lt;/b&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfrq48/btsPGFR7GqT/h6VfzcqY967EOFefOvHfH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfrq48/btsPGFR7GqT/h6VfzcqY967EOFefOvHfH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfrq48/btsPGFR7GqT/h6VfzcqY967EOFefOvHfH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfrq48%2FbtsPGFR7GqT%2Fh6VfzcqY967EOFefOvHfH0%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;799&quot; height=&quot;505&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;505&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;정리하자면 카프카의 단일 브로커는 컨트롤러, 그룹 코디네이터 (복수 개의 group 담당 가능), 파티션의 리더 혹은 팔로워의 세가지 역할을 동시에 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Kafka 3.x부터는 KRaft에서 컨트롤러 전용 노드 분리가 가능하다.&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;p. 89&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. 컨슈머가 죽었다고 판단해 새로운 컨슈머에 파티션을 할당했으나, 기존 컨슈머가 살아있었고 동일한 레코드를 중복 처리하게 되는 케이스가 발생 가능한가?&lt;/b&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;A. 아래 시나리오와 같이 가능하다.&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;1. Consumer-1은 정상적으로 메시지를 처리 중 그러나 GC, 네트워크 지연, I/O 블로킹 등으로 heartbeat를 일정 시간 이상 보내지 못함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Group Coordinator는 session.timeout.ms를 넘겼다고 판단 &amp;rarr; Consumer-1을 죽었다고 판단 리밸런싱 발생 &amp;rarr; 해당 파티션은 Consumer-2에게 재할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Consumer-1은 그 후에 깨어나서 계속 같은 파티션의 메시지를 처리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 결과: 같은 오프셋 범위가 Consumer-1과 Consumer-2 양쪽에서 처리됨 &amp;rarr; 중복 처리 발생&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;size18&quot;&gt;&lt;b&gt;Q. 위 시나리오에서 컨슈머 1은 어떻게 살아있다고 다시 인식되는가?&lt;/b&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;A. Consumer-1은 Group Coordinator에게 &quot;새로운 멤버로 재참여&quot;한다.&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. Consumer-1이 GC or 네트워크 이슈로 session.timeout.ms 이상 동안 heartbeat 미전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Consumer-1이 회복됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. heartbeat 재시도 혹은 poll 재시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 하지만 세션은 이미 만료되어 Group Coordinator는 Consumer-1을 새로운 멤버로 인식하여 JOIN_GROUP 요청&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;size18&quot;&gt;&lt;b&gt;Q. 그럼 commitSync는 이미 퇴출된 컨슈머에 의해서도 반영될 수 있는가?&lt;/b&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;A. 컨슈머가 &quot;이미 죽은 걸로 간주&quot;되어 리밸런싱이 발생한 후에도, Coordinator는 뒤늦게 도착한 commit 요청을 반영할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;consumer가 아직 그룹에 있어야만 offset commit을 할 수 있다는 강제 조건이 없고, late commit을 유연하게 수용하기 위한 설계이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 89&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. 정적 그룹 멤버십과 timeout, heartbeat 등의 설정을 길게 유지하는 것의 차이는?&lt;/b&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6fhDP/btsPDWBcJvS/hs7LlfSrrnAStXtH0CKlM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6fhDP/btsPDWBcJvS/hs7LlfSrrnAStXtH0CKlM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6fhDP/btsPDWBcJvS/hs7LlfSrrnAStXtH0CKlM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6fhDP%2FbtsPDWBcJvS%2Fhs7LlfSrrnAStXtH0CKlM0%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;798&quot; height=&quot;224&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;224&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;정적 멤버십의 장점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 재시작, 일시적 장애 시 리밸런싱 없이 복귀 가능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예 : 컨테이너 재기동, rolling deploy&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존 파티션 -&amp;gt; 동일 Consumer에 할당&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 기반 처리(stateful consumer)에서 매우 중요&lt;/li&gt;
&lt;li&gt;Kafka Streams, RocksDB 등을 쓰는 경우 매우 효과적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 정적 멤버십 + 충분한 session.timeout.ms 조합&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;/ul&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;size18&quot;&gt;&lt;b&gt;Q. 정적 멤버십을 사용하는 컨슈머가 timeout 끝나고 복귀하면?&lt;/b&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;A. 새 멤버로 간주되어 리밸런싱 발생하며, 다른 컨슈머가 동일한 id로 join한 상태라면 MemberIdRequiredException을 일으키거나 기존 멤버를 강제로 퇴출한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 94&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그렇기 때문에 현재 컨슈머 코드에서 레코드를 읽어오지 않고 메타데이터만 가져오기 위해 poll(0)을 호출하고 있다면(상당히 일반적으로 쓰이는 우회 방법이다), 이를 poll(Duration.ofMillis(0))로 바꾼다고 해서 같은 결과를 기대할 수 없는 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. 메타데이터만 가져오는 poll 이란?&lt;/b&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;A. consumer.poll() 호출 시, 실제 메시지는 없지만, Kafka 브로커로부터 partition, offset, group 상태 등의 메타데이터 정보만 받아오는 상황이다.&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;대표적인 메타데이터 poll 케이스는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW2tbL/btsPEppFAod/fkvngCozQUG0zuB6ie6z40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW2tbL/btsPEppFAod/fkvngCozQUG0zuB6ie6z40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW2tbL/btsPEppFAod/fkvngCozQUG0zuB6ie6z40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW2tbL%2FbtsPEppFAod%2FfkvngCozQUG0zuB6ie6z40%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;793&quot; height=&quot;447&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;447&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 95&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;fetch.max.bytes&lt;br /&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;&lt;b&gt;Q. 최대 설정값이 넘어도 전송 가능한가?&lt;/b&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;A. 맞다. Kafka에서 fetch.max.bytes는 엄격한 hard limit이 아니라 소프트 상한선이라 브로커는 설정된 최대값을 초과하더라도 배치 단위로는 예외적으로 초과 전송할 수 있다.&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;p. 102&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;앞에서 설명한 것과 같이, 카프카의 고유한 특성 중 하나는 많은 JMS 큐들이 하는 것처럼 컨슈머로부터의 응답을 받는 방식이 아니라는 점이다.&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;Kafka&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 보내고 나면 브로커는 &quot;이 메시지를 누가 읽었는지&quot; 알지 못한다&lt;/li&gt;
&lt;li&gt;Consumer가 읽었는지, 실패했는지, 중복 처리했는지는 브로커가 관여하지 않고, 오로지 Consumer가 offset을 어디까지 commit 했는지만을 기준으로 판단한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JMS Queue&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Consumer가 처리 후 ack를 보내야만 메시지가 삭제된다&lt;/li&gt;
&lt;li&gt;ack가 없으면 다시 전송하거나 DLQ로 이동한다&lt;/li&gt;
&lt;/ul&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;p. 104&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. consumer.commitSync()와 acknowledgement.acknowledge()의 차이는?&lt;/b&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;A. 둘 다 offset 커밋이라는 관점에서는 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;acknowledgement.acknowledge()는 Spring Kafka Listener에서 추상화된 개념이고, 내부적으로 consumer.commitSync() 또는 commitAsync()를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;consumer.commitSync()는 Kafka Consumer API 이며 로우레벨이다.&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;p. 112&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. seek(), poll() 의 차이는?&lt;/b&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;A. seek()은 어떤 오프셋부터 메시지를 읽을지 위치를 지정하는 메서드이고, poll()은 지정된 위치부터 메시지를 실제로 가져오는 메서드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tlbCY/btsPGG4AI8o/6maKakQSbGWD1IYAPfZalK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tlbCY/btsPGG4AI8o/6maKakQSbGWD1IYAPfZalK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tlbCY/btsPGG4AI8o/6maKakQSbGWD1IYAPfZalK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtlbCY%2FbtsPGG4AI8o%2F6maKakQSbGWD1IYAPfZalK%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;789&quot; height=&quot;290&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;290&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;size18&quot;&gt;&lt;b&gt;Q. 그렇다면 seek() 호출 시 __consumer_offsets 토픽에 무언가 기록되거나 전송되는가?&lt;/b&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;A. 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;seek()은 Consumer 인스턴스의 읽기 커서를 바꾸는 클라이언트 측 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka 브로커나 __consumer_offsets 토픽에는 아무것도 전송하지 않는다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 121&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컨슈머가 그룹에 조인할 필요가 없으니 subscribe() 메서드를 호출할 일이야 없겠지만, 오프셋을 커밋하려면 여전히 group.id 값을 설정해줄 필요가 있을 것이다.&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;Kafka에서 offset 커밋은 브로커의 내부 토픽인 __consumer_offsets에 다음과 같은 key로 저장됩니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(group.id,&amp;nbsp;topic,&amp;nbsp;partition)&amp;nbsp;&amp;rarr;&amp;nbsp;offset&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;즉, Kafka 브로커 입장에서 &lt;b&gt;&quot;이 커밋은 누구의 오프셋인지&quot;를 식별할 수 있어야 저장&lt;/b&gt;할 수 있습니다.&lt;br /&gt;&amp;rarr; 따라서 실제로 그룹 참여(subscribe &amp;rarr; rebalance)는 하지 않아도, &lt;b&gt;커밋 자체에는 group.id가 필수&lt;/b&gt;입니다.&lt;/p&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/120</guid>
      <comments>https://hwan33.tistory.com/120#entry120comment</comments>
      <pubDate>Sun, 3 Aug 2025 17:19:02 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] Ch 03</title>
      <link>https://hwan33.tistory.com/119</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ch 03. 카프카 프로듀서: 카프카에 메시지 쓰기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 62, 63&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;buffer.memory&lt;br /&gt;이 매개변수는 프로듀서가 메시지를 전송하기 전에 메시지를 대기시키는 버퍼의 크리(메모리의 양)를 결정한다.&lt;br /&gt;&lt;br /&gt;batch.size&lt;br /&gt;같은 파티션에 다수의 레코드가 전송될 경우 프로듀서는 이것들을 배치 단위로 모아서 한꺼번에 전송한다.&lt;br /&gt;이 매개변수는 각각의 배치에 사용될 메모리의 양을 결정한다('개수'가 아니라 '바이트' 단위임에 주의하라)&lt;/blockquote&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/corFtj/btsPBjvuqWT/4WkJZ2srk9B422R7Cx6N90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/corFtj/btsPBjvuqWT/4WkJZ2srk9B422R7Cx6N90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/corFtj/btsPBjvuqWT/4WkJZ2srk9B422R7Cx6N90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcorFtj%2FbtsPBjvuqWT%2F4WkJZ2srk9B422R7Cx6N90%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;1266&quot; height=&quot;646&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;646&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;buffer.memory : 기본 값 32MB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;batch.size : 기본 값 16KB&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;buffer.memory 안에 여러 크기의 batch.size를 가진 batch 들이 생성된다&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멱등성 보장 관련 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;acks = all&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dchEOo/btsPCnKDvo6/C798SpqxBGaeQEoQEokkgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dchEOo/btsPCnKDvo6/C798SpqxBGaeQEoQEokkgk/img.png&quot; data-alt=&quot;https://magpienote.tistory.com/251&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dchEOo/btsPCnKDvo6/C798SpqxBGaeQEoQEokkgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdchEOo%2FbtsPCnKDvo6%2FC798SpqxBGaeQEoQEokkgk%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;913&quot; height=&quot;580&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://magpienote.tistory.com/251&lt;/figcaption&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;모든 팔로워가 패치가 되었는지 기다린 후에 ack 응답을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정만으로는 요청 재시도 시 중복 데이터가 생성된다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;h4 data-ke-size=&quot;size20&quot;&gt;enable.idempotence = true&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFrrJw/btsPBlNxn9t/rD0ZAxoThtcamNzR9FJc7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFrrJw/btsPBlNxn9t/rD0ZAxoThtcamNzR9FJc7K/img.png&quot; data-alt=&quot;https://medium.com/@shesh.soft/kafka-idempotent-producer-and-consumer-25c52402ceb9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFrrJw/btsPBlNxn9t/rD0ZAxoThtcamNzR9FJc7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFrrJw%2FbtsPBlNxn9t%2FrD0ZAxoThtcamNzR9FJc7K%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;875&quot; height=&quot;205&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://medium.com/@shesh.soft/kafka-idempotent-producer-and-consumer-25c52402ceb9&lt;/figcaption&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;enable.idempotence = true로 설정하면, 각 프로듀서에는 고유한 프로듀서 ID (PID)가 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서는 메시지를 브로커에 보낼 때마다 이 PID를 포함하며, 각 메시지는 순차적으로 증가하는 시퀀스 번호를 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로듀서가 메시지를 보내는 각 토픽 파티션마다 별도의 시퀀스가 유지되고, 브로커는 파티션별로 성공적으로 처리된 PID-SEQ 번호 조합 중 가장 큰 값을 추적한다.&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdHtMo/btsPB7gYDvA/khaVk8cMmzIzw2FAaW0Mk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdHtMo/btsPB7gYDvA/khaVk8cMmzIzw2FAaW0Mk0/img.png&quot; data-alt=&quot;https://medium.com/@shesh.soft/kafka-idempotent-producer-and-consumer-25c52402ceb9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdHtMo/btsPB7gYDvA/khaVk8cMmzIzw2FAaW0Mk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdHtMo%2FbtsPB7gYDvA%2FkhaVk8cMmzIzw2FAaW0Mk0%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;875&quot; height=&quot;304&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://medium.com/@shesh.soft/kafka-idempotent-producer-and-consumer-25c52402ceb9&lt;/figcaption&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;브로커는 프로듀서의 요청이 PID/토픽 파티션 쌍에서 마지막으로 커밋된 메시지보다 시퀀스 번호가 정확히 1만큼 크지 않은 경우, 프로듀서 요청을 거절한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 프로듀서는 실패에 따른 요청 재시도를 할 수 있지만 모든 메시지는 로그에 정확히 한 번만 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(단, 프로듀서마다 고유 PID가 할당되므로, 단일 프로듀서 세션 내에서만 멱등성을 보장할 수 있다)&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;max.in.flight.requests.per.connection = 5&lt;/h4&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 data-ke-size=&quot;size16&quot;&gt;1. 메시지 순서 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 멱등 프로듀서는 같은 파티션에 대해 메시지의 순서를 보장해야 한다. 이 값이 5 이하일 때, 프로듀서에서 한 메시지가 실패하면 그 후의 메시지들은 서버에 전송되지 않는다. 이러한 동작은 메시지의 순서가 변경되지 않도록 보장하는 데 중요하다.&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;2. 중복 메시지 방지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 멱등 프로듀서는 메시지의 중복 전송을 방지한다. 이 값이 너무 크면 네트워크 지연 등의 문제로 인해 중복 메시지가 발생할 가능성이 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFgyV/btsPB4Lj9bd/kOj8xsSuWKbSkydg5bFxw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFgyV/btsPB4Lj9bd/kOj8xsSuWKbSkydg5bFxw1/img.png&quot; data-alt=&quot;https://magpienote.tistory.com/251&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFgyV/btsPB4Lj9bd/kOj8xsSuWKbSkydg5bFxw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFgyV%2FbtsPB4Lj9bd%2FkOj8xsSuWKbSkydg5bFxw1%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;1276&quot; height=&quot;813&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://magpienote.tistory.com/251&lt;/figcaption&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;h4 data-ke-size=&quot;size20&quot;&gt;멱등성 프로듀서의 한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등성 프로듀서는 동일한 세션 내에서만 정확히 한 번의 전달을 보장한다. 여기서 '동일한 세션'이란, PID의 생명주기를 의미한다. 만약 멱등성 프로듀서로 작동하는 프로듀서 애플리케이션에 문제가 발생해 종료되고 다시 시작하면 PID가 변경된다.&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;동일한 데이터를 전송하더라도, PID가 바뀌면 브로커는 다른 프로듀서 애플리케이션이 다른 데이터를 보냈다고 판단한다. 따라서 멱등성 프로듀서는 장애가 발생하지 않는 상황에서만 데이터를 정확히 한 번 적재하는 것을 보장한다는 점을 명심해야 한다.&lt;/p&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/119</guid>
      <comments>https://hwan33.tistory.com/119#entry119comment</comments>
      <pubDate>Sun, 27 Jul 2025 16:30:43 +0900</pubDate>
    </item>
    <item>
      <title>[카프카 핵심 가이드] ch 01, 02</title>
      <link>https://hwan33.tistory.com/118</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ch 01. 카프카 시작하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 7&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;&lt;b&gt;Q. 카프카의 오프셋은 각 파티션 내에서 반드시 단조 증가해야 하지 않나?&lt;/b&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;A. 카프카에서 각 파티션에 기록되는 메시지는 순차적인 오프셋을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, offset n 다음엔 반드시 offset n+1 이 온다&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;따라서 컨슈머 입장에서 만약 오프셋이 100, 101, 102, 104 순으로 온다면 103은 유실 혹은 아직 도착하지 않은 상태로 간주될 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단조 증가가 깨지면 손실 또는 중복 발생으로 판단할 수 밖에 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q. 그렇다면 왜 책에는 저렇게 쓰여 있을까?&lt;/b&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;A. 다음과 같은 케이스이지 않을까 추측해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Log Compaction이 활성화된 토픽에서는 동일 키의 오래된 레코드가 삭제될 수 있다.&lt;br /&gt;다만 여기에 대해서도 오프셋 자체는 사라지지 않으며, 빈 슬롯으로 존재하거나 skip 된다&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;p. 8&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;&lt;b&gt;Q. 컨트롤러의 역할을 하는 브로커는 파티션 리더/팔로워로서의 역할을 수행하는가?&lt;/b&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;A. 컨트롤러가 된 브로커도 여전히 파티션 리더/팔로워로서 메시지 저장 및 전달 역할을 정상적으로 수행한다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션 리더 선출 : Leader election 수행&lt;/li&gt;
&lt;li&gt;메타데이터 관리 : 토픽 파티션, 브로커 상태를 Zookeeper (또는 KRaft)에 반영&lt;/li&gt;
&lt;li&gt;브로커 장애 감지 : 다른 브로커의 heartbeat 감시&lt;/li&gt;
&lt;li&gt;토픽/파티션 변경 관리 : 새 토픽 생성, 파티션 수 변경 등의 요청 처리&lt;/li&gt;
&lt;li&gt;ISR(복제 대기열) 관리 : in-sync replica 리스트 유지/관리&lt;/li&gt;
&lt;/ul&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;Q. 그렇다면 실무적으로 컨트롤러는 부하 이슈가 없을까?&lt;/b&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;A. 컨트롤러는 메타데이터 관리를 담당하기 때문에 다음과 같은 단점이 있을 수 있다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리더 선출 등 중요한 이벤트가 몰릴 경우 CPU 사용률이 높아질 수 있다&lt;/li&gt;
&lt;li&gt;많은 토픽/파티션 수를 가진 대형 클러스터에서는 컨트롤러 역할이 병목 포인트가 될 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Kafka 2.8 이후에는 컨트롤러를 별도 클러스터(KRaft mode)로 분리 운영하는 것도 지원한다&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;p. 9&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;&lt;b&gt;Q. 하둡으로 영구 보관할 필요성이 있을까?&lt;/b&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;A. Kafka만으로는 &quot;영구 보관&quot;과 &quot;감사 목적의 이력 관리&quot;에 한계가 있기 때문에, 실무에서는 Hadoop(HDFS), S3, 혹은 Data Warehouse 등으로 Kafka 메시지를 이관하여 저장하는 패턴이 흔히 사용된다&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Ch 02. 카프카 설치하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 23&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;peerPort : 앙상블 안의 서버들이 서로 통신할 때 사용하는 TCP 포트 번호&lt;br /&gt;leaderPort : 리더를 선출하는 데 사용되는 TCP 포트 번호&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;&lt;b&gt;Q. 포트를 구분하는 이유는 무엇인가?&lt;/b&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;A. 이유는 아래와 같다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;책임이 다른 두 종류의 통신을 분리하기 위해&lt;/b&gt;&lt;br /&gt;Zookeeper는 리더 선출을 비동기적으로, 주기적으로 반복할 수 있으며, 이와 동시에 리더-팔로워 간 데이터 sync도 진행되므로 명확한 포트 분리가 필요하다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통신 안정성과 보안을 위해&lt;/b&gt;&lt;br /&gt;서로 다른 포트를 사용하면:&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방화벽, ACL, NAT, 보안 정책 적용이 더 유연&lt;/li&gt;
&lt;li&gt;Election 관련 트래픽과 데이터 트래픽을 독립적으로 모니터링 가능&lt;/li&gt;
&lt;li&gt;포트 충돌, 병목 등을 줄일 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성을 고려한 설계&lt;/b&gt;&lt;br /&gt;Zookeeper는 기본적으로 분산 시스템에서 최소 3노드 이상 구성되며, 서버 수가 많아질수록 내부 통신 복잡도도 증가한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p. 28&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;auto.leader.rebalance.enable &lt;br /&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;&lt;b&gt;Q. 리더의 역할을 어떤 단위로 분산하는가?&lt;/b&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;A. 이 설정은 Kafka 브로커 간 리더 파티션의 부하를 자동으로 분산시켜, 특정 브로커에 리더 파티션이 과도하게 몰리는 현상을 방지하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 리더를 골고루 나누는 것처럼 들릴 수 있지만, 실제로는 정책 기반 리더 재할당 작업이 주기적으로 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정을 true로 하면, Kafka Controller가 주기적으로 리더 분포 상태를 점검하고, 특정 브로커에 리더가 과도하게 몰렸으면 일부 파티션의 리더를 다른 브로커로 이동시킨다.&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;Q. 실무에서의 영향도는?&lt;/b&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;A. 리더-팔로워 간 리더 리밸런싱은 일반적으로 문제가 거의 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(partition reassignment 와 consumer rebalance에 비해 영향 범위가 현저히 낮다)&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;Kafka의 컨트롤러가 ISR 내에서 리더를 교체하는 것이고, 클러스터 안정성과 부하 분산에 오히려 긍정적이다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션 이동 없음&lt;/li&gt;
&lt;li&gt;리플리카 재배치 없음&lt;/li&gt;
&lt;li&gt;토픽 재설정 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 리더 전환 중에는 short-lived consumer rebalance 발생 가능하며, 네트워크 영역 간 리더가 바뀌면 latency가 급증할 수 있다.&lt;/p&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/118</guid>
      <comments>https://hwan33.tistory.com/118#entry118comment</comments>
      <pubDate>Sun, 20 Jul 2025 17:57:01 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴의 아름다움] ch 8.1 ~ 8.7</title>
      <link>https://hwan33.tistory.com/117</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;p. 388&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;8.1.6절에서 guava event bus를 사용하여 UserController 클래스를 다시 구현했다 하지만 UserController 클래스는 여전히 스레드 풀을 생성하고 옵서버를 등록하는 등 옵서버 패턴과 관련된 비지니스와 관련 없는 코드가 많이 남아 있다. 이 UserController 클래스를 어떻게 더 개선할 수 있을지 생각해보자.&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;EventBusConfigurator 등의 모듈에서 옵서버를 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EventDispatcher 같은 추상 인터페이스를 정의하고 GuavaEventDispatcher 구현체를 실제 UserController 에서 사용하도록 한다.&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;p. 390&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;read() 함수는 데이터를 읽는 전 과정을 정의하는 템플릿 메서드로, 하위 클래스별로 정의된 추상 메서드를 노출한다. 이 추상 메서드의 이름 역시 read()로 동일하지만, 매개변수와 템플릿 메서드는 같지 않다.&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;public abstract int read()&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;각 InputStream 서브클래스는 이 메서드만 최소한으로 구현하면 &quot;스트림에서 데이터 하나를 꺼내는&quot; 기능을 갖추게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public int read(byte[] b, int off, int len)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 바이트를 한 번에 읽어들이는 편의 메서드로, 기본적으로 read()를 여러 번 호출해 구현되어 있다.&lt;/li&gt;
&lt;li&gt;구현체가 업로드와 다운로드 같은 &amp;ldquo;대용량 데이터 처리&amp;rdquo;에 최적화하려면, 이 메서드를 오버라이드하여 더 효율적인 버퍼 복사 로직을 넣으면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 스트림에서 데이터 하나를 꺼내는 기능이 구현체마다 다르게 정의되는 이유는?&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;A. 각 스트림 구현체마다 &quot;한 바이트 읽기&quot;가 실제로 수행하는 일은 완전히 다르다.&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;1. FileInputStream 은 OS의 파일 디스크립터를 통해 직접 시스템 콜을 발생시켜야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SocketInputStream 은 네트워크 버퍼에서 데이터를 가져오기 위해 소켓 레벨의 I/O 경로를 타야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. ByteArrayInputStream 은 JVM 힙에 올라 있는 배열에서 단순히 인덱스를 증가시켜 바이트를 꺼낸다.&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;Q. 금융 회계 업무에 상태 머신을 활용한다면?&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;A. 금융&amp;middot;회계 시스템의 &lt;b&gt;전표, 결제, 환불, 승인&lt;/b&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;전표 승인 워크플로우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[생성]&amp;nbsp;&amp;rarr;&amp;nbsp;[승인&amp;nbsp;대기]&amp;nbsp;&amp;rarr;&amp;nbsp;(승인)&amp;nbsp;&amp;rarr;&amp;nbsp;[승인&amp;nbsp;완료]&amp;nbsp;&amp;rarr;&amp;nbsp;[집계&amp;nbsp;대기]&amp;nbsp;&amp;rarr;&amp;nbsp;[집계&amp;nbsp;완료] &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;↘&amp;nbsp;(반려)&amp;nbsp;↗&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;이벤트 : 승인, 요청, 반려, 재요청&lt;/li&gt;
&lt;li&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;전이 : submitForApproval, approve, reject, aggregate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;기타 궁금한 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 팀에서 상태머신이나 상태패턴을 사용해본 경험이 있는지 궁금합니다.&lt;/p&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/117</guid>
      <comments>https://hwan33.tistory.com/117#entry117comment</comments>
      <pubDate>Sun, 27 Apr 2025 21:39:46 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴의 아름다움] ch 7.5 ~ ch 7.7</title>
      <link>https://hwan33.tistory.com/116</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;p. 348&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;내가 이해하기로 퍼사드 패턴은 여러 서브시스템을 묶어 클라이언트에 노출할 단일, 간결한 인터페이스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어댑터 패턴과는 다르게 좋지 않은 설계에만 사용하는 패턴은 아닌 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼사드는 단순히 부실한 설계를 감추는 꼼수가 아니라, 건전한 계층화와 관심사 분리를 위한 패턴이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;p. 348&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;Q. 실제 개발을 하면서 트리 구조의 자료 구조를 생성하고 복합체 패턴을 활용해보신 적이 있는지?&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;계정과목 계층 구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계정과목 (Asset, Liability, Equity 등) 과 하위 세부 계정 (Current Assets, Cash 등) 을 하나의 인터페이스 (AccountComponent)로 다루기&lt;/li&gt;
&lt;li&gt;잔액 합계, 잔액 이동, 재귀적 집계 등을 Composite 노드 (그룹 계정)와 Leaf 노드 (개별 계정)에 동일하게 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;재무제표 구성 요소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재무상태표나 손익계산서의 항목을 챕터 -&amp;gt; 섹션 -&amp;gt; 라인 아이템 구조로 표현&lt;/li&gt;
&lt;li&gt;각 항목의 금액 집계 (getAmount())를 구현체마다 다르게 처리하되, 클라이언트에서는 인터페이스만 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분개 묶음 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하루치 분개, 월말 분개, 연말 조정 분개 등을 CompositeEntry로 묶고, 개별 분개는 JournalEntry Leaf로 구현&lt;/li&gt;
&lt;li&gt;일괄 승인, 일괄 롤백, 검증 로직을 Composite에 한 번만 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부서, 사업부 비용 집계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;법인 전체 -&amp;gt; 사업부 -&amp;gt; 부서 -&amp;gt; 팀 단위로 비용을 계층화&lt;/li&gt;
&lt;li&gt;부서별 비용 합계, 전사 비용 합계를 동일한 코드 (CostComponent.calculate())로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결산 작업 워크플로우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 단계 (전표검증 -&amp;gt; 조정분개 -&amp;gt; 시산표 생성 -&amp;gt; 재무제표 작성)를 Step 인터페이스로 공통화&lt;/li&gt;
&lt;li&gt;전체 결산 작업은 CompositeStep으로, 개별 단계는 LeafStep으로 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;p. 362&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;플라이웨이트 패턴은 JVM의 가비지 컬렉션 기능과는 상성이 좋지 않다.&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;Q. 언제 플라이웨이트를 써야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 1번의 경우에만 사용하자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메모리 절약 효과가 더 큰 경우 : 힙 사용량이 크게 내려가면, GC 부하 증가보다 이득이 크다&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 인스턴스를 수백만 개 생성해야 하는 상황&lt;/li&gt;
&lt;li&gt;인스턴스당 메모리 크기가 크고, 동일한 속성의 객체가 매우 자주 반복될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GC 부담이 더 큰 경우 : 오히려 GC가 더 자주, 길게 돌아가 전체 성능이 떨어진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;풀에 들어가는 플라이웨이트 객체가 너무 많이 Old 영역이 포화될 때&lt;/li&gt;
&lt;li&gt;객체 수가 그리 많지 않아 메모리 절약 효과가 미미할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;p. 368&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;Q. String은 가비지 컬렉션 대상인가?&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;1. 새로 생성된 String 객체 (new 생성자)&lt;/p&gt;
&lt;pre id=&quot;code_1745148913959&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String s = new String(&quot;hello&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;힙에 할당된 일반 객체이므로, 참조가 끊기면 GC 대상이 된다&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;2. 리터럴이나 intern() 된 String 객체&lt;/p&gt;
&lt;pre id=&quot;code_1745148938026&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String a = &quot;hello&quot;;
String b = new String(&quot;hi&quot;).intern();&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;문자열 풀에 저장되어 강한 참조로 유지되므로, 클래스 로더가 언로드되기 전까지는 GC되지 않는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 7 이후에는 이 풀이 힙 영역으로 옮겨졌지만, 여전히 풀 내부에 강한 참조로 남아 있어 쉽게 회수되지 않는다&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;Q. 그렇다면 리터럴이 너무 많으면 메모리 에러가 발생하나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 맞다. 다만 리터럴이 수만 개 이상이 아니라면 보통 문제가 되지 않는다. 동적으로 생성해서 intern()을 남발하거나 대량의 클래스를 런타임에 생성, 로딩 (예: 리플렉션 코드 생성 라이브러리) 할 때 리터럴이 폭증하면 문제가 될 수 있따.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JDK6 이하 (PermGen OOM)&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 리터럴은 PermGen 영역에 올라간다&lt;/li&gt;
&lt;li&gt;클래스 로딩 시 리터럴이 모두 풀에 적재되고, 해제되지 않으므로 PermGen이 가득차면 java.lang.OutOfMemoryError: PermGen space 에러 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JDK7 이상 (힙 OOM)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PermGen이 사라지고 메타스페이스(Metaspace)로 대체되면서, 상수 풀은 힙 영역에 위치한다&lt;/li&gt;
&lt;li&gt;리터럴이나 intern()된 문자열이 너무 많아 힙을 과도하게 차지하면 java.lang.OutOfMemoryError: Java heap space 에러 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>독서</category>
      <author>오렌지색 귤</author>
      <guid isPermaLink="true">https://hwan33.tistory.com/116</guid>
      <comments>https://hwan33.tistory.com/116#entry116comment</comments>
      <pubDate>Sun, 20 Apr 2025 20:40:12 +0900</pubDate>
    </item>
  </channel>
</rss>