<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Kafka on Gukin Han</title>
    <link>https://gukin.dev/tags/kafka/</link>
    <description>Recent content in Kafka on Gukin Han</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Sun, 23 Nov 2025 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://gukin.dev/tags/kafka/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Fat 메시지로 인한 Consumer 메모리 누수 케이스</title>
      <link>https://gukin.dev/posts/kafka-fat-message-consumer-memory-leak/</link>
      <pubDate>Sun, 23 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://gukin.dev/posts/kafka-fat-message-consumer-memory-leak/</guid>
      <description>&lt;h2 id=&#34;문제&#34;&gt;문제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;현상:&lt;/strong&gt; 8개 Fargate Task 중 1개만 메모리 사용률이 지속 상승 (60% → 97%)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과:&lt;/strong&gt; 메모리 알람 후 해당 Task 재시작&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특이점:&lt;/strong&gt; 동일 코드 기반의 다른 Task들은 GC 정상 작동&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;환경:&lt;/strong&gt; Java 21, Spring Boot, Kafka Consumer, MyBatis Batch, AWS Fargate&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;증상-분석&#34;&gt;증상 분석&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;원본 데이터로 부터 재 생성된 그래프&#34; loading=&#34;lazy&#34; src=&#34;https://gukin.dev/posts/kafka-fat-message-consumer-memory-leak/metrics_matplotlib_composite.png&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CloudWatch 메모리 그래프: 특정 Task만 계단식 상승&lt;/li&gt;
&lt;li&gt;덤프 서버: 동일 이벤트 처리에도 톱니형 GC 패턴 (정상)&lt;/li&gt;
&lt;li&gt;GC 자체 문제라기보다는 &lt;strong&gt;객체 참조 유지로 인한 회수 불가&lt;/strong&gt; 상황에 가까움&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;원인-분석&#34;&gt;원인 분석&lt;/h2&gt;
&lt;h3 id=&#34;대량-청크-메시지&#34;&gt;대량 청크 메시지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;권한 갱신 이벤트를 한 번에 2,500건 단위로 Kafka에 게시&lt;/li&gt;
&lt;li&gt;특정 Task가 해당 청크를 독점 처리하면 순간적으로 수만 개 객체 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;중복-역직렬화&#34;&gt;중복 역직렬화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Consumer 내부에서 &lt;code&gt;Map → DTO → VO&lt;/code&gt; 변환을 최대 세 번 반복&lt;/li&gt;
&lt;li&gt;일시적 객체 폭증, Eden → Old Gen 승격 가속&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;mybatis-batch-누적&#34;&gt;MyBatis Batch 누적&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ExecutorType.BATCH&lt;/code&gt; 사용 시 &lt;code&gt;flushStatements()&lt;/code&gt; 전까지 파라미터 참조 유지&lt;/li&gt;
&lt;li&gt;GC 입장에서는 여전히 &amp;ldquo;사용 중&amp;rdquo; 객체로 인식&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;컨슈머-병렬성-부족&#34;&gt;컨슈머 병렬성 부족&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;단일 스레드 리스너로 동작&lt;/li&gt;
&lt;li&gt;특정 파티션이 한 Task에 몰리면 부하 편향 및 메모리 사용량 집중&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;gc-관점에서-정리&#34;&gt;GC 관점에서 정리&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;구분&lt;/th&gt;
          &lt;th&gt;설명&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;GC는 참조가 끊긴 객체만 수거&lt;/td&gt;
          &lt;td&gt;리스트, 세션 등에서 참조 중이면 회수 불가&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;부하가 높으면 safepoint 진입 지연&lt;/td&gt;
          &lt;td&gt;GC 실행 타이밍이 밀리면서 Old Gen 누적&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Old Gen 승격 가속&lt;/td&gt;
          &lt;td&gt;장수 객체가 많아질수록 Old Gen 압박 증가&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;덤프 서버에서 정상인 이유&lt;/td&gt;
          &lt;td&gt;부하가 낮아 GC 개입 여유가 충분&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;단기-개선-해결책&#34;&gt;단기 개선 해결책&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;조치&lt;/th&gt;
          &lt;th&gt;설명&lt;/th&gt;
          &lt;th&gt;기대 효과&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;DTO 직접 바인딩&lt;/td&gt;
          &lt;td&gt;@KafkaListener에서 Map 대신 DTO로 수신&lt;/td&gt;
          &lt;td&gt;중복 역직렬화 제거&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;청크 분할 처리&lt;/td&gt;
          &lt;td&gt;2,500건 → 500건 단위로 분할 처리&lt;/td&gt;
          &lt;td&gt;동시 생성 객체 수 감소, 생존 시간 단축&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;리스트 참조 해제&lt;/td&gt;
          &lt;td&gt;처리 후 리스트 &lt;code&gt;clear()&lt;/code&gt; 또는 참조 &lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;GC 회수 가능 시점 앞당김&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Batch flush 주기 조정&lt;/td&gt;
          &lt;td&gt;200건마다 &lt;code&gt;flushStatements()&lt;/code&gt; 호출&lt;/td&gt;
          &lt;td&gt;MyBatis 내부 파라미터 참조 조기 해제&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;필드 누락 복구&lt;/td&gt;
          &lt;td&gt;누락된 &lt;code&gt;accessType&lt;/code&gt; 필드 복원&lt;/td&gt;
          &lt;td&gt;불필요한 대량 delete 방지&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;장기-개선-해결책&#34;&gt;장기 개선 해결책&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Kafka Consumer 병렬성 향상 (&lt;code&gt;factory.setConcurrency(2)&lt;/code&gt; 등)&lt;/li&gt;
&lt;li&gt;Partition assignment 전략을 &lt;code&gt;cooperative-sticky&lt;/code&gt;로 조정&lt;/li&gt;
&lt;li&gt;DLQ(Dead Letter Topic) 구성으로 재시도 루프 제거&lt;/li&gt;
&lt;li&gt;Micrometer로 배치 크기, 처리 시간, lag 메트릭 수집 및 모니터링&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    <item>
      <title>근태 서비스에 이벤트 기반 아키텍처를 적용한다면 - Polling Adapter &#43; Kafka</title>
      <link>https://gukin.dev/posts/ehr-polling-adapter-event/</link>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://gukin.dev/posts/ehr-polling-adapter-event/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;I forgot to punch out&#34; loading=&#34;lazy&#34; src=&#34;https://gukin.dev/posts/ehr-polling-adapter-event/img.gif&#34;&gt;&lt;/p&gt;
&lt;p&gt;우리는 매일 아침 출근을 한다. 근태관리에서 출퇴근은 결국 돈과 관련되기 때문에 고용주/고용인 양쪽 입장에서 모두 중요하며 민감하게 생각한다.&lt;/p&gt;
&lt;p&gt;과거에는 종이에 구멍을 뚫어(punch) 출퇴근 관리를 하였다. 언어란 신기하게도 현대 사회에서는 더 이상 사용하게 되지 않은 사물이나 행위를 나타내는 용어를 관습적으로 사용하는 경우가 많다. 여기서 얘기하는 펀치(punch)도 유사하게 출퇴근을 나타내는 용어로 여전히 사용 중이다. 펀치 인은 출근, 펀치 아웃은 퇴근. 아마 일부 외국 e-HR 시스템에서는 여전히 이러한 용어들을 사용하지 않을까?&lt;/p&gt;</description>
    </item>
    <item>
      <title>트랜잭션에서 이벤트로 - Sync / Async / Redis 성능 비교와 TTV 분석</title>
      <link>https://gukin.dev/posts/sync-async-redis-like-performance/</link>
      <pubDate>Fri, 29 Aug 2025 00:00:00 +0000</pubDate>
      <guid>https://gukin.dev/posts/sync-async-redis-like-performance/</guid>
      <description>&lt;h2 id=&#34;문제&#34;&gt;문제&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Transactional&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;like&lt;/span&gt;(String loginId, Long productId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 유저 조회&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    User user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; userService.&lt;span style=&#34;color:#a6e22e&#34;&gt;getByLoginId&lt;/span&gt;(loginId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 상품-좋아요 Insert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;boolean&lt;/span&gt; isInserted &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; productLikeRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      .&lt;span style=&#34;color:#a6e22e&#34;&gt;insertIgnoreDuplicateKey&lt;/span&gt;(user.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUserId&lt;/span&gt;(), productId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;isInserted) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 좋아요 수 집계 Update&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    productRepository.&lt;span style=&#34;color:#a6e22e&#34;&gt;incrementLikeCount&lt;/span&gt;(productId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;좋아요 기능에서 처음엔 모든 로직을 하나의 트랜잭션 안에서 &lt;strong&gt;동기적&lt;/strong&gt;(Sync)으로 처리&lt;/li&gt;
&lt;li&gt;안정적이지만 &lt;strong&gt;트랜잭션 크기&lt;/strong&gt;가 커지고, 상품 핫키에 경합으로 인한 &lt;strong&gt;병목&lt;/strong&gt;이 발생&lt;/li&gt;
&lt;li&gt;그래서, 이벤트 기반 설계를 통해 결합도를 낮추고(loosely coupled), 책임을 분리하려는 시도&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Github PR : &lt;a href=&#34;https://github.com/gukin-han/commercial-service/pull/7&#34;&gt;https://github.com/gukin-han/commercial-service/pull/7&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
