Kafka Rebalancing과 메시지 처리 병목 개선

Kafka Rebalancing과 메시지 처리 병목 개선

1. 문제 발생

운영 중 Kafka 메시지가 정상적으로 Consume되지 않는다는 문의를 전달받았습니다.

해당 서버의 로그를 확인한 결과 Consumer Group의 Rebalancing이 약 10분 간격으로 반복 발생하고 있었습니다.

Rebalancing이 발생하는 원인을 확인하기 위해 Consumer 설정과 실제 메시지 처리 시간을 함께 확인했습니다.

당시 max.poll.interval.ms는 10분으로 설정되어 있었고, 하나의 Kafka 메시지를 처리하는 데 10분 이상의 시간이 소요되고 있었습니다.

max.poll.interval.ms는 Consumer가 poll()을 호출한 이후 다음 poll()을 호출하기까지 허용되는 최대 간격입니다. 따라서 메시지 처리 시간이 해당 설정값을 초과하면서 Consumer가 정상적인 처리 주기를 유지하지 못하고 Rebalancing이 발생한 것으로 판단했습니다.

문제를 확인한 당시의 설정은 다음과 같았습니다.

max.poll.interval.ms=600000

로그와 설정을 함께 확인하면서 이번 문제는 Kafka 자체의 메시지 수신 문제라기보다 메시지 처리 시간이 길어지면서 Consumer의 처리 간격 제한을 초과한 문제라는 것을 확인할 수 있었습니다.

2. 우선적인 운영 조치

원인을 확인한 후 우선 운영 중인 Consumer에서 Rebalancing이 반복되는 상황을 완화할 필요가 있었습니다.

이에 max.poll.interval.ms를 기존 10분에서 30분으로 변경했습니다.

max.poll.interval.ms=1800000

설정 변경을 통해 현재 메시지 처리 시간이 10분 이상 소요되더라도 Consumer가 처리할 수 있는 시간을 확보했습니다.

다만 이 조치를 근본적인 해결 방법으로 판단하지는 않았습니다.

현재 처리 시간이 10분이기 때문에 30분으로 변경하는 것은 당장의 문제를 완화할 수 있지만, 향후 데이터 증가나 DB 처리 시간 증가로 인해 처리 시간이 30분을 초과한다면 동일한 문제가 다시 발생할 수 있기 때문입니다.

따라서 운영 환경에서는 우선 Consumer가 정상적으로 메시지를 처리할 수 있도록 설정을 변경한 후, 실제 메시지 처리 시간이 오래 걸리는 원인을 추가로 확인했습니다.

3. 메시지 처리 지연 원인 분석

메시지 처리 로직을 확인한 결과 하나의 Kafka 메시지를 처리하는 과정에서 약 46,000건의 데이터를 MSSQL에 저장하고 있었습니다.

여기서 문제는 46,000건이라는 데이터 건수 자체보다 하나의 메시지를 처리하기 위해 여러 건의 DB 저장 작업이 수행되면서 전체 처리 시간이 10분 이상으로 증가하고 있었다는 점이었습니다.

Kafka Consumer 입장에서는 하나의 메시지를 처리하는 작업이지만, 애플리케이션 내부에서는 해당 메시지를 처리하기 위해 여러 건의 DB 저장 작업을 수행하고 있었습니다.

따라서 Consumer 설정을 추가로 조정하기보다는 메시지 처리 시간 자체를 줄이는 것이 필요하다고 판단했습니다.

메시지 처리 로직을 따라가면서 각 작업 구간을 확인했고, DB 저장 부분이 전체 처리 시간에서 큰 비중을 차지하고 있다는 것을 확인했습니다.

이에 Consumer의 처리 시간을 줄이기 위해 해당 DB 저장 구간을 개선하는 방향으로 작업을 진행했습니다.

이번 문제의 원인과 해결 방향을 정리하면 다음과 같습니다.

  • 하나의 Kafka 메시지 처리에 10분 이상 소요

  • max.poll.interval.ms가 10분으로 설정되어 있음

  • 메시지 처리 중 Rebalancing 반복 발생

  • 메시지 처리 과정에서 DB 저장 구간에 상당한 시간 소요

  • DB 저장 방식을 개선하여 전체 메시지 처리 시간을 줄이는 방향으로 결정

4. DB 저장 방식 변경

메시지 처리 로직을 확인한 결과, 약 46,000건의 데이터를 MSSQL에 저장하는 과정에서 많은 시간이 소요되고 있었습니다.

기존 애플리케이션은 JPA를 사용하고 있었습니다. 이번 개선에서는 특정 메시지 처리 과정의 DB 저장 구간만 Batch 방식으로 변경할 필요가 있었습니다.

JPA의 Batch 설정은 해당 설정을 사용하는 Hibernate 작업 전반에 영향을 줄 수 있기 때문에, 기존 JPA 설정은 유지하면서 해당 저장 로직에만 JdbcTemplate.batchUpdate()를 적용했습니다.

jdbcTemplate.batchUpdate(
   sql,
   data,
   batchSize,
   (PreparedStatement ps, T obj) -> {
      // 데이터 바인딩
   }
 );

기존에는 여러 건의 데이터를 저장하는 과정에서 DB 저장 작업이 반복되고 있었지만, 변경 후에는 데이터를 Batch 단위로 묶어 Insert 하도록 처리했습니다.

이를 통해 기존 JPA를 사용하는 다른 저장 로직에는 영향을 주지 않으면서, 문제가 발생한 DB 저장 구간의 처리 시간을 줄이는 방향으로 개선했습니다.

5. 기존 ID 생성 방식 유지

DB 저장 방식을 변경하면서 기존 ID 생성 방식도 함께 확인했습니다.

해당 Entity는 Hibernate의 guid 전략을 사용하고 있었으며, 기존 환경에서는 MSSQL의 NEWID()를 이용하여 ID가 생성되고 있었습니다.

JdbcTemplate을 이용해 직접 Insert 하는 경우 JPA/Hibernate가 수행하던 ID 생성 과정을 직접 처리해야 하기 때문에 기존과 동일한 방식으로 ID가 생성되도록 구성할 필요가 있었습니다.

Java에서 UUID를 생성하여 전달하는 방법도 가능했지만, 이번 작업에서는 기존 시스템의 ID 생성 방식을 변경하지 않는 것을 우선했습니다.

따라서 Batch Insert에서도 MSSQL의 NEWID()를 사용하도록 구성했습니다.

INSERT INTO TABLE_NAME ( ID, COLUMN_A, COLUMN_B)
VALUES (NEWID() , ?, ?)

이를 통해 기존 시스템에서 사용하던 ID 생성 방식은 유지하면서 DB 저장 방식만 Batch Insert로 변경할 수 있었습니다.

이번 작업에서는 처리 성능을 개선하는 동시에 기존 데이터 생성 방식에 불필요한 변경이 발생하지 않도록 하는 것도 중요한 기준으로 삼았습니다.

6. 개선 결과

Batch Insert를 적용한 이후 약 46,000건을 저장하는 DB 처리 구간의 시간이 감소했고, 이에 따라 Kafka 메시지의 전체 처리 시간도 개선되었습니다.

문제 발생 당시에는 메시지 처리 시간이 max.poll.interval.ms를 초과하면서 Rebalancing이 반복적으로 발생하고 있었습니다.

이후 우선 max.poll.interval.ms를 30분으로 변경하여 운영 환경을 완화하고, 실제 처리 시간이 증가하는 원인이었던 DB 저장 구간을 Batch Insert 방식으로 개선했습니다.

그 결과 DB 저장에 소요되는 시간이 감소하면서 전체 메시지 처리 시간도 함께 줄어들었고, 기존에 반복적으로 발생하던 Rebalancing 현상도 완화되었습니다.

이번 개선에서 중요한 부분은 단순히 max.poll.interval.ms를 30분으로 증가시켜 문제를 회피한 것이 아니라, 설정 변경을 통한 운영 문제 완화와 실제 처리 시간을 줄이기 위한 코드 개선을 분리하여 접근했다는 점입니다.

설정 변경은 운영 중인 Consumer가 메시지를 처리할 수 있는 시간을 확보하기 위한 조치였고, Batch Insert 적용은 실제 메시지 처리 시간을 줄이기 위한 개선이었습니다.

이를 통해 운영 장애를 우선 완화하면서도 실제 처리 지연의 원인을 함께 개선할 수 있었습니다.

7. 이번 문제를 통해 확인한 점

이번 문제를 확인하면서 Kafka Consumer에서 발생하는 Rebalancing을 단순히 Kafka 설정의 문제로만 접근해서는 안 된다는 점을 확인했습니다.

처음에는 "Kafka 메시지가 Consume되지 않는다"는 현상으로 전달받았지만, 로그에서 Rebalancing이 반복되는 것을 확인하고 Consumer 설정과 실제 메시지 처리 시간을 비교하면서 원인을 좁혀갈 수 있었습니다.

특히 max.poll.interval.ms와 실제 메시지 처리 시간이 비슷한 수준으로 나타났고, 메시지 처리 과정에서 DB 저장에 상당한 시간이 소요되고 있다는 사실을 확인하면서 Consumer의 문제처럼 보였던 현상이 실제로는 메시지 처리 로직의 처리 시간과 연결되어 있다는 것을 확인할 수 있었습니다.

또한 max.poll.interval.ms를 증가시키는 것만으로 문제를 해결할 수는 없다는 점도 확인했습니다.

설정값을 증가시키면 Consumer가 메시지를 처리할 수 있는 시간을 더 확보할 수 있지만, 실제 처리 시간이 계속 증가한다면 결국 동일한 문제가 다시 발생할 수 있습니다.

따라서 운영 중 유사한 문제가 발생했을 때는 특정 설정값만 확인하기보다 Rebalancing이 발생한 시점과 실제 메시지 처리 흐름을 함께 확인하면서 어느 구간에서 시간이 증가하고 있는지를 찾는 것이 중요하다고 판단했습니다.

이번 사례에서는 DB 저장 구간이 주요 병목으로 확인되었지만, 동일한 현상이 발생하더라도 실제 원인은 DB가 아닐 수 있습니다.

중요한 것은 특정 기술이나 설정을 원인으로 미리 판단하기보다 로그와 설정, 실제 처리 흐름을 함께 확인하면서 원인을 단계적으로 좁혀가는 것이라고 생각합니다.

8. 마무리

이번 문제는 처음에는 Kafka 메시지가 정상적으로 Consume되지 않는 현상으로 시작했지만, 로그와 Consumer 설정 및 실제 처리 시간을 함께 확인하면서 Rebalancing이 발생하는 원인을 좁혀갈 수 있었습니다.

max.poll.interval.ms를 조정하는 것만으로 문제를 끝내지 않고 실제 메시지 처리 과정에서 시간이 오래 걸리는 DB 저장 구간까지 확인하여 처리 방식을 개선했습니다.

특히 운영 장애가 발생했을 때 현재의 증상을 빠르게 완화하는 것과 실제 처리 흐름을 분석하여 근본적인 원인을 개선하는 것은 구분해서 접근할 필요가 있다는 점을 확인할 수 있었습니다.

앞으로도 유사한 문제가 발생할 경우 단순히 오류가 발생한 지점이나 설정값만 확인하기보다 로그와 실제 처리 흐름을 함께 살펴보면서 원인을 단계적으로 좁혀가고, 확인된 병목 구간을 개선하는 방식으로 대응하고자 합니다.

참고

yeop

Site footer