신뢰성이란 내가 예상한 대로 기능이 올바르게 작동하는 것을 말한다.

 

대표적인 예시로 데이터베이스의 트랜잭션이 있다. 이건 ACID 하게 작동함을 보장해준다. 

 

그럼 카프카에서 제공하는 기본적인 신뢰성은 무엇일까?

  • 카프카는 파티션 안의 메시지들 순서를 보장한다.
  • 클라이언트가 쓴 메시지는 In-Sync 레플리카에 의해 복제된 이후에야 성공된 것으로 간주되며 컨슈머는 읽을 수 있다.
  • 커밋된 메시지들은 최소 1개의 파티션 레플리카가 남아있는 한 유실되지 않는다.
  • 컨슈머는 설정에 따라 커밋된 메시지만 읽을 수 있다.

카프카는 이런 기본적인 신뢰성 이외에도 설정에 따라서 신뢰성 수준을 추가할 수 있다는 것이 장점이다. 이에 대해서 알아보자.  

 

신뢰성을 주는 기본적인 매커니즘: 복제

카프카는 복제를 통해서 기본적으로 메시지가 유실되지 않음을 보장한다.

 

모든 메시지들은 리더 파티션에 써지며, 읽히는 것도 기본적으로 리더 파티션에서 읽힌다.

  • 리더 파티션에서 읽지 않고, In-Sync 레플리카에서 메시지를 읽도록 하는 것도 성능을 올릴 수 있는 방법이다.
    • 이 경우에는 브로커에서 replica.selector.class 설정을 LeaderSelector (= 항상 리더로부터 읽음) 에서 RackAwardReplicaSelector (= 클라이언트의 client.rack 설정과 일치하는 rack.id 를 가진 레플리카에서 읽음) 으로 변경해줘야한다 그리고 클라이언트에서도 client.rank 설정을 해줘야한다.)

 

In-Sync 레플리카와 Out-Sync 레플리카의 개념:

  • In Sync 레플리카가 되는 조건:
    • 레플리카 이므로 리더가 아니다. 리더가 크래쉬 되고 난 이후에는 In-Sync 레플리카 중에서 리더가 될 것이다.
    • 최근 18초 사이 (설정 가능)에 주키퍼로 하트비트를 전송한 경우로 주키퍼와의 활성 세션이 있어야한다.
    • 최근 30초 사이 (설정 가능) 리더로부터 메시지를 읽어와야한다.
    • 최근 30초 사이 (설정 가능) 렉이 없었어야한다. 
  • Out-Sync 레플리카는 In-Sync 레플리카가 아닌 레플리카를 말한다. 

 

In-Sync 레플리카의 동기화가 느리다면 프로듀서와 컨슈머도 느릴 수 있다.

  • 컨슈머의 경우에는 동기화가 된 데이터만 읽을 수 있기 때문에.
  • 프로듀서는 응답을 언제 받을건지에 따른 설정을 어떻게 하느냐에 따라서 다를 것이다. 

 

신뢰성 있는 브로커 설정

브로커에서 신뢰성을 주기 위한 설정은 크게 두 가지이다:

    1. 토픽 파티션의 복제 수를 정하는 것.
    1. 최소 In-Sync 레플리카 수를 유지하는 것.

하나씩 살펴보자.

 

먼저 '토픽 파티션의 복제 수를 정하는 것' 은 데이터를 복제함으로써 데이터 가용성과 신뢰성을 높이는 방법이다.

 

데이터가 복제되지 않으면 해당 장비만 크래쉬가 나도 데이터가 사라질 거니까 이와 관련된 설정은 필요하다.

 

replication.factor 를 통해서 수동으로 토픽을 만들 때 파티션 복제 수를 정할 수 있고, 자동으로 토픽이 만들어 졌을 때 default.replication.factor 를 통해서 파티션 복제 수를 정할 수 있다.

 

중요한 건 무조건 복제 수를 늘리는 건 좋지 않다.

 

복제 수를 늘리면 그만큼 디스크 사용량을 늘리고, 복제를 위한 네트워크 대역폭을 차지하고 처리량도 늘어날 것이니까.

 

그래서 메시지 저장의 신뢰성을 유지해야하는 것과 유지하지 않아도 되는 것을 구분하는 것이 중요하다.

 

다음으로는 브로커에 '최소 In-Sync 레플리카 수' 를 유지하도록 하는 설정이다.

 

만약 모든 레플리카가 Out-Sync 가 된다면 리더 레플리카가 죽고 새로운 리더가 선별되면서 데이터는 유실될 가능성이 생긴다.

 

그래서 In-Sync 레플리카 수를 유지하도록 하는 것이 중요하다.

 

이와 관련된 설정으로는 min.insync.replicas 설정이 있다.

 

이 설정은 파티션 메시지가 리더에 써진 후 이 설정 값만큼 레플리카에서 복제되지 않으면 쓰기를 성공할 수 없게 된다.

 

신뢰성 있는 프로듀서 사용하기

카프카 프로듀서에서 신뢰성을 주는 설정은 크게 두 가지가 있다.

    1. 메시지가 복제될 때까지 기다리는 것.
    1. 메시지 전송이 일시적으로 실패했다면 재시도 하는 것.

하나씩 살펴보자.

 

'메시지가 복제될 때까지 기다리는 것' 은 프로듀서가 메시지를 전송하는 설정 중 하나인 acks 설정과 관련있다.

 

이 설정을 acks=all 로 하게되면 브로커의 min.insync.replicas 설정과 연계해서 In-Sync 레플리카에 모두 복제가 된 이후에 쓰기 요청이 성공되게 만드는 것이다.

 

이렇게하면 파티션들 중 하나가 죽어도 메시지는 유실되지 않는다.

 

만약 acks=1 로 하게되면 리더 파티션에서만 쓰기가 되면 성공했다고 응답이 올텐데 이 경우에 리더 파티션이 곧바로 크래쉬가 나버린다면 메시지는 유실될 수 있다.

 

(극단적으로 acks=all 을 설정해도 메시지는 유실될 가능성이 있긴하다. 모든 브로커가 디스크에 메시지를 flush 하지 못한 상태에서 동시에 죽는 것.)

 

다음 설정은 '메시지 전송이 일시적으로 실패했다면 재시도 하는 것' 이다.

 

에러는 재시도가 가능한 에러와 재시도를 해도 안되는 에러가 있다.

 

카프카는 기본적으로 재시도가 가능한 에러라면 재시도를 Int.MAX 값만큼 하게 된다.

 

그래서 재시도 수와 관련된 건 기본 설정을 이용하면 되고 또 다른 설정으로 메시지 전송을 포기할 때까지 대기할 수 있는 시간 설정인 deliver.timeout.ms 설정인데 이 수를 최대 값만큼 올리면 된다. (기본 값은 2분이다.)

 

재시도에서 고려해야 하는 사항은 메시지를 보내는 순서가 달라질 수 있다는 것이다.

 

재시도를 하도록 설정되어 있고, enable.idempotence=false 로 설정하고, max.in.flight.requests.per.connection 값이 1보다 크다면 메시지 순서는 달라질 수 있게 된다.

 

신뢰성 있는 컨슈머 사용하기

카프카 컨슈머는 min.insync.replicas 에 복제된 데이터만 읽으니까 일관성있는 데이터만 읽을 것이다.

 

그러므로 읽는 데이터에 대한 걱정은 하지 않아도 된다.

 

여기서는 카프카 컨슈머에서 신뢰성을 주기 위한 설정들을 먼저 보고, 이후에 컨슈머에서 명시적으로 커밋을 할 때 고려해야하는 사항을 보자.

 

신뢰성 있는 처리를 위한 컨슈머 설정

group.id 를 통한 컨슈머 그룹 설정:

  • 만약 카프카 컨슈머가 그룹에 속하지 않고 모든 파티션에 대한 메시지를 읽어야한다면 별도의 독립된 컨슈머 그룹으로 설정해야한다.

 

auto.offset.reset 설정:

  • 컨슈머가 브로커에 없는 오프셋을 요청하거나, 브로커의 consumer_offsets 에 저장된 오프셋이 사라지거나, 처음 브로커와 연결되는 경우에 어떻게 오프셋을 리셋할 것인지 정하는 설정이다.
    • 컨슈머 그룹에 컨슈머가 하나도 없는 경우에는 토픽 파티션의 오프셋이 offsets.retention.minutes 정책에 의해 삭제될 수 있다. 기본적으로 7일 동안만 유지해준다.
  • 이 설정은 크게 earliestlatest, none 이 있다.
    • earliest 는 파티션의 제일 첫번째 메시지를 읽도록 하는 것이다.
    • latest 는 파티션의 제일 마지막 메시지를 읽도록 하는 것이다.
    • none 은 이전 오프셋이 없다면 에러를 던지도록 하는 것이다.

 

enable.auto.commit 설정:

  • 메시지 처리를 했다는 커밋을 자동으로 할 것인지, 수동으로 할 것인지에 대한 설정이다.
  • 자동으로 커밋을 했다 하더라도 메시지 처리가 끝난 후, 다음 번 메시지를 가지고 오는 poll() 에서 커밋을 수행하는 것이기 때문에 기본적으로 메시지 유실이 발생할 확률은 없다. 다만 메시지 처리에 실패했는데 이를 적절하게 Retry Queue 와 Dead Letter Queue 에 넣지 않고 다음 번 poll() 를 호출하는 일만 잘 막으면 된다. 그리고 만약 poll() 을 하는 스레드와 이를 처리하는 스레드가 별개로 작동하는 방식이라면 자동 커밋은 하지 않고 수동 커밋을 해야할 것이다.

 

리밸런싱이 일어날 경우 핸들링:

  • 리밸런싱이 일어날 경우 적절하데 자원을 정리해주고 커밋을 하지 않는다면 불필요한 데이터 중복이 일어나는 등의 문제가 발생할 수 있기 때문에 적절하게 처리해주는게 중요하다.
    • 리밸런싱은 크게 두 종류가 있다.
      • Eager Rebalance:
        • 이 리밸런싱이 일어날 경우에는 모든 컨슈머들은 잠시 자신이 가진 파티션들을 내려놓고 새롭게 파티션들을 재할당 받는다.
        • 잠깐 동안의 다운 타임이 발생할 수 있다.
      • Cooperative Rabalance:
        • 컨슈머 하나씩 할당된 파티션이 해제되어 다른 컨슈머에게 할당되는 과정이 일어나는 것이다.
        • 이 방식은 점진적으로 진행되며 다운 타임이 생기지 않는다.
  • 리밸런싱이 일어나는 과정은 리밸런싱 리스너를 통해 이벤트를 받을 수 있다. 이런 리스너에서 처리 작업을 진행하면 된다:
    • 리밸런싱 이벤트는 다음과 같다:
      • onPartitionsAssigned:
        • 파티션이 컨슈머에게 할당되고 poll() 을 호출하기 전 받는 이벤트이다.
        • 이때 컨슈머에서 사용할 자원을 생성하거나, 필요하다면 오프셋을 가져오는 작업을 진행하면 된다.
        • 중요한 건 모든 작업은 max.poll.timeout.ms 안에 끝내는 것이다. 이때 동안 끝내지 않으면 리밸런싱이 발생된다.
      • onPartitionsRevoked:
        • 컨슈머에 할당된 파티션이 해제될 때 받는 이벤트이다. 이때는 자신이 처리한 메시지까지 커밋을 온전히 수행하는 것이 필요하다.
      • onPartitionsLost:
        • onPartitionsRevoked 가 호출되지 않았는데 다른 컨슈머에서 파티션을 미리 받았을 때 생겨나는 이벤트이다. 이떄는 파티션이 자신의 소유가 아니므로 커밋을 하면 안된다.
        • 중요한 건 이 콜백을 구현하지 않으면 자동으로 onPartitionsRevoked 이 호출되니까 구현을 해야한다.

 

오프셋 커밋하기

신뢰성 있는 컨슈머를 위해 중요한 건 '메시지를 어디까지 읽었는지 커밋 하는 것' 이다.

 

메시지를 읽기만 하고 처리하진 않았는데 커밋을 하는 경우에는 메시지 처리에 신뢰성이 없으므로 이 같은 경우를 없도록 하는게 중요하다.

 

오프셋을 커밋할 땐 다음과 같은 요소들을 고려해서 적용하자:

  • 메시지를 먼저 처리하고, 이후에 오프셋 커밋을 해야한다:
    • 커밋을 먼저하게 되면 메시지 처리에 실패했을 경우에 메시지 유실이 발생할 수 있게 된다.
  • 메시지 처리에 실패할 경우 메시지 유실이 생기지 않도록 해야한다:
    • 메시지 처리에 실패할 경우 재시도를 통해 다시 해결할 수 있도록 해야한다. 이 경우에 가능한 방법은 크게 두 가지가 있다:
      • pause() 를 통해 잠시 메시지 처리를 멈추는 방법:
        • 실패한 메시지를 버퍼에 기록하고 pause() 를 통해 메시지를 읽어갈 수 없도록 설정하는 것이다. (읽어가게 되면 자동 커밋이 될 수 있으니.)
        • 그리고 계속 재시도를 통해서 메시지 처리에 성공하고 나서 resume() 을 통해 처리를 재개하는 것이다.
      • Retry QueueDead Letter Queue 에 기록하고 메시지 처리는 계속 이어가는 방법:
        • 실패한 메시지는 Retry Queue 에 넣고나서 커밋하고, 주기적으로 Retry Queue 에서 데이터를 읽어와서 처리하는 방식이다.
        • 계속 실패할 수도 있으므로 실패가 한계점을 넘었다면 이는 Dead Letter Queue 에 넣도록 하고 수동으로 처리하도록 한다.
  • 컨슈머가 상태를 기록해야할 수도 있다:
    • 메시지 처리를 통해서 평균 (Average) 와 같은 상태 정보를 갱신하고 유지해야한다면 오프셋 말고도 상태 정보도 저장을 해놔야한다.
    • 이 경우에는 별도의 results 토픽을 만들고 여기에 쓰면 된다. 카프카에서 여러 토픽에 쓰는 것은 트랜잭션으로 적용할 수 있으므로 커밋과 results 토픽에다가 메시지를 쓰는 것을 동시에 하면 된다.

 

시스템 신뢰성 검증하기

시스템이 신뢰성이 있는지 확인하려면 검증을 해야한다.

 

여기서는 세 가지 계층에 대한 검증을 소개하곘다.

설정 검증하기

어플리케이션 로직과 격리된 채 카프카 브로커와 클라이언트 설정에 대한 검증을 말한다.

 

이를 검증하기 위한 도구들을 위해 카프카에서 제공해주는 툴은 다음과 같다:

  • org.apache.kafka.tools 에 있는 VerifiableProducer (검증용 프로듀서) 와 VerifiableConsumer (검증용 컨슈머)

각 검증용 프로듀서와 검증용 컨슈머에 설정을 넣어놓고 실행시키고 예상되로 동작하는지 확인하면 된다.

 

검증해볼만한 테스트들은 다음과 같다:

  • 리더 선출: 리더를 정지시키면 어떻게 되는가? 프로듀서와 컨슈머가 평상시처럼 작동을 재개하는데 얼마나 걸릴까?
  • 컨트롤러 선출: 컨트롤러가 재시작한 뒤 시스템이 재개하는 데 얼마나 걸릴까?
  • 롤링 재시작: 메시지 유실 없이 브로커들을 하나씩 재시작할 수 있을까?
  • 언클린 리더 선출 테스트: 한 파티션의 모든 레플리카들을 하나씩 중단시킨 다음 Out-Sync 된 브로커를 재시작 시키면 어떻게 될까?

이외의 여러 테스트 스위트들을 보고 싶다면 다음 링크를 참고하자:

 

어플리케이션 검증하기

카프카와 관련된 다양한 장애 상황을 만들어 본 후 이에 대해 어플리케이션이 올바르게 작동하는지 검증해보면 된다.

 

카프카에 장애 주입은 Trogdor 를 통해서 할 수 있다.

 

어플리케이션에서 카프카를 테스트 하는 방법은 다음 툴 을 참고해서 수행하면 된다.

 

모니터링 하기

Producer 에서 봐야하는 주요 지표들:

  • 레코드 별 에러율 (error-rate)
  • 재시도율 (retry-rate)
  • 에러 로그:
    • 특히 Retry 가 모두 소진된 에러
    • 전송 Timeout 이 난 에러
    • 재시도 불가능한 에러

Consumer 에서 봐야하는 주요 지표들:

  • Consumer Lag:
    • 내가 가져온 메시지가 브로커에 저장된 메시지에 비해 얼마나 뒤쳐져 있는지를 나타내는 지표다.
    • 중요한 건 Consumer Lag 이 계속 쳐지고 있는 지가 중요하다. 따라 잡고 있다면 괜찮음.
  • 프로듀서가 보낸 메시지를 처리했는지 확인할 수 있도록 테스트 해보고 결과를 남겨보는 것도 필요할 수 있다. 

+ Recent posts