TIL

2025.05.04 재고 트래픽과 캐시 2

Gisungcu 2025. 5. 3. 22:55

 

 

 

트래픽에 대해서 생각을 하다가 몇 가지 궁금한 점이 생겼습니다.

 

1. 레디스 센티넬로 고가용성을 높였을 때, 슬레이브에 복제 전에 마스터가 죽을 경우는?

2. 레디스와 카프카 메시지 발행의 트랜잭션은 어떻게 해야 할까?

3. 레디스 캐시를 사용할 때, 캐시 업데이트는 언제 해야 할까?

 

 


어제 그린 마인드맵 아래쪽에 더 몇 가지를 추가했습니다. 

 

1. 레디스 센티넬로 고가용성을 높였을 때, 슬레이브에 복제 전에 마스터가 죽을 경우는?

레디스에서 이런 상황을 막기 위해 설정들이 존재하더군요.

min_replicas_to_write: 이 설정은 마스터가 쓰기 전, 슬레이브 레디스가 실행 중인지 확인하는 설정입니다. 슬레이브가 설정 값 보다 적게 살아있다면 쓰기를 하지 않는 것이지요.

min_replicas_max_lag: 슬레이브와의 replicas lag에 대한 설정입니다. 이게 넘어가면 쓰기를 막겠다는 것입니다.

min_replicas_to_write이 확인되어야 쓰기가 되니 복제 관점에서는 유실 가능성을 완화할 수 있다고 생각합니다.

다만 위 설정을 다 통과하였어도 복제가 비동기이기 때문에 슬레이브에 복제 전에 죽었을 경우는 문제가 될 수 있습니다.

이건 완벽히 통제가 불가능한 장애이므로 이후에 발행하는 카프카, DB커밋등과 모니터링을 통해 유실된 정보를 알 수 있어야 할 것 같습니다.

 

 

2. 레디스와 카프카 메시지 발행의 트랜잭션은 어떻게 해야 할까?

재고 계산 시 레디스에 업데이트하고 카프카로 DB 적재용 이벤트를 발행할 때 문제가 될 수 있는 부분은 어디일까요?

 

레디스가 먼저 장애 났을 때? 

이때는 레디스 업데이트가 불가능하니 카프카 메시지가 발행될 일이 없습니다.

혹은 1번에서 말한 완벽한 통제가 불가능한, 마스터에 쓰기 후 비동기 복제 전 죽는 경우는 이후에 발행될 카프카 메시지와 비교해 유실된 정보를 파악할 수 있어야 합니다.

 

카프카 메시지 발행 실패

카프카 발행 실패는 많이 알려진 방법으로 reties설정과 delivery_timeout으로 처리, 최종적으로 프로듀서 DLT나 DB에 메시지를 적재하면 됩니다.

이후에 스케줄러가 조회해 재발행하는 것이지요.

만약에 레디스가 모두 죽어서 DB의 데이터로 웜업을 해야 한다면 DLT, 실패 이벤트 DB에 값이 모두 처리되었는지 확인하고 웜업을 해야겠습니다.

 

만약 카프카 이벤트 발행 전에 애플리케이션이 죽는다면?

레디스 처리 -> 카프카 메시지 발행 순으로 코드를 작성했을 경우, 카프카 메시지 발행 전에 애플리케이션이 죽었을 경우는 어찌해야 할까요?

그레이스 풀 셧다운 설정이 있기는 합니다.

스프링에서는 timeout-per-shutdown-phase 설정으로 시간까지 설정으로 줄 수 있습니다.

설정 시간까지 카프카 메시지가 발행, 또 전체적인 메서드의 라이프사이클이 더 넘어가지 않는다면 발행은 보장되지 않을까 싶습니다.

만약 앞에서 응답지연으로 레디스 처리가 늦어지고 딱 카프카 메시지 발행 전에 애플리케이션이 죽을 수도 있습니다.

이때는 오히려 레디스를 기반으로 카프카 발행 유실된 것을 모니터링해야겠네요.

 

혹은 아예 메시지 발행으로 변경해서 레디스 재고 차감용 메시지와 DB차감용 메시지로 분리할 수 있습니다.

이 둘을 카프카 트랜잭션으로 묶고 카프카 메시지 발행은 보장하는 것이지요.

다만 처음 요구사항이었던 재고 차감이 실시간이 아닌 eventually consistency가 되기 때문에 재고가 없어 결제를 취소해야 하는 경우도 있을 수 있습니다.

 

일관성, 빠른 응답을 같이 가져가기는 힘드니, 장애 시 원복 및 누락된 주문, 이벤트를 알 수 있도록 시스템을 갖추어야 할 것입니다.

예를 들어 주문번호를 저장해 레디스와 DB의 일관성을 맞추는 데 사용한다던지.

그레이스 풀 셧다운이 어느 정도 해결한다지만 이런 절차는 필요해 보입니다.

 

 

  • graceful shutdown kafka redis consistency problem

 

 

3. 레디스 캐시를 사용할 때, 캐시 업데이트는 언제 해야 할까?

 

 

이번엔 재고용 레디스 캐시가 아닌, 조회용 캐시로 사용할 때 일관성을 어떻게 지켜야 할지에 대해서 생각해 봤습니다.

DB 데이터 변경이 이뤄질 때 캐시도 업데이트해야 합니다.

그럼 DB 커밋 전에 업데이트하던지 커밋 후에 하던지 두 선택지가 있습니다.

커밋전에 캐시 업데이트를 한다고 하면 DB롤백 시 캐시를 다시 롤백해야 합니다. 그전에 다른 요청이 조회했다면 문제가 더 커질 수 있습니다.

커밋 후에 업데이트한다고 하면 DB커밋과 캐시 업데이트 사이에 조회가 이뤄질 수 있습니다.

다르게 생각해 보겠습니다. 캐시 업데이트를 동기적으로 하게 되면 메서드의 라이프사이클이 끝나기 전이니 그 찰나는 괜찮을 수 있다고 생각합니다.

기술적으로 다른 서드파티의 커밋을 제어하는 것은 복잡합니다.

 

또 생각해봐야 하는 게 캐시 업데이트나 eviction이 실패할 경우 오래된 캐시를 읽을 가능성이 올라갑니다.

토스의 사례에서는 eviction실패 시 서킷브레이커를 통해 DB조회하도록 구현했다고 합니다.

더 발전한다면 실패한 key 조회 시만 DB를 조회하도록도 할 수 있을 거 같습니다.