※ 카프카의 내부 동작 원리와 구현에서 가장 중요한 부분 중 하나는 리플리케이션 동작이다.
리플리케이션에 이어, 리더와 팔로워의 역할과 리더에포크와 복구 동작들을 정리한다. 그 다음 리플리케이션 동작과 관련 있는 컨트롤러와 컨트롤러의 동작, 그리고 로그와 로그 컴팩션에 대해 정리한다.
1. 카프카 리플리케이션
고가용성 분산 스트리밍 플랫폼인 카프카는 무수히 많은 데이터 파이프라인의 정중앙에 위치하는 메인 허브 역할을 한다.
이렇게 중앙에서 메인 허브 역할을 하는 카프카 클러스터가 만약 하드웨어의 문제나 점검 등으로 인해 정상적으로 동작하지 못한다거나, 카프카와 연결된 전체 데이터 파이프라인에 영향을 미친다면 이는 매우 심각한 문제를 초래한다.
이때 안정성을 확보하기 위해 카프카 내부에서는 리플리케이션이라는 동작을 하게 된다.
1.1 리플리케이션 동작 개요
- 카프카의 리플리케이션 동작을 위해 토픽 생성 시 필숫값으로 replication factor라는 옵션을 설정해야 한다.
- 카프카는 리플리케이션 팩터라는 옵션을 이용해 관리자가 지정한 수만큼의 리플리케이션을 가질 수 있기 때문에 N개의 리플리케이션이 있는 경우 N-1까지의 브로커 장애가 발생해도 메시지 손실 없이 안정적으로 메시지를 주고 받을 수 있다.
1.2 리더와 팔로워
- 카프카는 내부적으로 모두 동일한 리플리케이션들을 리더와 팔로워로 구분하고, 각자의 역할을 분담시킨다.
- 리더는 리플리케이션 중 하나가 선정되며, 모든 읽기와 쓰기는 그 리더를 통해서만 가능하다. 다시 말해, 프로듀서는 모든 리플리케이션에 메시지를 보내는 것이 아니라 리더에게만 메시지를 전송한다.
- 또한 컨슈머도 오직 리더로부터 메시지를 가져온다.
- 팔로워는 리더에 문제가 발생하거나 이슈가 있을 경우를 대비해 언제든지 새로운 리더가 될 준비를 해야한다.
- 따라서 컨슈머가 토픽의 메시지를 꺼내 가는 것과 비슷한 동작으로 지속적으로 파티션의 리더가 새로운 메시지를 받았는지 확인하고, 새로운 메시지가 있다면 해당 메시지를 리더로부터 복제한다.
1.3 복제 유지와 커밋
- 리더와 팔로워는 ISR[InSyncReplica]이라는 논리적 그룹으로 묶여있다.
- 해당 그룹 안에 속한 팔로워들만이 새로운 리더의 자격을 가질 수 있다.
- 파티션의 리더는 팔로워들이 뒤처지지 않고 리플리케이션 동작을 잘하고 있는 지를 감시한다.
- 리더는 읽고 쓰는 동작은 물론, 팔로워가 리플리케이션 동작을 잘 수행하고 있는지도 판단한다.
- ISR 내에서 모든 팔로워의 복제가 완료되면, 리더는 내부적으로 커밋되었다는 표시를 한다.
- 마지막 커밋 오프셋 위치는 하이워터마크[high water mark]라고 부른다.
- 즉 커밋되었다는 것은 리플리케이션 팩터 수의 모든 리플리케이션이 전부 메시지를 저장했음을 의미한다. 이렇게 커밋된 메시지만 컨슈머가 읽어갈 수 있다.
- 카프카에서 커밋되지 않은 메시지를 컨슈머가 읽을 수 없게 하는 이유는 메시지의 일관성을 유지하기 위해서이다.
1.4 리더와 팔로워의 단계별 리플리케이션 동작
- 카프카는 리더와 팔로워 간의 리플리케이션 동작을 처리할 때 서로의 통신을 최소화 할 수 있도록 설계함으로써 리더의 부하를 줄였다.
→ 위 그림은 peter-test01 토픽이 1개의 파티션과 3개의 리플리케이션 팩터를 갖고 있다. 또한 현재는 리더만이 0번 오프셋에 Message1이라는 메시지를 갖고 있는 상태이다. 나머지 팔로워들은 아직 리더에게 저장된 메시지를 리플리케이션 하기 전이다.
→ 팔로워들은 리더에게 0번 오프셋 메시지 가져오기(fetch) 요청을 보낸 후 새로운 메시지 message1이 있다는 사실을 인지하고 Message1 메시지를 리플리케이션 하는 과정이다.
- 전통적인 메시징 큐 시스템인 래핏MQ의 트랜잭션 모드에서는 모든 미러(카프카에서 팔로워에 해당)가 메시지를 받았는지에 대한 ACK를 리더에게 리턴하므로, 리더는 미러들이 메시지를 받았는지 알 수 있다.
- 하지만 카프카의 경우에는 리더와 팔로워 사이에서 ACK를 주고 받는 통신이 없다. 오히려 카프카는 리더와 팔로워 사이에 ACK 통신을 제거함으로써 리플리케이션 동작의 성능을 더욱 높였다.
- 리더는 1번 오프셋의 위치에 두 번째 새로운 메시지인 Message2를 프로듀서로부터 받은 뒤 저장한다.
- 0번 오프셋에 대한 리플리케이션 동작을 마친 팔로워들은 리더에게 1번 오프셋에 대한 리플리케이션을 요청한다.
- 팔로워들로부터 1번 오프셋에 대한 리플리케이션 요청을 받은 리더는 팔로워들의 0번 오프셋에 대한 리플리케이션 동작이 성공했음을 인지하고, 오프셋 0에 대해 커밋 표시를 한 후 하이워터마크를 증가시킨다.
- 팔로워가 0번 오프셋에 대한 리플리케이션을 성공하지 못했다면, 팔로워는 1번 오프셋에 대한 리플리케이션 요청이 아닌 0번 오프셋에 대한 리플리케이션 요청을 보낸다.
- 따라서 리더는 팔로워들이 보내는 리플리케이션 요청의 오프셋을 보고, 팔로워들이 어느 위치의 오프셋까지 리플리케이션을 성공했는지를 인지할 수 있다.
- 리플리케이션의 마지막 과정으로 리더의 응답을 받은 모든 팔로워는 0번 오프셋 메시지가 커밋되었다는 사실을 인지하게 되고, 리더와 동일하게 커밋을 표시한다.
- 그리고 1번 오프셋 메시지인 message2를 리플리케이션한다. 이렇게 리더와 팔로워들은 위 과정들을 반복하며 동일한 파티션 내 리더와 팔로워 간 메시지의 최신 상태를 유지한다.
- 카프카는 ACK 통신 단계를 제거하여 속도를 향상시킬 수 있었다.
- 카프카의 또 다른 장점은 리플리케이션 동작에서 ACK 통신을 제거했음에도 불구하고 팔로워와 리더 간의 리플리케이션 동작이 매우 빠르면서도 신롸할 수 있다는 점이다.
- 리플리케이션 동작에서 리더의 부하를 줄여주기 위해 리더가 푸시하는 방식이 아니라 팔로워들이 풀하는 방식으로 동작한다.
1.5 리더에포크와 복구
리더에포크[LeaderEpoch]는 카프카의 파티션들이 복구 동작을 할 때 메시지의 일관성을 유지하기 위한 용도로 이용된다.
또한 복구 동작 시 하이워터마크를 대체하는 수단으로도 활용된다.
[ 리더에포크가 없다는 가정 하에 장애로부터 복구 과정 ]
- 리더는 프로듀서로부터 message1 메시지를 받았고, 0번 오프셋에 저장, 팔로워는 리더에게 0번 오프셋에 대한 가져오기 요청을 한다
- 가져오기 요청을 통해 팔로워는 message1 메시지를 리더로부터 리플리케이션한다
- 리더는 하이워터마크를 1로 올린다.
- 리더는 프로듀서로부터 다음 메시지인 message2를 받은 뒤 1번 오프셋에 저장한다.
- 팔로워는 다음 메시지인 message2에 대해 리더에게 가져오기 요청을 보내고, 응답으로 리더의 하이워터마크 변화를 감지하고 자신의 하이워터마크도 1로 올린다.
- 팔로워는 1번 오프셋의 message2 메시지를 리더로부터 리플리케이션한다
- 팔로워는 2번 오프셋에 대한 요청을 리더에게 보내고, 요청을 받은 리더는 하이워터마크를 2로 올린다.
- 팔로워는 1번 오프셋인 message2 메시지까지 리플리케이션을 완료했지만, 아직 리더로부터 하이워터마크를 2로 올리는 내용은 전달받지 못한 상태이다
- 예상하지 못한 장애로 팔로워가 다운된다.
위 그림은 장애가 발생한 팔로워가 종료된 후 장애 처리가 완료된 상태를 나타낸다. 장애에서 복구된 팔로워는 카프카 프로세스가 시작되면서 내부적으로 메시지 복구 동작을 하게 된다.
- 팔로워는 자신이 갖고 있는 메시지들 중에서 자신의 워터마크보다 높은 메시지들은 신뢰할 수 없는 메시지로 판단하고 삭제한다. 따라서 1번 오프셋의 message2는 삭제 된다.
- 팔로워는 리더에게 1번 오프셋의 새로운 메시지에 대한 가져오기 요청을 보낸다.
- 이 순간 리더였던 브로커가 예상치 못한 장애로 다운되면서, 해당 파티션이 유일하게 남아 있던 팔로워가 새로운 리더로 승격된다.
- 팔로워가 뉴리더로 승격된 후의 상태이다. 그림에서 알 수 있듯이 리더는 1번 로프셋의 message를 갖고 있었지만, 팔로워는 message2 없이 새로운 리더로 승격하였다. 결국 뉴리더는 message2를 갖고 있지 않다.
- 리더와 팔로워 간의 리플리케이션이 있음에도 불구하고, 리더가 변경되는 과정을 통해 최종적으로 1번 오프셋의 Message2 메시지가 손실된 것이다.
[ 리더에포크가 존재한다는 가정 하에 장애로부터 복구 과정 ]
- 리더와 팔로워의 리플리케이션 동작 이후, 즉 팔로워가 장애로 종료된 후 막 복구된 상태 이후의 과정이다.
- 앞선 동작에서는 카프카 프로세스가 시작되면서 복구 동작을 통해 자신의 하이워터마크보다 높은 메시지를 즉시 삭제했다.
- 하지만 리더에포크를 사용하는 경우에는 하이워터마크보다 앞에 있는 메시지를 무조건 삭제하는 것이 아니라 리더에게 리더에포크 요청을 보낸다. 복구 과정을 살펴보면,
- 팔로워는 복구 동작을 하면서 리더에게 리더에포크 요청을 보낸다.
- 요청을 받은 리더는 리더에포크의 응답으로 '1번 오프셋의 message2까지'라고 팔로워에게 보낸다.
- 팔로워는 자신의 하이워터마크보다 높은 1번 오프셋의 message2를 삭제하지 않고, 리더의 응답을 확인한 후 message2까지 자신의 하이워터마크를 상향 조정한다.
- 리더가 예상치 못한 장애로 다운되면서 팔로워가 새로운 리더로 승격된 후의 상태이다.
- 리더에포크를 적용하지 않는 경우에는 팔로워가 message2 메시지를 갖고 있음에도 복구 과정에서 하이워터마크보다 높은 메시지를 삭제했다.
- 하지만 리더에포크를 활용하는 경우에는 삭제 동작을 하기에 앞서 리더에포크 요청과 응답 과정을 통해 팔로워의 하이워터마크를 올릴 수 있었고, 메시지 손실은 발생하지 않았다.
위 과정들이 리더에포크의 필요성이다.
2. 로그 [로그 세그먼트]
- 카프카의 토픽으로 들어오는 메시지(레코드)는 세그먼트(로그 세그먼트라고도 함)라는 파일에 저장된다.
- 로그 세그먼트에는 메시지의 내용만 저장되는 것이 아니라 메시지의 키, 밸류, 오프셋, 메시지 크기 같은 정보가 함께 저장되며, 로그 세그먼트 파일들은 브로커의 로컬 디스크에 보관된다.
- 로그 세그먼트가 1GB[기본값]보다 커지는 경우에는 기본적으로 롤링(rolling) 전략을 사용한다. 다시 말해, 하나의 로그 세그먼트에 카프카로 인입되는 메시지들을 계속해서 덧붙이다가 로그 세그먼트의 크기가 1GB에 도달하면 해당 세그먼트 파일을 클로즈하고, 새로운 로그 세그먼트를 생성하는 방식으로 진행한다.
- 로그 세그먼트를 관리하는 방법은 크게 로그 세그먼트 삭제와 컴팩션으로 구분할 수 있다.
2.1 로그 세그먼트 삭제
- 로그 세그먼트 삭제 옵션은 브로커의 설정 파일인 server.properties에서 log.cleanup.policy가 delete로 명시되어야 한다.
- 해당 값은 기본값으로 적용되어 있다.
2.2 로그 세그먼트 컴팩션
- 컴팩션(compaction)은 로그를 삭제하지 않고 컴팩션하여 보관할 수 있다.
- 기본적으로 로컬 디스크에 저장되어 있는 세그먼트를 대상으로 실행되는데, 현재 활성화된 세그먼트는 제외하고 나머지 세그먼트들을 대상으로 컴팩션이 실행된다.
- 컴팩션할지라도 모든 것을 저장하면 용량의 한계에 도달할 것이다. 따라서 좀 더 효율적인 방법으로 컴팩션해야 하며 카프카에서는 로그 세그먼트를 컴팩션하면 메시지(레코드) 키값을 기준으로 마지막의 데이터만 보관한다.
- 컨슈머 그룹은 항상 마지막을 커밋된 오프셋 정보가 중요하므로, 과거에 커밋된 정보들은 삭제돼도 무방하다.
- 이렇게 로그 컴팩션은 메시지의 키 값을 기준으로 과거 정보는 중요하지 않고 가장 마지막 값이 필요한 경우에 사용한다.
- 로그 컴팩션의 장점은 바로 빠른 장애 복구이다.
- 장애 복구 시 전체 로그를 복구하지 않고, 메시지의 키를 기준으로 최신의 상태만 복구한다. 따라서 전체 로그를 복구할 때보다 복구 시간을 줄일 수 있다는 장점이 있다.
'CS > Apache Kafka' 카테고리의 다른 글
[Kafka - 03] 프로듀서의 내부 동작 원리와 구현, 중복 없는 전송 (0) | 2023.08.31 |
---|---|
[Kafka - 01] 카프카 기본 개념과 구조 (2) | 2023.08.23 |
[Kafka - 00] 카프카 개요 및 특징, 구성 (0) | 2023.08.21 |
Kafka 초기 설정 방법 - (Topic 생성 및 확인, Producer/Consumer 설정) (0) | 2023.05.08 |