고가용성을 향한 여정, Redis Sentinel과 Qdrant HA 전환에서 배운 것
이번 주에는 썩 귀중한 경험을 했다. 단일 파드 기반으로 구조화된 아키텍쳐를 HA 구성으로 마이그레이션하는 경험. 포트폴리오도 경험이라면 설계 단계에서부터 DB, 앱 등을 HA 구성 기준으로 만든 적은 있다. 하지만 단일 Redis, Qdrant 전제로 설계된 아키텍쳐에서 리소스를 HA 구성으로 전환하는 건 이번이 처음이다. 단순히 리소스만 HA 구성으로 전환해서 끝날 문제일까? 역시나, 아니었다. 설계부터가 단일 파드를 전제하다보니 이런저런 문제가 따라오기 시작했다.


일단 Redis부터 말하자면 standanlone 아키텍쳐를 replication으로 전환하는 건 그다지 어려울 게 없었다. 메니페스트를 replication으로 바꾸고 파드를 다시 띄우면 끝날 일이니깐. 이렇게 해서 문제가 깔끔하게 해결됐다면 정말로 좋았겠지만…

안타깝게도 그렇게 해결될 건 아니었고, 띄우자마자 어플리케이션이 고장나버렸다. 무슨 에러가 발생했냐면, 말그대로다: ReadOnly: You can’t write against a read only replica.
간단히 말해, 지금 레디스 파드는 마스터 파드만 Write가 가능하며 그 이외 슬레이브 파드들은 읽기 전용 파드로 동작하는 상태였다. Master(쓰기 가능) – Slave(읽기 전용) – Slave(읽기 전용). 그런데 단일 파드를 전제하는 기존 코드에서 데이터를 작성하는 요청을 보낼 때 세 파드 중 아무데나 보내다 보니 에러가 나고있는 거다.
이 문제는 어떻게 해결해야 할까?
1차적으로 생각할만한 해결책은 코드 상에서 작성용 마스터 파드를 호출하는 순간과 기타 읽기 전용 파드도 괜찮은 순간을 분기 처리하는 거다. 이것은 고가용성 뿐만이 아니라 사이트를 읽는 트래픽이 몰리는 상황에서 ‘읽기 전용’의 이점까지 누리고자 한다면 가장 좋은 선택지일 수 있다.
하지만 안타깝게도 내 입장에서는 이 선택지를 취할 수가 없었다. 어째서?
앞서 언급했다시피 문제는 설계 단계서부터 애당초 단일 파드를 전제로 코드가 설계되었다는데 있었다. 조금 더 명확히 말해, 단일 레디스 파드에 세션 데이터를 작성하고, 곧바로 저장된 데이터를 꺼내온다는 걸 전제로 작성된 코드가 있었다.
데이터 저장 → 레디스 파드1 → 곧바로 데이터 불러오기
이렇게 말이다. 구성이 고가용성으로 바뀌면 이 코드가 정상적으로 동작할 수가 없다. 파드1에 작성한 데이터가 파드2, 파드3에 (비동기로) 동기화되는데 걸리는 시간이 이후 데이터를 불러오는 시간이 늦을 수 있기 때문이다.
데이터 저장 → 레디스 파드1
레디스 파드2 ← 데이터 불러오기 시도 → 어… 데이터가 없네?
이렇게. 그래서 나는 코드상의 수정으로 ‘읽기 전용’ 파드의 이점을 누리는 건 포기하기로 했다. 목적은 HA 구성이었으니깐.
그렇다면 다른 해결책이 뭐가 있을까? 내가 찾아본 다른 해결책은 HAProxy를 사용하는 것이었다.
HAProxy를 사용하는 원리는 Redis 서비스 호출시 HAProxy를 경유하도록 트래픽을 중계하는데 있다.
클라이언트 (앱 파드)
│
│ redis:6379 호출 (Service DNS)
▼
Service: redis (ClusterIP)
│
│ 셀렉터: app=redis-haproxy
▼
Deployment: redis-haproxy
│
│ HAProxy가 haproxy.cfg에 따라 실제 Redis 파드로 프록시
▼
실제 Redis 파드 (마스터/슬레이브 판별 후 전달)
HAProxy는 정기적으로 실제 Redis 파드를 헬스 체크하여 3개의 파드 중 어떤 것이 작성이 가능한 마스터 파드인지를 체크한다. 그리고 Redis 서비스로 들어오는 트래픽을 마스터 파드로 리버스 프록시한다. 덕택에 기존 마스터 파드가 내려가면 자동으로 새로운 마스터 파드로 트래픽을 쏘게 된다.
비록 읽기 전용 파드의 이점을 살릴 수는 없지만, 전자의 방법을 사용할 수 없는 시점에서 문제를 해결할 가장 적절한 방안이라고 판단했다. 그래서 실제 현장에서는 ReadOnly Fail 문제를 이 방안으로 해결했다.