Study/Kafka

[Kafka] Kafka 의 Offset 관리

꼽냥이 2021. 11. 11. 15:42
반응형

Kafka Offset


카프카의 컨슈머가 poll() 을 호출할 때마다, 컨슈머 그룹은 카프카에 저장되어 있지만 아직 읽지 않은 메시지들을 가져와 처리한다. 이렇게 할 수 있는 것은 컨슈머 그룹이 카프카의 메시지를 어디까지 읽었는 지를 저장하고 있기 때문인데, 어디까지 읽었는 지를 Offset 이라는 값으로 저장하게 된다. 이전 포스팅 에서 다뤘듯, 카프카에 저장되는 각 레코드들은 파티션 별로 독립적인 Offset 값을 가지게 되고, 컨슈머 그룹이 파티션 별로 마지막으로 읽은 레코드의 Offset 을 저장함으로써 읽은 메시지와 읽지 않은 메시지를 구분한다.

 

그러면 컨슈머에 어떤 문제가 발생해 기존에 관리하던 오프셋 정보가 날아간다면? 컨슈머들은 파티션 별로 모든 메시지를 다시 읽고 중복 처리해야 할까?

정답부터 말하자면, 그렇지 않다. 카프카의 컨슈머 그룹은 위와 같은 문제가 발생하는 것을 막기 위해 Offset 을 내부적으로 저장하기도 하지만 카프카 내에 별도로 내부적으로 사용하는 토픽을 생성하고, 그 토픽에 오프셋 정보를 저장하게 되어있다.

 

위와 같이 오프셋 정보를 카프카의 내부 토픽에 저장하는 것을 Commit 이라 하는데, 카프카에서는 커밋과 관련된 여러 방법을 제공한다.

 

Commit


위에서 언급했듯, 컨슈머는 내부적으로 오프셋 정보를 저장하고 이를 사용해 읽지 않은 메시지들만 파티션에서 읽어올 수 있다. 하지만 어떤 상황에는 내부적으로 존재하는 정보를 사용할 수 없는 경우가 존재하는데, 다음의 경우에 그러한 문제들이 생길 수 있다.

  1. 컨슈머가 갑자기 다운될 경우
  2. 컨슈머 그룹에 새로운 컨슈머가 조인할 경우

위의 경우에, 컨슈머 그룹은 기존에 컨슈머 별로 할당된 파티션에 문제가 생긴다. 새로 조인한 컨슈머가 파티션을 배정받지 않아 노는 경우가 생기거나, 기존에 파티션을 배정받아 처리하던 컨슈머가 다운됨으로 인해 해당 파티션을 처리할 컨슈머가 존재하지 않게 된다.

컨슈머 그룹은 이를 해결하기 위해 그룹 내에서 컨슈머들의 리밸런싱을 수행하게 되는데, 이 때 컨슈머들이 기존에 배정받은 파티션과 다른 파티션을 할당받게 될 수 있어 내부적으로 저장한 오프셋 정보를 사용할 수 없게 된다.

 

카프카에서는 이를 위해 내부 토픽에 오프셋 정보를 저장하고, 오프셋 값을 업데이트하는 것을 커밋이라 부른다. 리밸런스를 거치게 되면 컨슈머들이 기존과 다른 파티션을 할당받을 수 있기 때문에 가장 최근에 커밋된 오프셋 값을 가져와 메시지를 다시 읽는 과정을 수행한다.

 

커밋은 어플리케이션의 설정에 따라 자동으로 오프셋 정보를 커밋하는 방법과 수동으로 오프셋을 커밋하는 방법이 나눠진다.

 

자동 커밋

컨슈머 어플리케이션들이 기본적으로 사용하는 자동 커밋 방식은, 설정한 주기에 따라 poll() 을 호출할 때 자동으로 마지막 오프셋을 커밋한다.

컨슈머 옵션 중 enable.auto.commit = true 로 설정하면 자동 커밋 방식을 사용해 오프셋을 관리할 수 있다. 커밋을 하기 위한 주기는 기본적으로는 5초마다 커밋을 하게 되고, 이 주기 또한 auto.commit.interval.ms 값을 조정해 변경이 가능하다.

 

자동으로 커밋을 하는 방식은 오프셋 관리를 자동으로 하기 때문에 편리한 장점이 있는 반면, 리밸런싱으로 인한 문제가 생길 수 있다는 점을 염두해야 한다.

 

예를 들어, 5초마다 자동으로 커밋을 하게 어플리케이션을 설정했다고 하자. 매 5초마다 커밋이 발생할텐데 커밋이 발생하고 3초 뒤에 리밸런싱이 이루어진다면 어떻게 될까? 3초동안 처리한 메시지들에 대해서는 커밋이 이루어지지 않아 리밸런싱이 수행된 후 다른 컨슈머에 의해 다시 메시지를 읽고 중복으로 처리하게 될 수 있다.

 

자동 커밋의 주기를 줄인다고 해도, 중복이 발생하는 메시지의 개수를 줄일 뿐 중복 자체를 없앨 수는 없다. 그러면 이 문제를 해결하기 위해 고려해야 하는 것은 어떤 것이 있을까? 카프카는 이와 같이 메시지의 중복 수신이 발생할 수 있기 때문에 메시지 처리에 있어 멱등성을 생각해야 한다. 중복으로 메시지를 처리하더라도 결과에 변동이 되지 않도록 어플리케이션의 설계를 잘 해야 한다.

 

수동 커밋

수동 커밋은 자동 커밋과 다르게, 메시지 처리가 완벽하게 이루어지지 않으면 메시지를 읽지 않은 것으로 간주해야 할 경우 사용된다.

 

예를 들어, 컨슈머가 메시지를 가져와 DB 에 저장하는 상황이라 가정하자. 만약 자동 커밋을 사용한다면, DB 에 저장하는 로직이 실패할 경우에도 메시지의 오프셋은 커밋되었기 때문에 메시지의 손실이 발생할 수 있다. 이러한 경우를 방지하기 위해서, DB 에 저장되는 로직이 성공한 것을 확인한 후 메시지의 오프셋을 커밋해 메시지가 손실되지 않고 처리될 수 있도록 해야 한다.

 

일반적으로는 위와 같이 메시지 처리의 결과에 따라 오프셋 커밋을 결정할 때 사용하는 것이 수동 커밋인데, 이 또한 자동 커밋 방식과 마찬가지로 메시지의 중복이 발생할 수 있다. 위와 같은 DB 저장 오류 시, 해당 메시지부터 다시 읽어와 처리를 수행하게 될텐데 해당 메시지 이후의 메시지들은 정상적으로 DB 에 저장되었다고 하자. 그러면 결국 오류가 발생한 메시지 외의 다른 메시지들은 중복으로 DB 에 저장될 것이다.

 

결국 어떤 커밋 방식을 사용한다 하더라도 메시지가 중복되는 것은 피할 수 없는 문제이다. 이는 카프카에서 기본적으로 Exactly-Once 방식이 아니라 At-Least-Once 방식을 채용하고 있기 때문인데, 중복은 발생할 수 있지만 손실은 발생하지 않는다는 원칙이다.

카프카에서도 Exactly-Once 를 사용할 수 있는 방식은 있지만 기본적으로는, 메시지의 중복을 처리하는 것은 결국 카프카를 사용하는 개발자의 몫이고 이 문제의 해결을 위해 많은 고민을 필요로 한다.

 

카프카의 Exactly-Once 방식은 추후 다른 포스팅에서 다루도록 하겠다.