이슈 해결 일지

Kafka offset이 뒤로 돌아가는 문제

haong_ 2023. 10. 17. 17:09

카프카를 적용해 실시간 대시보드용 데이터를 만들고 있다. 본격적인 운영전에 스테이지 환경에서 이것저것 테스트를 해보는 중에 컨슈머에서 offset이 몇백개씩 돌아가는 문제를 발견했다.

문제 발견 

스테이지 배포 후 AWS RDS에서 모니터링으로 들어가보니 특정 주기마다 CPU가 치솟아있는 것을 목격했다.

특정 주기를 설정 해놓은 부분은 커넥션이 끊겼을때를 대비해 재연결 하는 부분 밖에 없어서 커넥션이 재연결된 시점에 어떤 문제가 발생하고 있음이 분명했다. pod 로그를 살펴보니 컨슈머에서 offset이 최신값이 아닌 몇백개씩 돌아가고 있었고, 재연결 후 돌아간 오프셋만큼 한꺼번에 밀린 처리를 하려다보니 CPU가 특정 시점에 갑자기 올라가는 것이었다. 

처음에는 오프셋 리셋 옵션을 earlist 로 줘서 그런줄 알았는데, 오프셋 리셋 옵션은 컨슈머 그룹이 새로 할당 될때 작동하는 옵션이어서 이 경우에는 맞지 않았다. 우리는 똑같은 컨슈머 그룹을 사용하고 있는데도 오프셋이 돌아갔다. 현재 우리가 사용하고 있는 confluent-kafka는 오토커밋이 디폴트 5분으로 설정 되어있다. 당연히 오토커밋이 있으니 재연결이 되거나 팟이 재시작하더라도 마지막으로 커밋된 오프셋부터 재시작 할거라고 생각했는데, 그것보다 더 이전 값으로 돌아갔다. 오토커밋 주기를 짧게 바꾸면 그만큼 부하가 걸리게 되기 때문에 좋은 선택지는 아니라고 생각했고, 실제로 주기를 짧게 바꿔도 그보다 더 이전 값으로 오프셋이 돌아가기도 해서 완전한 해결책은 아니었다. 

해결 

카프카의 메인코드는 대체로 아래와 같이 사용한다. 메세지를 주기적으로 가져오고 에러가 나지 않을 경우 else 문에서 메세지를 처리하는 방식이다.

try:
    while True:
        current_time = datetime.datetime.now()
        time_diff = current_time - last_called

        if time_diff >= datetime.timedelta(hours=8): # 주기적으로 커넥션 재연결 하는 부분
            consumer.commit() # 커밋 명시 
            consumer.close()  # 후 종료 

            # consumer 호출
            producer.flush()
            # producer 호출

        msg = consumer.poll(1.0)
        if msg is None:
            continue
        elif msg.error():
            logger.error("msg_error")
            raise KafkaException(msg.error())
        else:
            retry_count = 0
            while True:
                try:
                    # 메세지 받아서 처리
                    break
                except Exception as e:
                    # 리트라이 및 DLQ 보내는 처리

except KeyboardInterrupt:
    logger.error('Aborted by user')
finally:
    consumer.commit() # 커밋 명시 
    consumer.close()
    producer.flush()

while이 종료될 때 finally 문에서 컨슈머의 close만 해주고 있었는데 commit을 명시적으로 적어둠으로써 종료 시점에 커밋을 할 수 있도록 했다. 그리고 커넥션 재연결 시점에도 재연결 하기 전에 커밋을 먼저하고 close로 종료 해준 후 재연결을 호출함으로써 오프셋이 돌아가는 문제를 해결했다. 

이 문제로 좀 헤맸었는데, 오프셋이 돌아간다는 것은 결국 어디선가 커밋과 종료가 제대로 이루어지지 않아서, 마지막 어딘가의 커밋 시점의 오프셋으로 돌아가는 것이기 때문에 해당 부분 위주로 살펴보면 좋을 것 같다.