DR 페일오버 — 데이터 유실, 시작 오프셋 결정, 페일백
Kafka: The Definitive Guide (O'Reilly, 2017, 1st Edition) — Neha Narkhede, Gwen Shapira, Todd Palino Chapter 8: Cross-Cluster Data Mirroring — Active-Standby Architecture: failover details (pp.164-169)
Active-Standby 구성에서 실제로 DR 클러스터로 넘어갈 때 부딪히는 문제들을 정리한다. 미러링이 비동기이므로 계획되지 않은 페일오버에는 데이터 유실이 따르고, 애플리케이션이 DR 클러스터에서 '어디서부터 읽을지'를 정하는 것이 가장 어렵다. auto offset reset, 오프셋 토픽 미러링, 시간 기반 페일오버, 외부 오프셋 매핑 네 가지 방법의 장단점과, 페일오버 이후 원래 클러스터를 되돌리는 절차, 클러스터 디스커버리 설계를 다룬다.
계획되지 않은 페일오버에는 데이터 유실과 불일치가 따른다
Kafka의 미러링 솔루션은 (Stretch Cluster를 제외하면) 모두 비동기다. 따라서 DR 클러스터는 프라이머리의 최신 메시지를 갖고 있지 않다. DR 클러스터가 얼마나 뒤처져 있는지 항상 모니터링해야 하고, 너무 뒤처지게 두어서는 안 된다. 그러나 바쁜 시스템이라면 DR 클러스터가 수백에서 수천 건 뒤처져 있는 것은 정상이다.
책의 계산을 보자. Kafka 클러스터가 초당 100만 건을 처리하고 프라이머리와 DR 사이의 랙이 5밀리초라면, 가장 좋은 경우에도 DR 클러스터는 5,000건 뒤처져 있다. 그러니 계획되지 않은 페일오버에는 어느 정도의 데이터 유실이 포함된다고 각오해야 한다. 계획된 페일오버라면 프라이머리 클러스터를 멈추고 미러링 프로세스가 남은 메시지를 다 옮길 때까지 기다린 뒤 애플리케이션을 넘기면 되므로 이 유실을 피할 수 있다.
예기치 않은 페일오버로 수천 건을 잃었을 때, 이 책 시점의 Kafka에는 트랜잭션 개념이 없다는 점도 문제가 된다. 여러 토픽에 걸친 이벤트들이 서로 관련되어 있다면(예: 판매 건과 그 라인 아이템), 일부는 페일오버 전에 DR에 도착하고 일부는 도착하지 못하는 상황이 생긴다. 애플리케이션은 페일오버 이후 '대응하는 판매 건 없는 라인 아이템'을 처리할 수 있어야 한다.
핵심 포인트
- 미러링은 비동기이므로 DR 클러스터는 항상 프라이머리보다 뒤처져 있다
- 초당 100만 건 + 5ms 랙이면 최선의 경우에도 5,000건 뒤처진 상태
- 계획된 페일오버는 프라이머리를 멈추고 미러링 완료를 기다리면 유실을 피할 수 있다
- 토픽 간 연관된 이벤트가 부분적으로만 도착할 수 있어, 애플리케이션이 불완전한 데이터를 감당해야 한다
방법 1·2 — auto offset reset과 오프셋 토픽 미러링
페일오버에서 가장 어려운 부분은 애플리케이션이 어디서부터 데이터를 소비할지 알게 하는 것이다. 책은 네 가지 접근을 제시한다.
Auto offset reset. Kafka 컨슈머에는 커밋된 오프셋이 없을 때 어떻게 동작할지에 대한 설정이 있다. 파티션의 처음부터 읽거나, 끝에서부터 읽는 것이다. 오프셋을 Zookeeper에 커밋하는 구버전 컨슈머를 쓰고 있고 DR 계획의 일부로 그 오프셋을 미러링하지 않는다면, 둘 중 하나를 골라야 한다. 처음부터 읽어 대량의 중복을 감수하거나, 끝으로 건너뛰어 알 수 없는(그리고 부디 적기를 바라는) 수의 이벤트를 놓치거나. 애플리케이션이 중복을 문제없이 처리하거나 약간의 데이터 누락이 큰 문제가 아니라면, 이 방법이 압도적으로 쉽다. 페일오버 시 토픽 끝으로 건너뛰는 것은 여전히 가장 널리 쓰이는 페일오버 방법이다.
Replicate offsets topic. 0.9 이상의 새 컨슈머는 오프셋을 __consumer_offsets라는 특수 토픽에 커밋한다. 이 토픽을 DR 클러스터로 미러링하면, 컨슈머가 DR에서 소비를 시작할 때 이전 오프셋을 집어 들고 멈춘 지점부터 이어 갈 수 있다. 단순하지만 주의사항이 길다.
첫째, 프라이머리의 오프셋이 세컨더리의 오프셋과 일치한다는 보장이 없다. 프라이머리에 데이터를 3일만 보관하는데 토픽 생성 일주일 뒤에 미러링을 시작했다고 하자. 프라이머리에서 사용 가능한 첫 오프셋은 57000000일 수 있지만(그 이전 4일치는 이미 삭제됨), DR 클러스터의 첫 오프셋은 0이다. 다음에 읽을 이벤트가 57000003이라고 믿는 컨슈머는 DR에서 그 오프셋을 읽지 못한다.
둘째, 토픽 생성과 동시에 미러링을 시작해 양쪽 모두 0에서 출발했더라도, 프로듀서 재시도 때문에 오프셋이 갈라질 수 있다. 요컨대 프라이머리와 DR 사이에서 오프셋을 보존해 주는 Kafka 미러링 솔루션은 존재하지 않는다.
셋째, 오프셋이 완벽히 보존되었다 해도, 프라이머리와 DR 사이의 랙 때문에, 그리고 이 시점의 Kafka에 트랜잭션이 없기 때문에, 컨슈머가 커밋한 오프셋이 해당 오프셋의 레코드보다 먼저 또는 나중에 도착할 수 있다. 페일오버한 컨슈머는 대응하는 레코드가 없는 커밋된 오프셋을 만날 수도 있고, DR의 최신 커밋 오프셋이 프라이머리의 최신 커밋 오프셋보다 오래된 것을 발견할 수도 있다.
이런 경우에는 중복을 감수해야 한다. DR의 최신 커밋 오프셋이 프라이머리보다 오래되었거나, 재시도 때문에 DR 쪽 레코드의 오프셋이 앞서 있는 경우 모두 그렇다. 또한 DR의 최신 커밋 오프셋에 대응하는 레코드가 없는 경우를 어떻게 처리할지도 정해야 한다. 토픽 처음부터 처리할 것인가, 끝으로 건너뛸 것인가?
한계가 있긴 하지만, 이 방법은 다른 접근보다 중복·누락 이벤트를 줄이면서도 구현이 단순하다는 장점이 있다.
핵심 포인트
- auto offset reset: 처음부터(대량 중복) 또는 끝에서(누락) — 가장 쉽고 여전히 가장 널리 쓰인다
- __consumer_offsets 미러링: 구현은 단순하나 오프셋 일치 보장이 없다
- 보관 기간 차이로 프라이머리 첫 오프셋이 57000000인데 DR은 0일 수 있다
- 프로듀서 재시도로도 오프셋이 갈라진다 — 오프셋을 보존하는 Kafka 미러링 솔루션은 존재하지 않는다
- 커밋 오프셋이 대응 레코드보다 앞서거나 뒤처져 도착할 수 있어 중복 감수가 불가피하다
방법 3·4 — 시간 기반 페일오버와 외부 오프셋 매핑
Time-based failover. 0.10.0 이상의 컨슈머를 쓴다면 각 메시지에 Kafka로 전송된 시각을 나타내는 타임스탬프가 들어 있다. 그리고 0.10.1.0 이상의 브로커는 타임스탬프로 오프셋을 조회하는 인덱스와 API를 제공한다. 따라서 DR로 페일오버할 때 장애가 새벽 4시 5분에 시작된 것을 안다면, 컨슈머에게 4시 3분부터 처리하라고 지시할 수 있다. 그 2분치는 중복되겠지만 다른 대안보다 낫고, 무엇보다 회사의 모든 사람에게 설명하기 쉽다. '4시 3분으로 되돌렸습니다'는 '최신 커밋 오프셋일 수도 아닐 수도 있는 지점으로 되돌렸습니다'보다 훨씬 낫다. 그래서 좋은 절충안인 경우가 많다.
남는 질문은 '컨슈머에게 4시 3분부터 처리하라고 어떻게 지시하는가'다. 한 가지 방법은 앱에 그 기능을 직접 넣는 것이다. 시작 시각을 사용자가 설정할 수 있게 하고, 설정되어 있으면 새 API로 그 시각의 오프셋을 가져와 seek한 뒤 평소처럼 커밋하며 소비하게 한다.
모든 애플리케이션을 미리 그렇게 작성해 두었다면 좋겠지만 그렇지 않다면? 타임스탬프를 받아 새 API로 그 시각의 오프셋을 조회하고, 특정 컨슈머 그룹에 대해 토픽·파티션 목록의 오프셋을 커밋해 주는 작은 도구를 만드는 것은 어렵지 않다. 책은 이런 도구가 Kafka에 추가되기를 바란다고 적고 있다(이후 kafka-consumer-groups.sh의 --reset-offsets --to-datetime으로 실현되었다). 중요한 것은 이 도구를 돌리는 동안 컨슈머 그룹이 멈춰 있어야 하고, 도구 실행 직후에 기동해야 한다는 점이다.
External offset mapping. 오프셋 토픽 미러링의 가장 큰 문제가 프라이머리와 DR의 오프셋이 갈라진다는 것이었으므로, 일부 조직은 Apache Cassandra 같은 외부 저장소에 두 클러스터 간 오프셋 매핑을 저장한다. 미러링 도구를 직접 만들어, DR 클러스터로 이벤트를 생산할 때마다 두 오프셋을 외부 저장소에 보낸다. 또는 두 오프셋의 차이가 변할 때만 저장한다. 예를 들어 프라이머리 495가 DR 500에 대응하면 (495,500)을 기록한다. 나중에 중복 때문에 차이가 바뀌어 596이 600에 대응하면 새 매핑 (596,600)을 기록한다. 495와 596 사이의 모든 매핑을 저장할 필요는 없다. 그 사이에는 차이가 그대로라고 가정하므로 프라이머리 550은 DR 555에 대응한다.
페일오버 시에는 (항상 약간 부정확한) 타임스탬프 대신 프라이머리 오프셋을 DR 오프셋으로 매핑해 사용한다. 컨슈머가 새 오프셋을 쓰게 만드는 방법은 앞의 두 기법 중 하나를 쓴다. 이 방법도 레코드보다 먼저 도착한 오프셋 커밋이나 제때 미러링되지 못한 오프셋 커밋 문제는 그대로 남지만, 일부 경우는 해결해 준다.
다만 책의 저자는 이 방법이 상당히 복잡하고 추가 노력을 들일 가치가 거의 없다고 본다. 시간 인덱스가 존재하지 않아 페일오버에 쓸 수 없던 시절의 유산이기 때문이다. 요즘이라면 오프셋 매핑을 만드는 수고 대신 클러스터를 업그레이드해 시간 기반 방법을 쓰는 편이 낫다.
핵심 포인트
- 메시지 타임스탬프는 0.10.0+, 타임스탬프→오프셋 조회 인덱스/API는 0.10.1.0+
- 시간 기반 페일오버는 중복 범위를 시간으로 설명할 수 있어 조직 내 커뮤니케이션에 유리하다
- 오프셋 커밋 도구를 돌리는 동안 컨슈머 그룹은 반드시 정지 상태여야 한다
- 외부 오프셋 매핑은 차이가 바뀌는 지점만 기록하면 되지만, 복잡도 대비 이득이 적어 권장되지 않는다
페일오버 이후와 클러스터 디스커버리
페일오버가 성공해 DR 클러스터에서 모든 것이 잘 돌아간다고 하자. 이제 원래 프라이머리 클러스터로 무언가를 해야 한다. 아마 그것을 DR로 바꾸는 것이다.
미러링 프로세스의 방향만 뒤집어 새 프라이머리에서 옛 프라이머리로 미러링하고 싶은 유혹이 있다. 그러나 두 가지 중요한 질문이 남는다.
첫째, 어디서부터 미러링을 시작할지 어떻게 아는가? 우리는 모든 컨슈머에 대해 풀어야 했던 문제를 미러링 애플리케이션 자체에 대해서도 풀어야 한다. 그리고 앞서 본 모든 해법이 중복이나 누락(대개는 둘 다)을 낳는다는 것을 기억하자.
둘째, 위에서 논한 이유로, 원래 프라이머리에는 DR 클러스터에 없는 이벤트들이 남아 있을 가능성이 높다. 새 데이터만 되돌려 미러링하면 그 여분의 이력이 그대로 남아 두 클러스터가 불일치하게 된다.
그래서 가장 단순한 해법은 원래 클러스터를 먼저 비우는 것(scrape)이다. 모든 데이터와 커밋된 오프셋을 삭제한 뒤, 새 프라이머리에서 이제 새 DR이 된 옛 클러스터로 미러링을 시작한다. 이렇게 하면 새 프라이머리와 동일한 깨끗한 상태에서 출발한다.
클러스터 디스커버리. 스탠바이 클러스터를 계획할 때 중요한 지점 하나는, 페일오버 시 애플리케이션이 페일오버 클러스터와 통신하는 법을 알아야 한다는 것이다. 프로듀서·컨슈머 프로퍼티에 프라이머리 브로커의 호스트명을 하드코딩해 두었다면 곤란해진다. 대부분의 조직은 단순하게 프라이머리 브로커를 가리키는 DNS 이름을 만들어 두고, 비상 시 그 DNS 이름을 스탠바이 클러스터로 돌린다.
디스커버리 서비스(DNS든 다른 것이든)가 모든 브로커를 포함할 필요는 없다. Kafka 클라이언트는 클러스터 메타데이터를 받아 나머지 브로커를 알아내기 위해 브로커 한 대에만 성공적으로 접속하면 되기 때문이다. 그래서 보통 세 대 정도만 넣어 두면 충분하다. 디스커버리 방식과 무관하게, 대부분의 페일오버 시나리오에서는 컨슈머 애플리케이션을 재기동해야 한다. 그래야 새로 시작할 오프셋을 찾을 수 있기 때문이다.
핵심 포인트
- 페일백 시 미러링 방향만 뒤집으면 시작 지점 문제와 옛 프라이머리의 여분 이력 문제가 남는다
- 가장 단순한 해법은 옛 프라이머리의 데이터와 커밋된 오프셋을 전부 지우고 새 프라이머리에서 다시 미러링하는 것
- 브로커 호스트명 하드코딩 대신 DNS 이름을 두고 비상 시 스탠바이로 전환한다
- 클라이언트는 브로커 한 대만 접속하면 나머지를 메타데이터로 알아내므로 디스커버리에 3대 정도면 충분하다
- 대부분의 페일오버는 컨슈머 애플리케이션 재기동(bouncing)을 필요로 한다
이 모듈과 연관된 문항 3개가 문제 은행에 있습니다.
이 내용으로 문제 풀어보기