Back to feed
ASH avatar
ASH

2026. 5. 25.·v2·

Kafka에서 고려할 수 있는 처리량 제한

'가상 면접 사례로 배우는 대규모 시스템 설계' 스터디에서 4장을 정리한 내용입니다.

kafka

일반적인 백엔드 서버에서는 초당 API 몇 번 허용 같은 rate limit를 주로 다루지만 kafka같은 메세지 브로커의 처리량 제한은 관점이 조금 다르다.

카프카에서는 요청 수보다 아래와 같은 기준이 더 중요하다.

  1. 초당 produce되는 메시지 수
  2. 초당 produce되는 byte 수
  3. consumer가 한 번에 가져오는 record 수
  4. consumer가 초당 처리할 수 있는 record 수
  5. partition별 lag 증가량
  6. broker network / disk 처리량

Producer 쪽에서 고려할 제한사항

producer는 카프카에 메시지를 넣는 쪽으로 여기서 제한을 고려하지 않으면 짧은 시간에 메시지가 폭발적으로 쌓일 수 있다.

예를 들어 주문 API 요청이 급증하면 주문 생성 이벤트를 Kafka에 계속 발행하고 이 이벤트를 처리하는 consumer의 처리속도보다 발행량이 많다면 consumer lag이 증가하고 후속 처리 지연으로 이어지게 된다.

이런 문제들 때문에 producer 쪽에서는 아래와 같은 설정들을 고려할 수 있다.

yaml
spring:
	kafka:
		producer:
			properties:
				linger.ms: 5
				batch.size: 16384
				buffer.memory: 33554432

batch.size는 같은 파티션으로 보내는 record들을 묶어서 요청 수를 줄이는데 사용되고, linger.ms는 producer가 메세지 하나를 받자마자 바로 kafka로 보내지 않고 얼마나 기다렸다가 더 모아서 같이 보낼지를 정하는 대기 시간이다.

하지만 이건 처리량 최적화에 가깝고 사용자별 초당 N개만 브로커로 이벤트 발행 같은 제한을 두려면 애플리케이션 레벨에 두어야한다.

kotlin
fun createOrder(userId: Long, request: CreateOrderRequest) {
	rateLimiter.check(userId) // 여기서 체크하고 초과한다면 에러를 보내던가 할 수 있다.

	val order = orderService.create(request)

	kafkaTemplate.send("order-created", order.id.toString(), order)
}

Broker 레벨 quota(할당량)

카프카 자체에도 할당량 개념이 있어서 user나 client-id 단위로 byte rate quota를 설정하는게 가능하다.

bash
bin/kafka-configs.sh \
  --bootstrap-server localhost:9092 \
  --alter \
  --add-config 'producer_byte_rate=1048576,consumer_byte_rate=2097152' \
  --entity-type clients \
  --entity-name order-service

위 설정의 의미는 client-id가 order-service인 클라이언트의 producer 전송량을 초당 약 1MB로 제한하고 consumer 수신량을 초당 약 2MB로 제한한다는 뜻이다.

횟수기반 limit이 아니라 byte 기반이라 비즈니스 정책처럼 쓰기에는 조금 까다로워 보인다.

그래서 이건 특정 서비스가 Kafka 클러스터 네트워크를 과도하게 쓰지 못하게 하거나 특정 consumer가 broker에서 데이터를 너무 많이 가져가지 못하게 할 때 사용한다고 한다.

Consumer는?

스프링 백엔드에서 @KafkaListener를 쓴다면 consumer가 한 번에 너무 많이 가져와서 처리 시간이 길어지는 걸 조심해야한다.

yaml
spring:
	kafka:
		consumer:
			max-poll-records: 100

max.poll.records는 한 번의 poll에서 반환되는 최대 record수를 제한한다.

max.poll.records가 너무 크다면 poll 한 번에 많은 record를 받게 되고 이를 @KafkaListener가 record를 하나씩 처리하는 과정에서 전체 처리 시간이 길어져 다음 poll이 늦어진다.

이 경우 consumer group을 사용할 때 poll과 poll 호출 사이의 최대 지연 시간인 max.poll.interval.ms를 초과할 수 있고 이 시간을 넘기면 consumer가 실패한 것으로 간주되어 group rebalance가 발생하게 된다.

rebalance는 consumer group안에서 각 consumer가 어떤 파티션을 담당할지 다시 배정하는 과정으로 추후에 자세히 알아보도록 하고 지금은 rebalance가 자주 발생하면 성능과 안정성에 악영향을 끼친다는 것만 알아두자.

그래서 consumer의 처리 시간이 길다면 max.poll.interval.ms의 값을 크게 주는 것이 좋다.

하지만 무작정 크게 준다면 그 만큼 문제 감지와 복구가 늦어질 수 있기 때문에 내 서비스에 맞는 적절한 값을 찾는게 중요하다.

@KafkaListener의 concurrency

Spring Kafka에서는 @KafkaListener로 동시성을 조절할 수 있다.

kotlin
@KafkaListener(
	topics = ["order-created"],
	groupId = "order-service",
	concurrency = "3"
)

concurrency를 늘리게되면 컨슈머 그룹 내 컨슈머의 수를 늘려 병렬처리를 할 수 있게 된다.

하지만 kafka는 파티션 단위로 컨슈머가 1대1로 붙어 처리되기 때문에 같은 컨슈머 그룹 안에서 concurrency가 파티션 수보다 많다면 남는 컨슈머가 생길 수 있다. 큰 문제를 일으키는 건 아니지만 리소스가 낭비되므로

concurrency <= partition 수로 맞춰주는 것이 좋다.

주문 생성 이벤트를 Kafka로 받고 컨슈머에서 쿠폰, 재고, 알림, 통계 처리를 한다고 해보자.

이때 처리량 제한을 고려하지 않으면 이런 문제가 발생할 수 있다.

yaml
Kafka에는 초당 1000 이벤트 유입

Consumer는 초당 200개만 처리 가능

Lag 계속 증가

   이벤트 처리 지연

사용자는 결제했는데 알림/재고 반영이 늦어짐

위에서 언급했던 내용들을 적용해본다면 고려할 수 있는 선택지는 아래와 같다.

markdown
1. consumer concurrency 증가
2. topic partition 수 증가
3. max.poll.records 조정
4. producer 쪽에서 유입량 제한
5. broker quota 적용

Related

0
Comments

Join the thread

Leave feedback, ask for clarification, or keep a focused discussion attached to this article.

0 comments
No comments yet. Start the first thread for this article.
Current user avatar
Styling with Markdown is supported