
doa-kafka-kafka-bootstrap:9092)를 통해 Kafka 클러스터에 접속하고,

upserter.py 로 정의되어있다.DLQ Topic에 Message 를 전달하는 역할도 한다.cdc.public.rma_lists)에 Message는 Postgresql PUBLICATION 등록되어있는 rma_lists 테이블 업데이트 시 Replication Slot을 통해 Debezium CDC가 WAL을 읽어 Kafka Topic에 Message를 전달한다. 해당 Message를 Consumer가 읽어서 Goolge API 전송 후 정상처리 완료시에만 Kafka Broker에 Offset을 늘리고 Commit한다.
Kafka-Topic-rma.lists
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
name: cdc.public.rma-lists
namespace: streaming
labels:
strimzi.io/cluster: doa-kafka # 클러스터 이름 Label
spec:
topicName: cdc.public.rma_lists
partitions: 3 # 병렬 소비 Parition 개수
replicas: 1 # Kafka Broker 보다 클 수 없음
config: # Topic Message보관관련
retention.ms: 604800000 ## 7일
segment.bytes: 1073741824 ## 1GB
Consumer.group.id
def build_consumer()->Consumer:
return Consumer({
"bootstrap.servers": BOOTSTRAP, ## doa-kafka-kafka-bootstrap:9092
"group.id": KAFKA_GROUP_ID, ## sheets-upserter
....
})
apiVersion: apps/v1
kind: Deployment
metadata:
name: sheets-upserter
namespace: streaming
spec:
replicas: 3
selector:
matchLabels: { app: sheets-upserter }
template:
metadata:
labels: { app: sheets-upserter }
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
spec:
containers:
- name: app
image: ganplank/sheets-upserter:10.2TOPIC 내용 확인
alias kconsume='kubectl -n streaming exec -it doa-kafka-kp-brokers-0 -- /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic cdc.public.rma_lists --from-beginning'
{"id":"rma-20251013061548-SwzL3S","name":"흠냐","company":"흠냐","email":"hmm@gmail.com","model":"kaka","serial_number":"ka","as_method":"유상","initial_install_date":20367,"failure_date":null,"version":null,"memo":"kaka","status":"pending","created_at":1760336148304617,"__deleted":"false","__op":"c","__source_ts_ms":1760336148305,"__table":"rma_lists"}
Consumer 수동 + 비동기 동작 설정
def build_consumer()->Consumer:
return Consumer({
"bootstrap.servers": BOOTSTRAP,
"group.id": KAFKA_GROUP_ID,
"enable.auto.commit": False,
...
try:
with m_batch_latency.time():
apply_batch(state, upserts, deletes)
consumer.commit(offsets=last_offsets, asynchronous=False)
m_msgs_ok.inc(len(upserts)+len(deletes))
__PK 값 기준으로 멱등성을 보장하여 최신 Message 기준으로 업데이트되므로 중복제거
Kafka Consumer Loop 구조 에러 발생 시 재시도 설정
@retry(wait=wait_exponential(min=1, max=30), stop=stop_after_attempt(6),
retry=retry_if_exception_type((HttpError, ConnectionError, TimeoutError, BrokenPipeError)))
consumer Poll 설정
BATCH_SIZE = int(os.environ.get("BATCH_SIZE", "200"))
POLL_MS = int(os.environ.get("POLL_MS", "1000"))
....
try:
while not stop_flag:
msgs = consumer.consume(BATCH_SIZE, timeout=POLL_MS/1000.0)
if not msgs: continue
BATCH_SIZE : Message(Google Sheet 행) 단위응답대기시간 : POLL_MS/1000.0 = 초 단위Prometheus 매트릭 수집 (진행 예정)
이메일 및 슬랙 메시지 알림 (진행 예정)
1) poll: 카프카에서 최대 BATCH_SIZE개 메시지 가져옴
2) apply: 모은 레코드를 한 번에 Sheets API(append/update/batchUpdate)로 반영
3) commit: 위 2번이 정상 완료됐을 때만, 그 배치의 마지막 오프셋까지 커밋
다음 배치로 go
consumer 동작 플로우
┌────────────────────────────┐
│ PostgreSQL (Debezium) │
│ → cdc.public.rma_lists │
└──────────────┬─────────────┘
│ Kafka Broker
▼
┌───────────────────────────────┐
│ Kafka Consumer (upserter) │
│ poll() │
│ ↓ │
│ 메시지 수신 │
│ ↓ │
│ Google Sheets API 업데이트 │
│ ↓ │
│ 처리 성공 시: │
│ last_offsets.append( │
│ TopicPartition( │
│ msg.topic(), │
│ msg.partition(), │
│ msg.offset() + 1)) │
│ ↓ │
│ consumer.commit(offsets=last_offsets) │
│ ↓ │
│ Prometheus metrics inc() │
│ ↓ │
│ 다음 poll() 실행 │
└──────────────────────────────┘
│
(예외 발생 시)
▼
┌────────────────────────────┐
│ Kafka DLQ Producer │
│ produce(DLQ_TOPIC, ...) │
└────────────────────────────┘
흐름표
| 단계 | 동작 | 세부 내용 |
|---|---|---|
| ① Kafka Poll | consumer.poll() | Kafka로부터 CDC 메시지 수신 |
| ② 파싱 | json.loads(msg.value()) | Debezium JSON이벤트를 Python Dictionary로 파싱 |
| ③ Sheets 업데이트 | update_sheets() | Google Sheets API로 데이터 쓰기 (.values().update()) |
| ④ 성공 시 offset 기록 | msg.offset() + 1 | 다음 메시지부터 읽도록 마킹 |
| ⑤ 커밋 | consumer.commit(offsets=last_offsets) | Kafka Broker에게 처리완료 Commit되면 내부적으로 Kafka Broker __consumer_offsets Topic에 저장 |
| ⑥ 실패 시 DLQ | producer.produce(DLQ_TOPIC, value=msg.value()) | 오류난 메시지는 DLQ로 전송 |
| ⑦ 지표 증가 | m_api_calls.inc() | Prometheus 메트릭 증가 |
https://sungjk.github.io/2021/01/10/kafka-consumer.html
https://pathtosenior.substack.com/p/a-gentle-introduction-to-kafka-consumer
https://velog.io/@jaehyeong/Apache-Kafka%EC%95%84%ED%8C%8C%EC%B9%98-%EC%B9%B4%ED%94%84%EC%B9%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80
https://velog.io/@hyun6ik/Apache-Kafka-Partition-Assignment-Strategy
https://yeongchan1228.tistory.com/m/60
https://curiousjinan.tistory.com/entry/understand-kafka-partitions
https://github.com/schooldevops/kafka-tutorials-with-kido/blob/main/01.kafka_install.md
https://www.jaenung.net/tree/28776