<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Home on San Kim의 위키</title><link>https://kimtks456.github.io/</link><description>Recent content in Home on San Kim의 위키</description><generator>Hugo</generator><language>ko-kr</language><lastBuildDate>Thu, 14 May 2026 00:21:11 +0900</lastBuildDate><atom:link href="https://kimtks456.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>1. 스케줄러 조사</title><link>https://kimtks456.github.io/docs/springbatch/concept/1_scheduler-research/</link><pubDate>Thu, 14 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/springbatch/concept/1_scheduler-research/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;배치 공통 플랫폼 구축 시 필요한 &lt;strong&gt;자동 스케줄링&lt;/strong&gt;과 &lt;strong&gt;중복 실행 방지&lt;/strong&gt; 솔루션을 비교·분석한다.
선택 기준은 Spring Batch와의 통합 난이도, 별도 인프라 필요 여부, 중복 실행 방지 메커니즘이다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-핵심-요구사항"&gt;1. 핵심 요구사항&lt;a class="anchor" href="#1-%ed%95%b5%ec%8b%ac-%ec%9a%94%ea%b5%ac%ec%82%ac%ed%95%ad"&gt;#&lt;/a&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;자동 스케줄링&lt;/td&gt;
 &lt;td&gt;특정 시간에 Job 자동 실행 (cron 표현식)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;중복 실행 방지&lt;/td&gt;
 &lt;td&gt;같은 Job이 동시에 2개 이상 실행되는 것 차단&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;런타임 변경&lt;/td&gt;
 &lt;td&gt;배포 없이 스케줄 cron 변경 가능 여부&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;인프라 비용&lt;/td&gt;
 &lt;td&gt;별도 서버/플랫폼 없이 애플리케이션 내장 가능 여부&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="2-선택지별-분석"&gt;2. 선택지별 분석&lt;a class="anchor" href="#2-%ec%84%a0%ed%83%9d%ec%a7%80%eb%b3%84-%eb%b6%84%ec%84%9d"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="2-1-scheduled--shedlock"&gt;2-1. &lt;code&gt;@Scheduled&lt;/code&gt; + ShedLock&lt;a class="anchor" href="#2-1-scheduled--shedlock"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Spring Batch App
├── @Scheduled(cron = &amp;#34;0 0 2 * * *&amp;#34;) ← 스케줄
└── @SchedulerLock(name = &amp;#34;jobName&amp;#34;) ← 중복 방지 (DB 락)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;동작 원리&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>1. 초기설계</title><link>https://kimtks456.github.io/docs/springbatch/practice/1_initial-design/</link><pubDate>Tue, 12 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/springbatch/practice/1_initial-design/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 &lt;code&gt;batch-common&lt;/code&gt; 공통 플랫폼의 &lt;strong&gt;패키지 구조&lt;/strong&gt;와
각 기능 영역별 &lt;strong&gt;구현 목록&lt;/strong&gt; 및 실제 구현 중 확인된 &lt;strong&gt;설계 결정 사항&lt;/strong&gt;을 정리한다.
실제 코드는 &lt;code&gt;/spring-batch-practice&lt;/code&gt; 저장소의 &lt;code&gt;batch-common&lt;/code&gt; 모듈에 있다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-batch-common-실제-패키지-구조"&gt;1. batch-common 실제 패키지 구조&lt;a class="anchor" href="#1-batch-common-%ec%8b%a4%ec%a0%9c-%ed%8c%a8%ed%82%a4%ec%a7%80-%ea%b5%ac%ec%a1%b0"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;batch-common/
└── src/main/java/com/example/spring_batch_practice/
 │
 ├── core/ # 공통 인프라 (job 독립)
 │ ├── api/ # Admin REST API
 │ │ ├── BatchAdminController.java # GET/POST 엔드포인트
 │ │ ├── BatchJobService.java # 실행·중단·재시작·이력 조회
 │ │ └── dto/
 │ │ └── JobExecutionResponse.java # read/write/skip 집계 DTO
 │ │
 │ ├── error/
 │ │ └── BatchErrorConfig.java # Retry·Skip 정책 기본 빈
 │ │ # @ConditionalOnMissingBean → 잡별 재정의 가능
 │ │
 │ ├── item/
 │ │ ├── reader/
 │ │ │ ├── JdbcCursorReaderFactory.java # JdbcCursorItemReader 팩토리
 │ │ │ ├── JpaPagingReaderFactory.java # JpaPagingItemReader 팩토리
 │ │ │ └── MyBatisCursorReaderFactory.java # MyBatisCursorItemReader 팩토리
 │ │ └── writer/
 │ │ ├── JpaItemWriterFactory.java # JpaItemWriter 팩토리
 │ │ └── MyBatisBatchWriterFactory.java # MyBatisBatchItemWriter 팩토리
 │ │
 │ ├── listener/
 │ │ └── JobLoggingListener.java # 잡 실행 전후 로그 (시작·종료·소요시간)
 │ │
 │ └── partition/
 │ ├── RangePartitioner.java # ID 범위 기반 파티셔너 (JdbcTemplate 사용)
 │ └── PartitionStepHelper.java # 파티션 Step 정적 빌더
 │
 └── job/
 └── sample/ # 샘플 잡 (JPA vs MyBatis 비교용)
 ├── domain/
 │ └── Order.java # @Entity(name=&amp;#34;BatchOrder&amp;#34;), @Table(name=&amp;#34;orders&amp;#34;)
 ├── mybatis/
 │ └── OrderMapper.java # @Mapper, SELECT/UPDATE/INSERT/DELETE
 ├── SampleJpaJobConfig.java # JpaCursorItemReader + JpaItemWriter
 └── SampleMyBatisJobConfig.java # MyBatisCursorItemReader + MyBatisBatchItemWriter

└── src/main/resources/
 ├── application.yaml
 ├── mapper/
 │ └── OrderMapper.xml # MyBatis SQL XML
 └── schema/
 ├── batch-schema-postgresql.sql # Spring Batch 메타 테이블 DDL (수동 관리)
 └── batch-schema-drop-postgresql.sql # 메타 테이블 DROP DDL&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2-의존성"&gt;2. 의존성&lt;a class="anchor" href="#2-%ec%9d%98%ec%a1%b4%ec%84%b1"&gt;#&lt;/a&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;spring-boot-starter-batch&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;배치 코어&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;spring-boot-starter-data-jpa&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;JPA 리더·라이터&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;spring-boot-starter-web&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;Admin REST API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;spring-boot-starter-actuator&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;헬스체크, 메트릭&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;mybatis-spring-boot-starter&lt;/td&gt;
 &lt;td&gt;3.0.4&lt;/td&gt;
 &lt;td&gt;MyBatis 자동구성 (&lt;code&gt;mybatis-spring&lt;/code&gt; 포함 → 배치 클래스도 내장)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;spring-cloud-starter-task&lt;/td&gt;
 &lt;td&gt;Cloud 관리&lt;/td&gt;
 &lt;td&gt;Task 메타 테이블&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;postgresql&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;운영 DB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;h2&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;테스트 인메모리 DB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;lombok&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;보일러플레이트 제거&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;spring-batch-test&lt;/td&gt;
 &lt;td&gt;Boot 관리&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;JobLauncherTestUtils&lt;/code&gt;, &lt;code&gt;JobRepositoryTestUtils&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;code&gt;mybatis-spring-batch&lt;/code&gt; 별도 아티팩트는 존재하지 않는다.
&lt;code&gt;MyBatisCursorItemReader&lt;/code&gt;, &lt;code&gt;MyBatisBatchItemWriter&lt;/code&gt; 등 배치 통합 클래스는
&lt;code&gt;mybatis-spring&lt;/code&gt; (&lt;code&gt;org.mybatis.spring.batch.*&lt;/code&gt;) 내에 포함되어 있으며,
&lt;code&gt;mybatis-spring-boot-starter&lt;/code&gt;를 추가하면 함께 제공된다.&lt;/p&gt;</description></item><item><title>Kafka 통합 테스트 — auto-offset-reset 미설정 시 메시지 유실</title><link>https://kimtks456.github.io/docs/kafka/issues/1_test_auto_offset_reset/</link><pubDate>Mon, 11 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/issues/1_test_auto_offset_reset/</guid><description>&lt;h2 id="1-문제"&gt;1. 문제&lt;a class="anchor" href="#1-%eb%ac%b8%ec%a0%9c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spring Boot + Testcontainers 기반 Kafka 통합 테스트에서 다음과 같은 실패가 반복 발생했다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Wanted but not invoked:
orderEventConsumer.handle(&amp;lt;any OrderCreatedEvent&amp;gt;);
-&amp;gt; at com.example.order.kafka.OrderEventConsumer.handle(...)
Actually, there were zero interactions with this mock.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;producer.publish()&lt;/code&gt; 로 메시지를 발행한 뒤 &lt;code&gt;verify(consumer, timeout(5000).times(1))&lt;/code&gt; 를 호출했지만, 컨슈머가 메시지를 수신하지 못했다.&lt;/p&gt;
&lt;p&gt;영향 테스트:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OrderEventFlowTest.주문_이벤트_발행_후_컨슈머_수신()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OrderPubSubTest.여러_이벤트_연속_발행_모두_수신()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OrderIdempotencyTest.어노테이션_없을때_동일_eventId_두번_발행_두번_모두_처리()&lt;/code&gt; — latch.await(5s) timeout&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-원인"&gt;2. 원인&lt;a class="anchor" href="#2-%ec%9b%90%ec%9d%b8"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Race condition: 컨슈머의 파티션 할당보다 프로듀서의 발행이 먼저 완료된다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[context 시작]
 ├─ producer bean 초기화 완료 (즉시)
 └─ consumer listener container 시작 → 백그라운드 스레드에서 join-group 진행 중

[test 메서드 실행 (context 준비 직후)]
 ├─ producer.publish() → 메시지 offset 0에 적재
 └─ consumer의 join-group이 아직 진행 중...

[몇 초 뒤]
 └─ consumer join-group 완료 → 파티션 할당
 → auto-offset-reset: latest (기본값)
 → 최신 offset = 1 (메시지 다음 위치)
 → offset 0의 메시지를 건너뜀 → 유실&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;실제 로그:&lt;/p&gt;</description></item><item><title>1. Colima — Mac Docker 환경 설정</title><link>https://kimtks456.github.io/docs/devenv/mac/1_colima/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/devenv/mac/1_colima/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Mac에서 Docker Desktop 없이 컨테이너를 실행하려면 Colima를 쓴다.&lt;br&gt;
Colima는 Apple Silicon/Intel 모두 지원하며, Docker Desktop보다 가볍다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-엔터프라이즈-환경에서-colima를-쓰는-이유"&gt;1. 엔터프라이즈 환경에서 Colima를 쓰는 이유&lt;a class="anchor" href="#1-%ec%97%94%ed%84%b0%ed%94%84%eb%9d%bc%ec%9d%b4%ec%a6%88-%ed%99%98%ea%b2%bd%ec%97%90%ec%84%9c-colima%eb%a5%bc-%ec%93%b0%eb%8a%94-%ec%9d%b4%ec%9c%a0"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="docker-desktop-라이선스-제한"&gt;Docker Desktop 라이선스 제한&lt;a class="anchor" href="#docker-desktop-%eb%9d%bc%ec%9d%b4%ec%84%a0%ec%8a%a4-%ec%a0%9c%ed%95%9c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Docker Desktop은 2022년부터 상업적 사용에 유료 구독을 요구한다.&lt;/p&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;무료 사용 조건 (모두 충족해야 함)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;직원 수 250명 미만 &lt;strong&gt;AND&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;연 매출 $10M(약 130억 원) 미만&lt;/li&gt;
&lt;li&gt;위 조건 중 하나라도 벗어나면 유료 플랜(Team/Business) 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;&lt;h3 id="cli만-쓰면-괜찮지-않나"&gt;&amp;ldquo;CLI만 쓰면 괜찮지 않나?&amp;rdquo;&lt;a class="anchor" href="#cli%eb%a7%8c-%ec%93%b0%eb%a9%b4-%ea%b4%9c%ec%b0%ae%ec%a7%80-%ec%95%8a%eb%82%98"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;아니다.&lt;/strong&gt; 라이선스는 GUI 사용 여부가 아니라 &lt;strong&gt;Docker Desktop 소프트웨어(데몬 + VM) 실행&lt;/strong&gt; 기준이다.&lt;/p&gt;</description></item><item><title>1. Gradle 기초 개념</title><link>https://kimtks456.github.io/docs/gradle/concept/1_gradle_basics/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/gradle/concept/1_gradle_basics/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Gradle은 JVM 기반 프로젝트의 빌드 자동화 도구다.&lt;br&gt;
의존성 다운로드 → 컴파일 → 테스트 → 패키징 → 배포까지 일련의 과정을 자동화한다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-핵심-구성-요소"&gt;1. 핵심 구성 요소&lt;a class="anchor" href="#1-%ed%95%b5%ec%8b%ac-%ea%b5%ac%ec%84%b1-%ec%9a%94%ec%86%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kafka-practice/
├── settings.gradle.kts ← 프로젝트 구조 정의 (무엇을 빌드할지)
├── build.gradle.kts ← 빌드 로직 정의 (어떻게 빌드할지)
├── gradle/
│ ├── libs.versions.toml ← 버전 카탈로그 (의존성 버전 관리)
│ └── wrapper/ ← Gradle Wrapper (팀 전체 동일 버전 보장)
├── gradlew ← Gradle Wrapper 실행 스크립트 (Unix)
└── gradlew.bat ← Gradle Wrapper 실행 스크립트 (Windows)&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2-주요-블록"&gt;2. 주요 블록&lt;a class="anchor" href="#2-%ec%a3%bc%ec%9a%94-%eb%b8%94%eb%a1%9d"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="plugins-"&gt;&lt;code&gt;plugins {}&lt;/code&gt;&lt;a class="anchor" href="#plugins-"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;빌드에 사용할 플러그인을 선언한다.&lt;br&gt;
플러그인은 태스크(&lt;code&gt;compileJava&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;jar&lt;/code&gt; 등)와 설정 옵션을 제공한다.&lt;/p&gt;</description></item><item><title>1. SNAPSHOT vs Release</title><link>https://kimtks456.github.io/docs/nexus/concept/1_snapshot_release/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/nexus/concept/1_snapshot_release/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Gradle/Maven 버전 관리의 핵심 구분.&lt;br&gt;
버전 번호 뒤에 &lt;code&gt;-SNAPSHOT&lt;/code&gt;이 붙는지 여부가 전부다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-개념-비교"&gt;1. 개념 비교&lt;a class="anchor" href="#1-%ea%b0%9c%eb%85%90-%eb%b9%84%ea%b5%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;SNAPSHOT (&lt;code&gt;1.0.0-SNAPSHOT&lt;/code&gt;)&lt;/th&gt;
 &lt;th&gt;Release (&lt;code&gt;1.0.0&lt;/code&gt;)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;재배포&lt;/td&gt;
 &lt;td&gt;가능 — 같은 버전으로 계속 덮어씀&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;불가&lt;/strong&gt; — 동일 버전 재배포 시 Nexus 오류&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;소비자 동작&lt;/td&gt;
 &lt;td&gt;Gradle이 매번 Nexus에서 최신본 재확인&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;--refresh-dependencies&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;불필요 (버전 올려서 재배포)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Nexus 레포&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;maven-snapshots&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;maven-releases&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;용도&lt;/td&gt;
 &lt;td&gt;개발 중 반복 배포&lt;/td&gt;
 &lt;td&gt;버전 확정 후 배포&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;개발 중: version = &amp;#39;1.0.0-SNAPSHOT&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── publish → maven-snapshots → 소비자가 매번 최신본 수신
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;릴리즈: version = &amp;#39;1.0.0&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── publish → maven-releases → 이후 동일 버전 변경 불가 (immutable)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="2-nexus-레포-구조"&gt;2. Nexus 레포 구조&lt;a class="anchor" href="#2-nexus-%eb%a0%88%ed%8f%ac-%ea%b5%ac%ec%a1%b0"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nexus 설치 시 기본 생성되는 3개 레포 (별도 생성 불필요):&lt;/p&gt;</description></item><item><title>2. Groovy DSL vs Kotlin DSL (.kts)</title><link>https://kimtks456.github.io/docs/gradle/concept/2_kotlin_dsl/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/gradle/concept/2_kotlin_dsl/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Gradle 빌드 스크립트는 두 가지 언어로 작성할 수 있다.&lt;br&gt;
&lt;code&gt;build.gradle&lt;/code&gt; (Groovy) vs &lt;code&gt;build.gradle.kts&lt;/code&gt; (Kotlin).&lt;br&gt;
Gradle 공식은 Kotlin DSL을 기본으로 전환했고(Gradle 8.x), 신규 프로젝트는 &lt;code&gt;.kts&lt;/code&gt;가 표준이다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-핵심-차이"&gt;1. 핵심 차이&lt;a class="anchor" href="#1-%ed%95%b5%ec%8b%ac-%ec%b0%a8%ec%9d%b4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;항목&lt;/th&gt;
 &lt;th&gt;Groovy DSL&lt;/th&gt;
 &lt;th&gt;Kotlin DSL (.kts)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;타입&lt;/td&gt;
 &lt;td&gt;동적 타입&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;정적 타입&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;IDE 자동완성&lt;/td&gt;
 &lt;td&gt;부분적 (추론 실패 많음)&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;완전 지원&lt;/strong&gt; (IntelliJ 기준)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;오타 검출 시점&lt;/td&gt;
 &lt;td&gt;런타임 (빌드 실행 시)&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;컴파일 타임&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;리팩터링&lt;/td&gt;
 &lt;td&gt;불안정&lt;/td&gt;
 &lt;td&gt;안정적 (rename, find usages 등)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;빌드 캐시 히트율&lt;/td&gt;
 &lt;td&gt;높음&lt;/td&gt;
 &lt;td&gt;높음 (동일)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;첫 빌드 속도&lt;/td&gt;
 &lt;td&gt;빠름&lt;/td&gt;
 &lt;td&gt;약간 느림 (스크립트 컴파일)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;생태계 표준&lt;/td&gt;
 &lt;td&gt;레거시&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;신규 표준&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="2-문법-비교"&gt;2. 문법 비교&lt;a class="anchor" href="#2-%eb%ac%b8%eb%b2%95-%eb%b9%84%ea%b5%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="플러그인-선언"&gt;플러그인 선언&lt;a class="anchor" href="#%ed%94%8c%eb%9f%ac%ea%b7%b8%ec%9d%b8-%ec%84%a0%ec%96%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Groovy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;plugins &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id &lt;span style="color:#e6db74"&gt;&amp;#39;org.springframework.boot&amp;#39;&lt;/span&gt; version &lt;span style="color:#e6db74"&gt;&amp;#39;3.5.14&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id &lt;span style="color:#e6db74"&gt;&amp;#39;java&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Kotlin DSL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;plugins {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id(&lt;span style="color:#e6db74"&gt;&amp;#34;org.springframework.boot&amp;#34;&lt;/span&gt;) version &lt;span style="color:#e6db74"&gt;&amp;#34;3.5.14&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; java
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="의존성-선언"&gt;의존성 선언&lt;a class="anchor" href="#%ec%9d%98%ec%a1%b4%ec%84%b1-%ec%84%a0%ec%96%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Groovy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; implementation &lt;span style="color:#e6db74"&gt;&amp;#39;org.springframework.boot:spring-boot-starter-web&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; testImplementation &lt;span style="color:#e6db74"&gt;&amp;#39;org.springframework.boot:spring-boot-starter-test&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Kotlin DSL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; implementation(&lt;span style="color:#e6db74"&gt;&amp;#34;org.springframework.boot:spring-boot-starter-web&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; testImplementation(&lt;span style="color:#e6db74"&gt;&amp;#34;org.springframework.boot:spring-boot-starter-test&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="태스크-정의"&gt;태스크 정의&lt;a class="anchor" href="#%ed%83%9c%ec%8a%a4%ed%81%ac-%ec%a0%95%ec%9d%98"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Groovy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;test &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; useJUnitPlatform&lt;span style="color:#f92672"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Kotlin DSL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tasks.withType&amp;lt;Test&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; useJUnitPlatform()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="3-kotlin-dsl의-실질적-이득"&gt;3. Kotlin DSL의 실질적 이득&lt;a class="anchor" href="#3-kotlin-dsl%ec%9d%98-%ec%8b%a4%ec%a7%88%ec%a0%81-%ec%9d%b4%eb%93%9d"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="ide-자동완성"&gt;IDE 자동완성&lt;a class="anchor" href="#ide-%ec%9e%90%eb%8f%99%ec%99%84%ec%84%b1"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; implementation(libs. &lt;span style="color:#75715e"&gt;// ← 여기서 Ctrl+Space → libs에 등록된 라이브러리 전체 목록 표시&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Groovy에서는 문자열이라 IDE가 추론 불가. Kotlin DSL + Version Catalog 조합이 특히 강력하다.&lt;/p&gt;</description></item><item><title>8. Kafka UI — 토픽·메시지·모니터링</title><link>https://kimtks456.github.io/docs/kafka/monitoring/1_kafka_ui/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/monitoring/1_kafka_ui/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Kafka UI(Provectus)는 브라우저에서 Kafka 클러스터를 관리·모니터링할 수 있는 오픈소스 웹 대시보드다.&lt;br&gt;
토픽 CRUD, 메시지 발행/조회, 컨슈머 그룹 추적, 브로커 상태까지 한 화면에서 확인할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-실행"&gt;1. 실행&lt;a class="anchor" href="#1-%ec%8b%a4%ed%96%89"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="docker-compose"&gt;docker-compose&lt;a class="anchor" href="#docker-compose"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kafka-ui&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;provectuslabs/kafka-ui:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;8989:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KAFKA_CLUSTERS_0_NAME&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kafka:9092&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Schema Registry 연동 (선택)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schema-registry:8081&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Kafka Connect 연동 (선택)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: connect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect:8083&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kafka&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;브라우저에서 &lt;code&gt;http://localhost:8989&lt;/code&gt; 접속.&lt;/p&gt;
&lt;h3 id="멀티-클러스터"&gt;멀티 클러스터&lt;a class="anchor" href="#%eb%a9%80%ed%8b%b0-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;환경변수 인덱스를 올려서 여러 클러스터를 동시에 등록할 수 있다.&lt;/p&gt;</description></item><item><title>1. ArgoCD 개념</title><link>https://kimtks456.github.io/docs/devops/argocd/1_concept/</link><pubDate>Mon, 04 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/devops/argocd/1_concept/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;ArgoCD 는 &lt;strong&gt;K8s 클러스터의 실제 상태를 git 에 적힌 manifest 와 일치시키는 GitOps continuous delivery 도구&lt;/strong&gt;.
빌드는 안 한다 — 그건 CI 의 일이다. ArgoCD 의 일은 &lt;em&gt;git 의 선언&lt;/em&gt; 을 &lt;em&gt;클러스터의 실제&lt;/em&gt; 로 옮기는 것 단 하나.
출처: &lt;a href="https://argo-cd.readthedocs.io/en/stable/"&gt;Argo CD docs — What Is Argo CD&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitOps 자체의 정의·배경·역사 (왜 push 모델에서 pull 로 왔는지) 는 &lt;a href="https://kimtks456.github.io/docs/devops/gitops/1_concept/"&gt;DevOps/GitOps/1. GitOps — 정의·배경·역사&lt;/a&gt; 참조. 본 문서는 그 &lt;em&gt;구체 구현체&lt;/em&gt; 인 ArgoCD 만 다룬다.&lt;/p&gt;</description></item><item><title>1. GitOps — 정의·배경·역사</title><link>https://kimtks456.github.io/docs/devops/gitops/1_concept/</link><pubDate>Mon, 04 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/devops/gitops/1_concept/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;GitOps 는 &lt;em&gt;&amp;ldquo;인프라/애플리케이션의 desired state 를 git 에 선언하고, 클러스터 안의 에이전트가 git 을 pull 해서 actual state 를 지속적으로 일치시키는&amp;rdquo;&lt;/em&gt; 운영 방식.
본 문서는 GitOps 의 정의·해결하려는 문제, 그리고 &lt;em&gt;어떻게 여기까지 왔는지&lt;/em&gt; 의 흐름을 정리한다.
출처: &lt;a href="https://www.gitops.tech/"&gt;gitops.tech&lt;/a&gt;, &lt;a href="https://opengitops.dev/"&gt;OpenGitOps Principles&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-정의-gitopstech-인용"&gt;1. 정의 (gitops.tech 인용)&lt;a class="anchor" href="#1-%ec%a0%95%ec%9d%98-gitopstech-%ec%9d%b8%ec%9a%a9"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;GitOps is a way of implementing Continuous Deployment for cloud native applications. &amp;hellip; using Git as a single source of truth for declarative infrastructure and applications, together with tools ensuring the actual state of infrastructure and applications converges towards the desired state declared in Git.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>1. 설계v1 - GitOps</title><link>https://kimtks456.github.io/docs/kafka/practice/design_v1_gitops/</link><pubDate>Mon, 04 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/practice/design_v1_gitops/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 Kafka 공통 플랫폼의 &lt;strong&gt;GitOps 기반 운영 방향성&lt;/strong&gt; — 즉, &lt;em&gt;어떤 운영 모델로 갈 것인가&lt;/em&gt; 를 정한다.
GitOps + Pull 모델 채택의 근거를 1차 출처(gitops.tech, Confluent 블로그/docs) 인용으로 정리하고,
기존 push 기반 CI/CD 와의 차이를 비교한다.&lt;/p&gt;
&lt;p&gt;관련 문서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kimtks456.github.io/docs/kafka/concept/3_topic_design/"&gt;3. Topic 설계&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kimtks456.github.io/docs/kafka/concept/4_message_format/"&gt;4. Message Format 설계&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kimtks456.github.io/docs/kafka/practice/design_v2_connect/"&gt;5. 설계&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-목표"&gt;1. 목표&lt;a class="anchor" href="#1-%eb%aa%a9%ed%91%9c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 작업의 목표는 &lt;em&gt;Kafka 클러스터 자체&lt;/em&gt; 의 구축이 아니라, &lt;strong&gt;Kafka 자원(Topic, ACL, Schema, Connector 등)을 조직 차원에서 일관되게 관리하기 위한 플랫폼/표준을 정립&lt;/strong&gt;하는 것.&lt;/p&gt;</description></item><item><title>1. Connect 개념</title><link>https://kimtks456.github.io/docs/kafka/connect/1_concept/</link><pubDate>Sat, 02 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/connect/1_concept/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Kafka Connect 는 &lt;strong&gt;Kafka 와 외부 시스템 사이의 데이터 이동을 코드 작성 없이 선언형 설정으로 처리&lt;/strong&gt;하는 별도 프레임워크다.
본 문서는 sink 종류(DB, 검색엔진, 객체스토리지 등)와 무관하게 통용되는 Connect 의 &lt;strong&gt;개념·아키텍처·고가용성·자원&lt;/strong&gt;을 1차 출처(Apache / Confluent docs) 인용 중심으로 정리한다.&lt;/p&gt;
&lt;p&gt;관련 문서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모니터링: &lt;a href="https://kimtks456.github.io/docs/kafka/connect/2_monitoring/"&gt;2. 모니터링&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DB sink 시나리오의 구체 의문(Q&amp;amp;A): &lt;a href="https://kimtks456.github.io/docs/kafka/connect/3_db_sink_qna/"&gt;3. DB Sink 시나리오 Q&amp;amp;A&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-connect-개념"&gt;1. Connect 개념&lt;a class="anchor" href="#1-connect-%ea%b0%9c%eb%85%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;출처: &lt;a href="https://docs.confluent.io/platform/current/connect/concepts.html"&gt;Confluent — Kafka Connect Concepts&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;h3 id="11-connect-란-무엇인가"&gt;1.1. Connect 란 무엇인가&lt;a class="anchor" href="#11-connect-%eb%9e%80-%eb%ac%b4%ec%97%87%ec%9d%b8%ea%b0%80"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;핵심 인용:&lt;/p&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;You can deploy Kafka Connect as a &amp;hellip; distributed, scalable, fault-tolerant service supporting an entire organization.&amp;rdquo;&lt;/em&gt;
&lt;em&gt;&amp;ldquo;a separate framework layer above the Kafka cluster itself.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>1. 카프카 개요</title><link>https://kimtks456.github.io/docs/kafka/concept/1_outline/</link><pubDate>Tue, 21 Apr 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/concept/1_outline/</guid><description>&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=0Ssx7jJJADI&amp;amp;t=33s"&gt;kafka 조금 아는 척하기 by 최범준님&lt;/a&gt; 시리즈를 통해 대략적인 개념을 정리한다.&lt;/p&gt;
&lt;h2 id="1-카프카-핵심"&gt;1. 카프카 핵심&lt;a class="anchor" href="#1-%ec%b9%b4%ed%94%84%ec%b9%b4-%ed%95%b5%ec%8b%ac"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="11-분산환경에서-메시지-처리-철학message-delivery-semantics"&gt;1.1. 분산환경에서 메시지 처리 철학(Message Delivery Semantics)&lt;a class="anchor" href="#11-%eb%b6%84%ec%82%b0%ed%99%98%ea%b2%bd%ec%97%90%ec%84%9c-%eb%a9%94%ec%8b%9c%ec%a7%80-%ec%b2%98%eb%a6%ac-%ec%b2%a0%ed%95%99message-delivery-semantics"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;분산 네트워크 환경(언제든 끊길 수 있음)에서 시스템 간 메시지를 어떻게 처리할 것인지에 대해 3가지 철학이 Kafka 내부 아키텍처에서 구현함&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;strong&gt;의미론 (Semantics)&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;데이터 유실&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;데이터 중복&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;카프카 기본 동작&lt;/strong&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;At-most-once&lt;/strong&gt; (최대 한 번)&lt;/td&gt;
 &lt;td&gt;메시지가 손실될지언정, 절대 중복해서 전달하지 않음&lt;/td&gt;
 &lt;td&gt;발생함&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;acks=0&lt;/code&gt;, 혹은 컨슈머가 &amp;lsquo;읽자마자 커밋&amp;rsquo; 할 때&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;At-least-once&lt;/strong&gt; (최소 한 번)&lt;/td&gt;
 &lt;td&gt;메시지가 중복될지언정, 절대 유실시키지 않음&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;td&gt;발생함&lt;/td&gt;
 &lt;td&gt;카프카의 기본(Default) 철학&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Exactly-once&lt;/strong&gt; (정확히 한 번)&lt;/td&gt;
 &lt;td&gt;유실도 없고, 중복도 없이 정확히 한 번만 처리됨&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Kafka는 왜 At-least-once 인가?&lt;/p&gt;</description></item><item><title>2. 실전 예제 설계</title><link>https://kimtks456.github.io/docs/springbatch/practice/2_sample-jobs-design/</link><pubDate>Wed, 13 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/springbatch/practice/2_sample-jobs-design/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;3가지 실전 배치 예제를 설계한다.&lt;br&gt;
각 예제의 Job 흐름, 도메인, Skip/Retry 전략, 테스트 전략을 정리한다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-예제-개요"&gt;1. 예제 개요&lt;a class="anchor" href="#1-%ec%98%88%ec%a0%9c-%ea%b0%9c%ec%9a%94"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;Job명&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;1&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;notificationResendJob&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;REST API (trigger)&lt;/td&gt;
 &lt;td&gt;Job Parameter로 일자를 받아 해당 일자 이후 FAILED 알림 재발송&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;scheduledNotificationResendJob&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Spring Scheduler (1분)&lt;/td&gt;
 &lt;td&gt;전체 FAILED 알림 재발송&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;movieLoadJob&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Spring Scheduler (1분)&lt;/td&gt;
 &lt;td&gt;외부 API(Feign)에서 영화 데이터 조회 → IF 테이블 적재&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="2-배치에서의-응답-방식"&gt;2. 배치에서의 응답 방식&lt;a class="anchor" href="#2-%eb%b0%b0%ec%b9%98%ec%97%90%ec%84%9c%ec%9d%98-%ec%9d%91%eb%8b%b5-%eb%b0%a9%ec%8b%9d"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spring Batch Job은 &lt;strong&gt;비동기 처리&lt;/strong&gt;가 기본이다.&lt;br&gt;
HTTP 요청처럼 즉시 결과를 반환하지 않으며, 처리 결과는 로그와 &lt;code&gt;BATCH_*&lt;/code&gt; 메타 테이블에 남는다.&lt;/p&gt;</description></item><item><title>Kafka 통합 테스트 — 컨테이너 종료 후 JVM hang</title><link>https://kimtks456.github.io/docs/kafka/issues/2_test_jvm_hang/</link><pubDate>Mon, 11 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/issues/2_test_jvm_hang/</guid><description>&lt;h2 id="1-문제"&gt;1. 문제&lt;a class="anchor" href="#1-%eb%ac%b8%ec%a0%9c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Testcontainers 기반 Kafka 통합 테스트에서 테스트는 모두 통과하지만 &lt;strong&gt;Gradle 테스트 프로세스가 종료되지 않고 멈춘다.&lt;/strong&gt; 테스트 클래스가 끝난 후에도 다음 경고가 반복 출력된다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;WARN [log-service] [vice-producer-1] o.a.k.c.NetworkClient :
 [Producer clientId=log-service-producer-1]
 Connection to node 1 (localhost/127.0.0.1:32989) could not be established.
 Node may not be available.

INFO [log-service] [ntainer#0-0-C-1] o.a.k.c.NetworkClient :
 [Consumer clientId=consumer-log-service-2, groupId=log-service] Node 1 disconnected.&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2-원인"&gt;2. 원인&lt;a class="anchor" href="#2-%ec%9b%90%ec%9d%b8"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Spring 컨텍스트 캐싱 + Testcontainers 컨테이너 종료 순서 불일치 → Kafka 클라이언트 non-daemon 스레드가 JVM 종료를 막는다.&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>2. build.gradle.kts vs settings.gradle.kts</title><link>https://kimtks456.github.io/docs/gradle/concept/3_build_settings/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/gradle/concept/3_build_settings/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;두 파일은 역할이 완전히 다르다.&lt;br&gt;
&lt;code&gt;settings.gradle.kts&lt;/code&gt;는 &lt;strong&gt;프로젝트 구조&lt;/strong&gt;를 정의하고,&lt;br&gt;
&lt;code&gt;build.gradle.kts&lt;/code&gt;는 &lt;strong&gt;빌드 로직&lt;/strong&gt;을 정의한다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-평가-순서"&gt;1. 평가 순서&lt;a class="anchor" href="#1-%ed%8f%89%ea%b0%80-%ec%88%9c%ec%84%9c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Gradle은 빌드 시작 시 다음 순서로 파일을 평가한다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[1] settings.gradle.kts ← 가장 먼저 실행
 └── 어떤 모듈이 있는지, 플러그인 저장소는 어디인지 결정
 
[2] build.gradle.kts (root)
 └── 공통 설정 (전체 서브프로젝트에 적용할 것들)

[3] build.gradle.kts (각 서브프로젝트)
 └── 모듈별 의존성, 태스크 등&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;settings.gradle.kts&lt;/code&gt;가 평가되기 전까지 Gradle은 빌드 대상 모듈이 무엇인지조차 모른다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2-settingsgradlekts"&gt;2. settings.gradle.kts&lt;a class="anchor" href="#2-settingsgradlekts"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="역할"&gt;역할&lt;a class="anchor" href="#%ec%97%ad%ed%95%a0"&gt;#&lt;/a&gt;&lt;/h3&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;&lt;code&gt;rootProject.name&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;프로젝트 이름 (Nexus 배포 시 groupId 등에 영향)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;include()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;서브모듈 등록&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pluginManagement {}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;플러그인 저장소 및 버전 선언&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;dependencyResolutionManagement {}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;의존성 저장소 전역 통제, Version Catalog 선언&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="예시-멀티모듈-프로젝트"&gt;예시 (멀티모듈 프로젝트)&lt;a class="anchor" href="#%ec%98%88%ec%8b%9c-%eb%a9%80%ed%8b%b0%eb%aa%a8%eb%93%88-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rootProject.name = &lt;span style="color:#e6db74"&gt;&amp;#34;kafka-practice&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;include(&lt;span style="color:#e6db74"&gt;&amp;#34;kafka-common-lib&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;order-service&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;include()&lt;/code&gt;에 없는 디렉토리는 Gradle이 서브모듈로 인식하지 않는다.&lt;/p&gt;</description></item><item><title>2. 설계v2 - Connect 적용</title><link>https://kimtks456.github.io/docs/kafka/practice/design_v2_connect/</link><pubDate>Sat, 09 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/practice/design_v2_connect/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;SI 환경 공통 플랫폼. 운영 환경은 단순 배포 (GitOps/K8s 생략).
공통팀이 브로커 설정과 공통 라이브러리를 통제하고, 도메인 개발자는 가져다 쓰는 구조.
토픽 네이밍은 &lt;a href="https://kimtks456.github.io/docs/kafka/concept/3_topic_design/"&gt;3. Topic 설계&lt;/a&gt;, 메시지 포맷은 &lt;a href="https://kimtks456.github.io/docs/kafka/concept/4_message_format/"&gt;4. Message Format 설계&lt;/a&gt; 참조.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-리포-구성-2개"&gt;1. 리포 구성 (2개)&lt;a class="anchor" href="#1-%eb%a6%ac%ed%8f%ac-%ea%b5%ac%ec%84%b1-2%ea%b0%9c"&gt;#&lt;/a&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;&lt;strong&gt;&lt;code&gt;kafka-platform&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;공통팀&lt;/td&gt;
 &lt;td&gt;브로커 설정, 토픽 선언, Connect 설정, 로컬 테스트 환경&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;&lt;code&gt;kafka-common-lib&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;공통팀&lt;/td&gt;
 &lt;td&gt;Producer/Consumer client 라이브러리, 이벤트 POJO, 멱등성 처리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;도메인 서비스는 &lt;code&gt;kafka-common-lib&lt;/code&gt; 를 Maven/Gradle 의존성으로 가져다 쓴다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="2-kafka-설정--hard-vs-soft-구분"&gt;2. Kafka 설정 — Hard vs Soft 구분&lt;a class="anchor" href="#2-kafka-%ec%84%a4%ec%a0%95--hard-vs-soft-%ea%b5%ac%eb%b6%84"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="hard-설정-변경-불가--공통팀만-통제"&gt;Hard 설정 (변경 불가 — 공통팀만 통제)&lt;a class="anchor" href="#hard-%ec%84%a4%ec%a0%95-%eb%b3%80%ea%b2%bd-%eb%b6%88%ea%b0%80--%ea%b3%b5%ed%86%b5%ed%8c%80%eb%a7%8c-%ed%86%b5%ec%a0%9c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;안정성·데이터 정합성에 직결. &lt;code&gt;kafka-common-lib&lt;/code&gt; 내 Config 클래스에서 강제 적용 — 도메인 서비스에서 오버라이드해도 lib 설정이 우선.&lt;/p&gt;</description></item><item><title>2. 모니터링</title><link>https://kimtks456.github.io/docs/kafka/connect/2_monitoring/</link><pubDate>Sat, 02 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/connect/2_monitoring/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 Kafka Connect 클러스터를 운영할 때 보아야 할 메트릭·도구·표준 스택을 정리한다.
sink 종류와 무관하게 통용되는 부분만 다루며, 외부 시스템(DB, ES 등) 내부 모니터링은 &lt;em&gt;그 시스템 측 가이드&lt;/em&gt; 로 미룬다.
출처: &lt;a href="https://docs.confluent.io/platform/current/connect/monitoring.html"&gt;Confluent — Kafka Connect Monitoring (JMX)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;관련 문서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;개념·아키텍처·HA·자원: &lt;a href="https://kimtks456.github.io/docs/kafka/connect/1_concept/"&gt;1. Connect 개념&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DB sink 시나리오의 모니터링 보완(예: DB connection pool): &lt;a href="https://kimtks456.github.io/docs/kafka/connect/3_db_sink_qna/"&gt;3. DB Sink 시나리오 Q&amp;amp;A&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-모니터링이-답해야-할-질문"&gt;1. 모니터링이 답해야 할 질문&lt;a class="anchor" href="#1-%eb%aa%a8%eb%8b%88%ed%84%b0%eb%a7%81%ec%9d%b4-%eb%8b%b5%ed%95%b4%ec%95%bc-%ed%95%a0-%ec%a7%88%eb%ac%b8"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Connect 운영자가 매일 답하고 싶은 것은 결국 다음 4 가지다:&lt;/p&gt;</description></item><item><title>1. 버전 정리 — 4.0과 그 이전의 차이</title><link>https://kimtks456.github.io/docs/kafka/concept/2_version/</link><pubDate>Wed, 29 Apr 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/concept/2_version/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 Kafka 버전 흐름을 짚고, &lt;strong&gt;4.0&lt;/strong&gt; 과 &lt;strong&gt;3.x&lt;/strong&gt; 의 주요 차이를 정리한다.
단정적 진술은 1차 출처(Apache Kafka 공식 release announcement, KIP 문서) 인용으로 갈음하고,
출처가 약한 내용은 &lt;strong&gt;(미확인)&lt;/strong&gt; 표기.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-마일스톤-한눈에"&gt;1. 마일스톤 한눈에&lt;a class="anchor" href="#1-%eb%a7%88%ec%9d%bc%ec%8a%a4%ed%86%a4-%ed%95%9c%eb%88%88%ec%97%90"&gt;#&lt;/a&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;th&gt;출처&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2.8.0&lt;/td&gt;
 &lt;td&gt;2021-04&lt;/td&gt;
 &lt;td&gt;KRaft &lt;strong&gt;early access&lt;/strong&gt; (KIP-500)&lt;/td&gt;
 &lt;td&gt;(미확인 — 본 문서 작성 시 직접 검증 안 함)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.0.0&lt;/td&gt;
 &lt;td&gt;2021-09&lt;/td&gt;
 &lt;td&gt;KRaft 진행 / 메이저 정리&lt;/td&gt;
 &lt;td&gt;(미확인)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.3.0&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;2022-10-03&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;KRaft production-ready&lt;/strong&gt; (KIP-833, 신규 클러스터 한정)&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-833:&amp;#43;Mark&amp;#43;KRaft&amp;#43;as&amp;#43;Production&amp;#43;Ready"&gt;KIP-833&lt;/a&gt;, &lt;a href="https://www.confluent.io/blog/apache-kafka-3-3-0-new-features-and-updates/"&gt;Confluent blog&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.4 ~ 3.8&lt;/td&gt;
 &lt;td&gt;2023~2024&lt;/td&gt;
 &lt;td&gt;ZK→KRaft 마이그레이션 기능 추가, KIP-848 early access (3.7)&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://www.confluent.io/blog/introducing-apache-kafka-3-9/"&gt;Confluent — Kafka 3.9&lt;/a&gt; (간접)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;3.9.0&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;2024-11-06&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;ZK 지원 마지막 메이저&lt;/strong&gt;, Tiered Storage GA&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://kafka.apache.org/blog/2024/11/06/apache-kafka-3.9.0-release-announcement/"&gt;Apache 공식 release announcement&lt;/a&gt;, &lt;a href="https://www.confluent.io/blog/introducing-apache-kafka-3-9/"&gt;Confluent blog&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;4.0.0&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;2025-03-18&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;ZK 완전 제거&lt;/strong&gt;, Java 17 (broker), KIP-848 GA, KIP-932 EA&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://kafka.apache.org/blog/2025/03/18/apache-kafka-4.0.0-release-announcement/"&gt;Apache 4.0 release announcement&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;3.9 의 트위터 공식 계정 발표:
&lt;em&gt;&amp;ldquo;Hello Apache Kafka 3.9.0! - &lt;strong&gt;last release using ZooKeeper&lt;/strong&gt; - tiered storage is production-ready&amp;rdquo;&lt;/em&gt; — &lt;a href="https://x.com/apachekafka/status/1854677903176614030"&gt;Apache Kafka X(Twitter)&lt;/a&gt;&lt;/p&gt;</description></item><item><title>3. Gradle Version Catalog</title><link>https://kimtks456.github.io/docs/gradle/concept/4_version_catalog/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/gradle/concept/4_version_catalog/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;code&gt;gradle/libs.versions.toml&lt;/code&gt;에 버전을 선언하고 모든 빌드 스크립트에서 타입세이프하게 참조하는 방식.&lt;br&gt;
Gradle 7.4+에서 안정화, Gradle 8.x에서 사실상 표준이 됐다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-도입-전--기존-버전-관리-방식들"&gt;1. 도입 전 — 기존 버전 관리 방식들&lt;a class="anchor" href="#1-%eb%8f%84%ec%9e%85-%ec%a0%84--%ea%b8%b0%ec%a1%b4-%eb%b2%84%ec%a0%84-%ea%b4%80%eb%a6%ac-%eb%b0%a9%ec%8b%9d%eb%93%a4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="방법-a-문자열-하드코딩"&gt;방법 A: 문자열 하드코딩&lt;a class="anchor" href="#%eb%b0%a9%eb%b2%95-a-%eb%ac%b8%ec%9e%90%ec%97%b4-%ed%95%98%eb%93%9c%ec%bd%94%eb%94%a9"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; implementation(&lt;span style="color:#e6db74"&gt;&amp;#34;org.springframework.kafka:spring-kafka:3.2.1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; implementation(&lt;span style="color:#e6db74"&gt;&amp;#34;org.springframework.boot:spring-boot-starter-data-redis:3.5.14&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;모듈이 늘어날수록 같은 버전 문자열이 여러 파일에 흩어진다. 업그레이드 시 누락 위험.&lt;/p&gt;
&lt;h3 id="방법-b-val-변수로-추출"&gt;방법 B: &lt;code&gt;val&lt;/code&gt; 변수로 추출&lt;a class="anchor" href="#%eb%b0%a9%eb%b2%95-b-val-%eb%b3%80%ec%88%98%eb%a1%9c-%ec%b6%94%ec%b6%9c"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; springBootVersion = &lt;span style="color:#e6db74"&gt;&amp;#34;3.5.14&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; implementation(&lt;span style="color:#e6db74"&gt;&amp;#34;org.springframework.boot:spring-boot-starter-web:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;$springBootVersion&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;단일 파일 내에서는 관리되지만, &lt;strong&gt;&lt;code&gt;plugins {}&lt;/code&gt; 블록 안에서 변수를 사용할 수 없다&lt;/strong&gt;는 Gradle 제약이 있다.&lt;/p&gt;</description></item><item><title>3. 설계v3 - Connect 제외</title><link>https://kimtks456.github.io/docs/kafka/practice/design_v3_no_connect/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/practice/design_v3_no_connect/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Aiven JDBC Connector의 운영 리스크(&lt;a href="https://kimtks456.github.io/docs/kafka/connect/4_license/"&gt;Connect 라이선스 이슈 → 향후 리스크&lt;/a&gt;)가 현실화될 경우,&lt;br&gt;
Kafka Connect를 걷어내고 &lt;code&gt;log-service&lt;/code&gt; Spring Boot 모듈로 대체하는 설계를 정리한다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-모듈-위치-멀티모듈-구성"&gt;1. 모듈 위치 (멀티모듈 구성)&lt;a class="anchor" href="#1-%eb%aa%a8%eb%93%88-%ec%9c%84%ec%b9%98-%eb%a9%80%ed%8b%b0%eb%aa%a8%eb%93%88-%ea%b5%ac%ec%84%b1"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kafka-practice/
├── kafka-common-lib/ # 공통 라이브러리 (변경 없음)
├── order-service/ # 도메인 서비스 (변경 없음)
└── log-service/ # 신규 — Kafka → DB 적재 전담 서비스&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;settings.gradle.kts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;include(&lt;span style="color:#e6db74"&gt;&amp;#34;kafka-common-lib&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;order-service&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;log-service&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="2-패키지-트리"&gt;2. 패키지 트리&lt;a class="anchor" href="#2-%ed%8c%a8%ed%82%a4%ec%a7%80-%ed%8a%b8%eb%a6%ac"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;log-service/
└── src/
 ├── main/
 │ ├── java/com/example/log/
 │ │ ├── LogServiceApplication.java # @SpringBootApplication
 │ │ ├── domain/
 │ │ │ └── SystemLog.java # JPA 엔티티
 │ │ ├── repository/
 │ │ │ └── SystemLogRepository.java # Spring Data JPA
 │ │ └── kafka/
 │ │ └── SystemLogConsumer.java # @KafkaListener + @IdempotentConsumer
 │ └── resources/
 │ ├── application.yaml # 공통 설정
 │ └── application-dev.yaml # 로컬 개발 설정
 └── test/
 └── java/com/example/log/
 ├── LogServiceApplicationTests.java
 └── kafka/
 └── SystemLogFlowTest.java # Testcontainers 통합 테스트&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="3-핵심-클래스"&gt;3. 핵심 클래스&lt;a class="anchor" href="#3-%ed%95%b5%ec%8b%ac-%ed%81%b4%eb%9e%98%ec%8a%a4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="systemlogconsumer"&gt;SystemLogConsumer&lt;a class="anchor" href="#systemlogconsumer"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SystemLogConsumer&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@KafkaListener&lt;/span&gt;(topics &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;prd.log.system.v1&amp;#34;&lt;/span&gt;, groupId &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;log-service&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@IdempotentConsumer&lt;/span&gt;(keyType &lt;span style="color:#f92672"&gt;=&lt;/span&gt; IdempotencyKey.&lt;span style="color:#a6e22e"&gt;EVENT_ID&lt;/span&gt;, ttlSeconds &lt;span style="color:#f92672"&gt;=&lt;/span&gt; 86400)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle&lt;/span&gt;(SystemLogEvent event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; repository.&lt;span style="color:#a6e22e"&gt;save&lt;/span&gt;(SystemLog.&lt;span style="color:#a6e22e"&gt;from&lt;/span&gt;(event));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&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;code&gt;@IdempotentConsumer&lt;/code&gt; — kafka-common-lib의 Redis 기반 멱등성 AOP 그대로 재사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@KafkaListener&lt;/code&gt; — Kafka Connect 없이 Spring Kafka로 직접 소비&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="systemlog-jpa-엔티티"&gt;SystemLog (JPA 엔티티)&lt;a class="anchor" href="#systemlog-jpa-%ec%97%94%ed%8b%b0%ed%8b%b0"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Entity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Table&lt;/span&gt;(name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;system_log&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SystemLog&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Id&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;@GeneratedValue&lt;/span&gt;(strategy &lt;span style="color:#f92672"&gt;=&lt;/span&gt; GenerationType.&lt;span style="color:#a6e22e"&gt;IDENTITY&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Long id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Column&lt;/span&gt;(name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;event_id&amp;#34;&lt;/span&gt;, nullable &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, unique &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String eventId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Column&lt;/span&gt;(name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;service_id&amp;#34;&lt;/span&gt;, nullable &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String serviceId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String level; &lt;span style="color:#75715e"&gt;// INFO / WARN / ERROR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Column&lt;/span&gt;(columnDefinition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TEXT&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String message;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Column&lt;/span&gt;(name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;context_json&amp;#34;&lt;/span&gt;, columnDefinition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TEXT&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String contextJson; &lt;span style="color:#75715e"&gt;// Map&amp;lt;String,String&amp;gt; → &amp;#34;key=val,...&amp;#34; 형태&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Column&lt;/span&gt;(name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;occurred_at&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Instant occurredAt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; SystemLog &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt;(SystemLogEvent event) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="4-kafka--db-흐름"&gt;4. Kafka → DB 흐름&lt;a class="anchor" href="#4-kafka--db-%ed%9d%90%eb%a6%84"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;도메인 서비스 (order-service 등)
 │
 │ kafkaTemplate.send(&amp;#34;prd.log.system.v1&amp;#34;, eventId, SystemLogEvent)
 ▼
Kafka (prd.log.system.v1 토픽)
 │
 ▼
log-service :: SystemLogConsumer.handle(SystemLogEvent)
 │
 ├── IdempotencyAspect → Redis SET NX &amp;#34;idempotency:{eventId}&amp;#34;
 │ 중복이면 skip, 신규면 proceed
 │
 ▼
SystemLogRepository.save(SystemLog.from(event))
 │
 ▼
PostgreSQL :: system_log 테이블&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="5-kafka-connect와의-비교"&gt;5. Kafka Connect와의 비교&lt;a class="anchor" href="#5-kafka-connect%ec%99%80%ec%9d%98-%eb%b9%84%ea%b5%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;항목&lt;/th&gt;
 &lt;th&gt;Kafka Connect (JDBC Sink)&lt;/th&gt;
 &lt;th&gt;log-service&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;커넥터 의존&lt;/td&gt;
 &lt;td&gt;Aiven JDBC Connector 필요&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;커스텀 이미지&lt;/td&gt;
 &lt;td&gt;buildx로 직접 빌드&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;불필요&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;멱등성&lt;/td&gt;
 &lt;td&gt;Connect 내장 (offset 기반)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;@IdempotentConsumer&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;변환 로직&lt;/td&gt;
 &lt;td&gt;SMT (제한적)&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Java 코드로 자유롭게&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;테스트&lt;/td&gt;
 &lt;td&gt;Connect Worker 환경 필요&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Testcontainers로 단독 테스트&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;배포&lt;/td&gt;
 &lt;td&gt;Connect Worker 컨테이너&lt;/td&gt;
 &lt;td&gt;Spring Boot JAR (기존 패턴)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="6-테스트-전략"&gt;6. 테스트 전략&lt;a class="anchor" href="#6-%ed%85%8c%ec%8a%a4%ed%8a%b8-%ec%a0%84%eb%9e%b5"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="systemlogflowtest-통합-테스트"&gt;SystemLogFlowTest (통합 테스트)&lt;a class="anchor" href="#systemlogflowtest-%ed%86%b5%ed%95%a9-%ed%85%8c%ec%8a%a4%ed%8a%b8"&gt;#&lt;/a&gt;&lt;/h3&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;&lt;code&gt;로그이벤트_발행후_DB_저장&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;발행 → DB 저장 E2E, 저장된 필드값 일치 확인&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;중복_eventId_한번만_저장&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;동일 eventId 2회 발행 → DB row 1개만 생성&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;다른_serviceId_이벤트_각각_저장&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;서로 다른 이벤트 → 각각 저장 (count=2)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;다양한_레벨_이벤트_모두_저장&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;INFO/WARN/ERROR 3건 → 모두 저장&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Testcontainers 구성:&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>3. Topic 설계</title><link>https://kimtks456.github.io/docs/kafka/concept/3_topic_design/</link><pubDate>Mon, 04 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/concept/3_topic_design/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 토픽 &lt;strong&gt;네이밍 컨벤션&lt;/strong&gt; 을 정한다.
출처: &lt;a href="https://www.confluent.io/learn/kafka-topic-naming-convention/"&gt;Confluent — &lt;em&gt;Kafka Topic Naming Convention: Best Practices, Patterns, and Guidelines&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;관련 문서: &lt;a href="https://kimtks456.github.io/docs/kafka/practice/design_v2_connect/"&gt;5. 설계&lt;/a&gt; — 토픽 YAML 의 위치 (&lt;code&gt;kafka-platform/topics/&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-confluent-가-권장하는-4가지-구성-요소"&gt;1. Confluent 가 권장하는 4가지 구성 요소&lt;a class="anchor" href="#1-confluent-%ea%b0%80-%ea%b6%8c%ec%9e%a5%ed%95%98%eb%8a%94-4%ea%b0%80%ec%a7%80-%ea%b5%ac%ec%84%b1-%ec%9a%94%ec%86%8c"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Data Source / Domain&lt;/strong&gt; — 발생 시스템 (예: &lt;code&gt;sales&lt;/code&gt;, &lt;code&gt;hr&lt;/code&gt;, &lt;code&gt;product&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Type / Action&lt;/strong&gt; — 이벤트 종류 (예: &lt;code&gt;order&lt;/code&gt;, &lt;code&gt;click&lt;/code&gt;, &lt;code&gt;transaction&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment / Region&lt;/strong&gt; — 배포 컨텍스트 (&lt;code&gt;prod&lt;/code&gt;/&lt;code&gt;dev&lt;/code&gt; 또는 &lt;code&gt;us-east&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version&lt;/strong&gt; — 스키마 버전 (&lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="2-일반적-패턴-confluent-제시"&gt;2. 일반적 패턴 (Confluent 제시)&lt;a class="anchor" href="#2-%ec%9d%bc%eb%b0%98%ec%a0%81-%ed%8c%a8%ed%84%b4-confluent-%ec%a0%9c%ec%8b%9c"&gt;#&lt;/a&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Hierarchical&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;domain.data_type.region.version&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Action-Based&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;user.signup.success&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Environment-Specific&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;prod.order.events&lt;/code&gt;, &lt;code&gt;dev.order.events&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Multi-Region&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;global.sales.eu-west&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="3-본-조직-채택안-제안"&gt;3. 본 조직 채택안 (제안)&lt;a class="anchor" href="#3-%eb%b3%b8-%ec%a1%b0%ec%a7%81-%ec%b1%84%ed%83%9d%ec%95%88-%ec%a0%9c%ec%95%88"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;env&amp;gt;.&amp;lt;domain&amp;gt;.&amp;lt;event&amp;gt;.&amp;lt;version&amp;gt;&lt;/code&gt;&lt;/strong&gt; — Hierarchical + Environment-Specific 결합.&lt;/p&gt;</description></item><item><title>3. DB Sink 시나리오 Q&amp;A</title><link>https://kimtks456.github.io/docs/kafka/connect/3_db_sink_qna/</link><pubDate>Sat, 02 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/connect/3_db_sink_qna/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 sink = &lt;strong&gt;관계형 DB&lt;/strong&gt; 라는 구체 시나리오에서 Connect 도입 시 떠올린 의문과 그 답을 정리한다.
Connect 의 일반 개념은 &lt;a href="https://kimtks456.github.io/docs/kafka/connect/1_concept/"&gt;1. Connect 개념&lt;/a&gt;, 모니터링 일반론은 &lt;a href="https://kimtks456.github.io/docs/kafka/connect/2_monitoring/"&gt;2. 모니터링&lt;/a&gt;.
본 문서는 &lt;em&gt;DB 적재&lt;/em&gt; 라는 케이스에 한정해 — connection 산정, schema 변환, batch insert, DB 측 모니터링, &amp;ldquo;그냥 직접 짜면 안 되나?&amp;rdquo; 같은 의문을 다룬다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-시나리오"&gt;1. 시나리오&lt;a class="anchor" href="#1-%ec%8b%9c%eb%82%98%eb%a6%ac%ec%98%a4"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;본 조직의 적재 요구:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Service A] ┌──→ [Postgres]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Service B] ──→ [Kafka Broker] ←── [Connect Worker Cluster] ──┤
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ ... ] (JDBC Sink Connector) └──→ (필요 시 ES/S3 등 추가)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;요구사항 (이전 프로젝트에서 직접 짠 consumer 가 했던 일):&lt;/p&gt;</description></item><item><title>@IdempotentConsumer — 멱등성 패턴 설계 분석</title><link>https://kimtks456.github.io/docs/kafka/practice/4_idempotent_consumer/</link><pubDate>Mon, 11 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/practice/4_idempotent_consumer/</guid><description>&lt;p&gt;작성된 코드는 엔터프라이즈 환경에서 Kafka의 &lt;strong&gt;At-least-once 전송 보장&lt;/strong&gt;으로 인해 필연적으로 발생하는 &lt;strong&gt;메시지 중복 수신 문제를 방어하기 위한 멱등성(Idempotency) 패턴&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;비즈니스 로직(Consumer)과 인프라 로직(Redis 체크)을 AOP로 완벽하게 분리한 구조다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="1-핵심-개념--redis-set-nx"&gt;1. 핵심 개념 — Redis &lt;code&gt;SET NX&lt;/code&gt;&lt;a class="anchor" href="#1-%ed%95%b5%ec%8b%ac-%ea%b0%9c%eb%85%90--redis-set-nx"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SET NX&lt;/code&gt;는 &amp;ldquo;SET if Not eXists(존재하지 않을 때만 저장)&amp;ldquo;의 Redis 명령어 옵션이다. Spring Data Redis에서는 &lt;code&gt;setIfAbsent()&lt;/code&gt;로 제공한다.&lt;/p&gt;
&lt;p&gt;여러 Consumer Pod이 떠 있는 분산 환경에서, 네트워크 지연이나 리밸런싱 때문에 &lt;strong&gt;동일한 이벤트(예: 주문 완료 ID: 123)가 컨슈머 A와 B에 동시에 들어올 수 있다.&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>4. Connect 라이선스 이슈</title><link>https://kimtks456.github.io/docs/kafka/connect/4_license/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/connect/4_license/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Kafka Connect를 엔터프라이즈·SaaS 환경에서 도입할 때 반드시 검토해야 할 라이선스 문제.&lt;br&gt;
특히 &lt;strong&gt;Confluent Community License&lt;/strong&gt; 가 어디까지 허용되고 어디서 막히는지 분석한다.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-라이선스-계층-구조"&gt;1. 라이선스 계층 구조&lt;a class="anchor" href="#1-%eb%9d%bc%ec%9d%b4%ec%84%a0%ec%8a%a4-%ea%b3%84%ec%b8%b5-%ea%b5%ac%ec%a1%b0"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kafka Connect 스택은 컴포넌트마다 라이선스가 다르다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│ Connect Worker (connect-distributed.sh) │
│ → Apache Kafka 내장 → Apache 2.0 ✓ │
├─────────────────────────────────────────────────────┤
│ Connector Plugin (JDBC Sink 등) │
│ → 벤더마다 다름 ← 여기서 문제 발생 │
├─────────────────────────────────────────────────────┤
│ JDBC Driver (PostgreSQL 등) │
│ → PostgreSQL JDBC: BSD-2-Clause ✓ │
└─────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Connect worker 자체는 Apache 2.0이다.&lt;/strong&gt; 문제는 그 위에 올리는 &lt;strong&gt;커넥터 플러그인&lt;/strong&gt;에 있다.&lt;/p&gt;</description></item><item><title>4. Message Format 설계</title><link>https://kimtks456.github.io/docs/kafka/concept/4_message_format/</link><pubDate>Mon, 04 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/concept/4_message_format/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;본 문서는 메시지의 &lt;strong&gt;직렬화 포맷·Schema Registry·호환성 정책·버전 관리&lt;/strong&gt; 를 정한다.
본 시점에는 결정 단계 — 후보를 정리하고 비교하며, 본 조직 결정이 내려지면 §5 에 채워 넣는다.&lt;/p&gt;
&lt;p&gt;관련 문서:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;토픽 이름의 &lt;code&gt;.v1&lt;/code&gt; &lt;code&gt;.v2&lt;/code&gt; 부분: &lt;a href="https://kimtks456.github.io/docs/kafka/concept/3_topic_design/"&gt;3. Topic 설계&lt;/a&gt; §1, §3&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kafka-common-lib&lt;/code&gt; 의 &lt;code&gt;serde/&lt;/code&gt; 위치: &lt;a href="https://kimtks456.github.io/docs/kafka/practice/design_v2_connect/"&gt;5. 설계&lt;/a&gt; §4&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-직렬화-포맷-비교"&gt;1. 직렬화 포맷 비교&lt;a class="anchor" href="#1-%ec%a7%81%eb%a0%ac%ed%99%94-%ed%8f%ac%eb%a7%b7-%eb%b9%84%ea%b5%90"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;출처: &lt;a href="https://docs.confluent.io/platform/current/schema-registry/index.html"&gt;Confluent — Schema Registry Concepts (Schema Formats)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&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;th&gt;단점&lt;/th&gt;
 &lt;th&gt;비고&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Avro&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;✅ (&lt;code&gt;.avsc&lt;/code&gt;)&lt;/td&gt;
 &lt;td&gt;Schema Registry 통합 표준 · binary 효율 · evolution rule 성숙&lt;/td&gt;
 &lt;td&gt;사람이 읽기 어려움 (binary)&lt;/td&gt;
 &lt;td&gt;Confluent 에코시스템의 default. 가장 흔한 운영 표준&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Protobuf&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;✅ (&lt;code&gt;.proto&lt;/code&gt;)&lt;/td&gt;
 &lt;td&gt;gRPC 와 호환 · 다국어 지원 우수&lt;/td&gt;
 &lt;td&gt;Avro 대비 evolution rule 가 약간 더 빡셈&lt;/td&gt;
 &lt;td&gt;이미 gRPC 쓰는 조직에 자연스러움&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;JSON Schema&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;✅ (&lt;code&gt;.json&lt;/code&gt;)&lt;/td&gt;
 &lt;td&gt;사람이 읽기 좋음 · 도구 생태계 풍부&lt;/td&gt;
 &lt;td&gt;binary 비효율 · payload 크기 큼&lt;/td&gt;
 &lt;td&gt;사람 디버깅 빈번한 케이스&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;JSON (스키마 없음)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;진입 장벽 낮음&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;schema drift 위험&lt;/strong&gt; · 운영급 비추 &lt;em&gt;(개인 추론)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;빠른 PoC 외에는 비권장&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;String / ByteArray&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;❌&lt;/td&gt;
 &lt;td&gt;단순&lt;/td&gt;
 &lt;td&gt;도메인 의미 0&lt;/td&gt;
 &lt;td&gt;메타·로그 wrapper 가 아니면 비추&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;→ 본 조직 default 후보: &lt;strong&gt;Avro + Schema Registry&lt;/strong&gt; (Confluent 표준). 단, 이미 Protobuf 쓰는 도메인이 있다면 Protobuf 도 허용 — &lt;em&gt;(미정)&lt;/em&gt;&lt;/p&gt;</description></item><item><title>7. Connect를 통한 DB 적재</title><link>https://kimtks456.github.io/docs/kafka/connect/5_db_sink/</link><pubDate>Sun, 10 May 2026 00:00:00 +0900</pubDate><guid>https://kimtks456.github.io/docs/kafka/connect/5_db_sink/</guid><description>&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;code&gt;prd.log.system.v1&lt;/code&gt; 토픽의 시스템 로그를 Kafka Connect JDBC Sink로 PostgreSQL에 적재하는 실습.&lt;br&gt;
별도 Consumer 서비스 없이 Connect만으로 처리하는 설정과 동작을 단계별로 구성한다.&lt;br&gt;
개념 일반론은 &lt;a href="https://kimtks456.github.io/docs/kafka/connect/1_concept/"&gt;Connect 개념&lt;/a&gt;, Q&amp;amp;A는 &lt;a href="https://kimtks456.github.io/docs/kafka/connect/3_db_sink_qna/"&gt;DB Sink 시나리오&lt;/a&gt;, 라이선스 이슈는 &lt;a href="https://kimtks456.github.io/docs/kafka/connect/4_license/"&gt;Connect 라이선스&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2 id="1-전체-흐름"&gt;1. 전체 흐름&lt;a class="anchor" href="#1-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;order-service (Producer)
 │ SystemLogEvent (JSON)
 ▼
Kafka Broker — prd.log.system.v1
 │
 ▼
Kafka Connect Worker
 │
 ├── [1] Consumer.poll() ←── max.poll.records, fetch.min.bytes, fetch.max.wait.ms
 │
 ├── [2] SMT Pipeline
 │ ├── rename: camelCase → snake_case
 │ └── drop: context 필드 제거
 │
 └── [3] JDBC Sink
 └── batch INSERT (batch.size 개씩 묶어 1 SQL 문장으로)
 │
 ▼
 PostgreSQL — system_log 테이블&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="2-커넥터-설정-전체-system-log-sinkjson"&gt;2. 커넥터 설정 전체 (&lt;code&gt;system-log-sink.json&lt;/code&gt;)&lt;a class="anchor" href="#2-%ec%bb%a4%eb%84%a5%ed%84%b0-%ec%84%a4%ec%a0%95-%ec%a0%84%ec%b2%b4-system-log-sinkjson"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;system-log-sink&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;config&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;connector.class&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;io.aiven.connect.jdbc.JdbcSinkConnector&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;tasks.max&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;topics&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;prd.log.system.v1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;connection.url&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${env:DB_URL}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;connection.user&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${env:DB_USER}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;connection.password&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${env:DB_PASSWORD}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;insert.mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;insert&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;auto.create&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;auto.evolve&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;table.name.format&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;system_log&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;pk.mode&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;none&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;value.converter&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;org.apache.kafka.connect.json.JsonConverter&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;value.converter.schemas.enable&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;batch.size&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;consumer.override.max.poll.records&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;consumer.override.fetch.min.bytes&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1024&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;consumer.override.fetch.max.wait.ms&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;1000&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;transforms&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;rename,drop&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;transforms.rename.type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;org.apache.kafka.connect.transforms.ReplaceField$Value&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;transforms.rename.renames&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;eventId:event_id,aggregateId:aggregate_id,serviceId:service_id,occurredAt:occurred_at&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;transforms.drop.type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;org.apache.kafka.connect.transforms.ReplaceField$Value&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;transforms.drop.exclude&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="3-필드별-설명"&gt;3. 필드별 설명&lt;a class="anchor" href="#3-%ed%95%84%eb%93%9c%eb%b3%84-%ec%84%a4%eb%aa%85"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="31-기본-식별"&gt;3.1. 기본 식별&lt;a class="anchor" href="#31-%ea%b8%b0%eb%b3%b8-%ec%8b%9d%eb%b3%84"&gt;#&lt;/a&gt;&lt;/h3&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;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;system-log-sink&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Connect 내 커넥터 고유 이름. REST API &lt;code&gt;/connectors/{name}&lt;/code&gt; 으로 조회·관리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;connector.class&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;io.aiven.connect.jdbc.JdbcSinkConnector&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;사용할 커넥터 구현체. Aiven JDBC Sink (Apache 2.0). &lt;a href="https://kimtks456.github.io/docs/kafka/connect/4_license/"&gt;라이선스 이슈 참고&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tasks.max&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;병렬 처리 task 수. &lt;strong&gt;1로 고정한 이유&lt;/strong&gt;: 여러 task 쓰면 각 task가 독립 batch 가져가므로 batch 크기가 분산됨. 로그 적재는 순서보다 batch 효율이 중요하므로 단일 task로 묶음 처리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;topics&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;prd.log.system.v1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;이 커넥터가 소비할 Kafka 토픽. 여러 개면 콤마 구분&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="32-db-연결"&gt;3.2. DB 연결&lt;a class="anchor" href="#32-db-%ec%97%b0%ea%b2%b0"&gt;#&lt;/a&gt;&lt;/h3&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;&lt;code&gt;connection.url&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;${env:DB_URL}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JDBC URL. &lt;code&gt;${env:VAR}&lt;/code&gt; 형식으로 Connect Worker의 환경변수에서 주입. 하드코딩 금지&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;connection.user&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;${env:DB_USER}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;DB 사용자&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;connection.password&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;${env:DB_PASSWORD}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;DB 패스워드&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Connect Worker의 &lt;code&gt;connect-distributed.properties&lt;/code&gt;에서 &lt;code&gt;config.providers=env,file&lt;/code&gt; 설정이 필요.&lt;/p&gt;</description></item></channel></rss>