차단 등 검증 로직의 위치
WhatsApp 채팅 아키텍처 설계에 대한 질문입니다. 영상에서는 참가자가 ws 서버에 메시지를 보내면 바로 Redis pub/sub으로 들어가고, 람다나 Stream을 통해 DB로 저장하는 방식을 설명하고 있습니다.
하지만 DB 저장에 앞서 채팅방 참가 여부 검증, 메시지 전송 차단/해제, 구독자만 전송 가능 등의 검증(validation)이 필요한 경우가 있을 것 같습니다. 또한 이 경우 사용자에게 메시지 전송 실패/불가라는 즉각적인 피드백도 제공해줘야 할 것입니다. ws 서버에서는 보통 검증 로직은 담당하지 않는 것으로 알고 있는데, 이 경우 어디에 검증 로직을 넣는 게 적당할까요?
답변 2
0
안녕하세요. 한재현님,
좋은 질문해주셔서 감사합니다
말씀하신 것처럼 실제 채팅 시스템에서는 메시지를 Redis pub/sub이나 Stream으로 넘기기 전에 검증 단계가 필요합니다.
질문 주신 부분은 앞단의 message acceptance / validation layer에 해당합니다.
보통은 검증을 한 곳에만 두기보다 역할을 나눕니다.
첫 번째로, WebSocket 서버 또는 API Gateway 근처에서 즉시 판단 가능한 검증을 합니다.
사용자가 바로 피드백을 받아야 하기 때문에, Redis나 DB 저장 파이프라인으로 넘기기 전에 처리하는 것이 좋습니다.
두 번째로, DB 저장 직전 또는 메시지 서비스 계층에서도 최종 검증을 한 번 더 하는 경우가 많습니다.
이유는 WebSocket 서버는 여러 대일 수 있고, 네트워크 지연이나 상태 변경 때문에 앞단 검증 시점과 실제 저장 시점 사이에 조건이 바뀔 수 있기 때문입니다.
예를 들어 사용자가 메시지를 보내는 순간에는 채팅방 멤버였지만, 처리되기 전에 방에서 제거될 수도 있습니다. 또는 차단 상태가 바뀔 수도 있어서, 중요한 정합성 검증이 보통 한 번 더 필요할 수 있습니다.
따라서, 검증 로직은 Redis pub/sub이나 DB 저장 이후가 아니라 그 전에 위치하는 것이 맞습니다.
다만 모든 검증을 WebSocket 서버에 몰아넣기보다는, WebSocket 서버는 빠른 1차 검증을 담당하고, 메시지 서비스 또는 DB 트랜잭션 계층에서 최종 정합성 검증을 한 번 더 수행하는 구조가 더 적합할 수 있음을 알려드립니다.
하지만, 매번 DB를 조회하면 성능이 저하될 수 있으므로, ”차단 리스트“ 와 같은 검증을 Redis에 캐싱하여 1차 검증 시에도 어느 정도 정교한 비즈니스 검증을 수행할 수 있음을 참고해주시면 좋습니다
감사합니다.
좋은 하루 되세요 ☺
0
답변 감사합니다. 사용자 응답을 위한 권한 확인, 실제 저장을 위한 권한 확인 두 번의 확인이 필요하며, websocket 서버가 검증 책임을 가질 수 있다고 이해했습니다.
하지만 캐시는 고민이 됩니다. 참가 여부와 차단 여부를 ws 서버에서 검증하려면 ws 서버의 부하 특성 상 캐시를 사용하는 것이 커넥션 고갈 방지와 처리량을 위해 필수적이라고 보여집니다. 그런데 캐시는 네트워크 파티션이나 접속 장애 등 상황에서 일관성을 보장하기 어렵다고 알고 있습니다.
A가 B를 API 서버를 통해 차단하는 경우를 예로 들겠습니다.
DB에는 차단 기록 커밋 후 Redis 캐시에 invalidate 또는 write-through가 실패
메시지 큐를 통해서 BlockedEvent를 발행한다면, 캐시에 쓰는 소비자와 Redis 간 파티션 등이나 접속 장애로 쓰기 실패
이 경우에 사용자는 API 서버로부터 차단 요청에 성공 응답을 받지만, ws 서버가 실제로 검증에 사용하는 Redis에는 반영되지 않은 상태가 됩니다. 특히 네트워크 장애나 파티셔닝은 언제 복구될지 모르기 때문에 더 위험하다고 생각됩니다. 채팅에서 사용자는 차단 시 거의 즉시 차단되는 것을 기대할 것이기에 캐시 반영 지연은 서비스에서 허용 불가할 것으로 생각됩니다.
그렇다면
Redis 캐시를 사용하면서 일관성을 보장하려면 어떻게 해야하는지
강한 일관성을 위해 DB를 사용하는 것은 성능 상 불가능에 가깝다고 봐야하는지
일반적으로는 invalidation 정도만으로 타협을 해도 괜찮은건지
를 추가로 여쭙고 싶습니다. 감사합니다.
1
안녕하세요. 한재현님,
추가 질문 감사합니다. 아주 좋은 질문을 해주셨습니다.
말씀해주신 우려에 동의하는 부분 입니다.
차단 여부나 채팅방 참가 여부를 Redis에 캐싱하면 WebSocket 서버가 빠르게 검증할 수 있다는 장점이 있습니다. 하지만 Redis 캐시는 결국 DB의 복사본에 가깝기 때문에, 말씀해주신 것처럼, DB와 항상 완전히 같은 상태인 일관성을 보장하기는 어렵습니다.
예를 들어, 아래처럼 A가 B를 차단하는 경우를 생각해보면,
1. A가 B를 차단한다.
2. DB에는 차단 기록이 정상적으로 저장된다.
3. 그런데 Redis 캐시 갱신 또는 invalidate가 실패한다.
4. WebSocket 서버는 아직 이전 Redis 값을 보고 “차단되지 않았다”고 판단할 수 있다.
5. 그러면 B의 메시지가 잠깐 허용될 수 있다.
이러한 경우가 캐시를 사용할 때 생기는 대표적인 정합성 문제입니다.
따라서 Redis를 “진짜 원본 데이터”로 보면 안 되고,
보통은 아래처럼 책임을 나누는 것이 좋습니다.
DB / 권한 서비스 → 원본 데이터
Redis → 빠른 조회를 위한 캐시
WebSocket 서버 → Redis를 보고 빠르게 1차 검증
즉, Redis는 성능을 위해 사용하는 1차 검증 레이어이고, 실제 권한과 차단 상태의 기준은 DB나 권한 서비스에 있어야 합니다.
실제로 공개된 빅테크 사례를 봐도 비슷한 방향을 확인할 수 있습니다.
제가 빅테크 사례를 알아보니, Meta의 Memcache 관련 아티클에서 Meta도 캐시를 “원본 데이터”로 보지 않고, DB의 변경을 캐시에 전파하는 구조를 사용하고 있습니다.
그리고 캐시 불일치는 사용자 입장에서 데이터 유실처럼 보일 수 있기 때문에, Polaris라는 별도 시스템으로 캐시가 DB와 일치하는지 계속 측정하고 추적하여 이 문제를 해결하고 있음을 알 수 있습니다. 그래서 다른 방안으로는 캐시 불일치를 측정하고 복구하는 별도 시스템도 고려해보는 것도 좋은 방법으로 생각됩니다.
다른 사례를 보면, Uber도 비슷합니다. Uber의 Docstore 통합 캐시 사례를 보면, 캐시 invalidation만으로는 기본적으로 eventual consistency가 됩니다.
그래서 읽기-쓰기 직후 일관성이 더 중요한 경우에는 write가 끝난 뒤 해당 캐시 row를 명시적으로 invalidate하는 API를 따로 두었다고 설명합니다. 즉, 일반적인 캐시 경로와 더 강한 정합성이 필요한 경로를 분리하는 것 입니다. 강한 정합성이 필요한 요청은 캐시를 우회하거나 별도 경로로 처리해야 한다는 의미입니다.
그래서 아래와 같이 합리적인 설계방안으로 하는 것을 고려해야 합니다.
1. WebSocket 서버에서 Redis를 보고 빠르게 1차 검증한다.
2. 메시지 서버에서 중요한 권한을 한 번 더 확인한다.
3. DB 저장 직전에도 필요한 경우 최종 검증을 한다.
4. 차단 이벤트는 유실되지 않게 전파한다.
5. 캐시 상태가 불확실하면 메시지를 허용하기보다 거절하거나 보류한다.
여기서 차단 이벤트의 경우에는 DB에 차단 기록을 저장할 때 “이 차단 사실을 다른 시스템에도 알려야 한다”는 기록을 DB에 같이 남겨두는 방식입니다.
DB transaction 안에서 같이 저장
1. A가 B를 차단했다.
2. 이 차단 이벤트를 Redis / WebSocket 서버 / 메시지 서버에 알려야 한다.
이렇게 하면 Redis 갱신이나 이벤트 발행이 일시적으로 실패하더라도, DB에 남아 있는 미처리 이벤트를 보고 나중에 다시 시도할 수 있습니다.
DB에 차단 기록 저장 → BlockedEvent 저장
→ 별도 worker가 Redis 갱신 / 이벤트 발행 시도
→ 실패하면 재시도 → 성공하면 처리 완료
하지만 위와 같이 설계한다고 해서 Redis가 항상 즉시 100% 최신 상태가 된다는 뜻은 아닙니다. DB와 Redis는 서로 다른 시스템이기 때문에 아주 짧은 순간이라도 상태가 어긋날 수 있고, 네트워크 장애가 길어지면 그 차이도 길어질 수 있습니다.
그래서 질문 주신 “그럼 강한 일관성을 위해 DB를 사용하는 것은 성능 상 불가능에 가깝다고 봐야하는지” 부분에 대해서는, 무조건 불가능하다고 볼 수는 없지만, 대규모 채팅 시스템에서는 모든 메시지마다 DB를 직접 조회하는 방식은 보통 부담이 큽니다.
메시지 하나를 보낼 때마다 매번 아래 정보를 DB에서 확인하면 트래픽이 많아질수록 DB가 병목이 될 수 있습니다. 그래서 DB 의 병목을 해결하기 위해 아래와 같이 설계하는 것을 권장드릴 수 있습니다.
일반적인 메시지 전송 → Redis 캐시로 빠르게 1차 검증
캐시 miss / Redis 장애 / 상태가 불확실한 경우 → DB 또는 권한 서비스로 fallback 처리
차단 직후처럼 민감한 상태 → 더 강하게 검증하거나, 불확실하면 메시지를 막음
DB 저장 직전 → 메시지 서비스에서 최종 검증
또 하나 중요한 기준은 데이터의 성격입니다.
온라인 상태, 입력 중 표시, 읽음 여부처럼 잠깐 늦게 반영되어도 되는 정보는 eventual consistency 최종 일관성으로 타협할 수 있습니다.
반면 차단 여부, 채팅방 참가 권한, 유료 구독자 권한, 관리자 권한처럼 잘못 허용되면 문제가 큰 정보는 단순 Redis invalidation만으로는 부족할 수 있습니다.
정리하면 다음과 같습니다.
Redis 캐시만으로 차단/권한 검증의 강한 일관성을 보장하기는 어렵습니다. 하지만 모든 메시지마다 DB를 조회하는 것도 대규모 채팅 시스템에서는 부담이 큽니다.
그래서 Redis는 빠른 1차 검증에 사용하고, DB 또는 권한 서비스는 원본 데이터로 유지하며, 차단처럼 중요한 기능은 여러 단계에서 한 번 더 검증하는 구조가 더 합리적인 시스템이 될 것으로 생각합니다.
그래서 차단, 권한, 구독 여부처럼 잘못 허용되면 문제가 큰 데이터는 캐시만 믿지 않고, 캐시 갱신 실패를 대비한 재시도 구조와 최종 검증 레이어를 함께 두는 것을 권장 해드리고 싶습니다.
다른 질문 사항이 있으시면 언제든지 남겨주세요!
감사합니다.
좋은 밤 되시고, 즐거운 한 주가 되시길 바랍니다.
0
상세한 답변 감사합니다! 크게 다음과 같이 이해했습니다.
Transactional Outbox 패턴이나 Change Data Capture (CDC) 등의 메커니즘을 통해 DB 변화와 다른 시스템(e.g. 캐시) 간 eventual consistency는 보장할 수 있으나 네트워크 장애 시 수렴 속도는 통제할 수 없다.
WebSocket 서버에서는 캐시를 이용해 일차적으로만 검증하며, 실제로 메시지를 저장하거나 전달하는 등 강한 일관성이 필요하다면 DB를 참조한다.
다만 말씀해주신 내용을 제가 고민하고 있던 시스템에 어떻게 적용해야 할지가 아직 명확치 않아 추가로 질문드립니다. 구체적인 예시를 통해 설명드리겠습니다. 대략적으로는 푸시 알림이 없는 실시간 채팅 및 이전 메시지 조회 가능 시스템이라고 보시면 됩니다.
시스템 동작과 요구사항
사용자가 메시지를 전송하면 채팅방 내 mute 여부를 확인 후 처리한다.
mute된 사용자는 메시지를 전송할 수 없다.메시지의 처리는 다른 접속 사용자에게 실시간 전달 + DB 저장이다.
시스템에는 수 천개의 채팅방이 존재하며, 각 채팅방의 실시간 접속 사용자는 10만 명 까지 늘어날 수 있다. 메시지 전송 트래픽은 평균 2000 rps 이상으로 가정한다. (핵심은 모두 DB 조회 시 병목 발생하는 시나리오)
초기 아키텍처
처음에는 다음 아키텍처를 생각했습니다.

고민
위 아키텍처를 구상하면서 다음 고민이 생겼습니다.
메시지 전송 시 WebSocket이 Redis를 통해 무엇을, 어느 수준에서 검증해야 하는가?
메시지 전송 시 Worker에서 추가적으로 검증해야 하나? DB 저장 효율성을 위해 batch로 처리할 수는 없나?
mute 처리 시 캐시 반영을 비동기로 전파해도 되나? 네트워크 장애로 Worker와 Redis 간 연결이 안 되면, WebSocket 서버는 계속 stale cache를 사용하게 되는 거 아닌가?
WebSocket이 Redis만 보고 검증한다면 pub/sub에 직접 발행하기에 stale cache 적용 시 차단이 안 된 것처럼 보일 것이고, DB로 검증한다면 커넥션 고갈과 병목이 발생할 것 같아 두 방법 모두 불가능해 보이는 것이 가장 큰 고민인 것 같습니다.
기존 아키텍처를 어떻게 수정해야 요구사항을 만족할 수 있을지 조언을 듣고 싶습니다.
감사합니다.
0
안녕하세요. 한재현 님,
좋은 질문 감사합니다.
답변이 늦어 죄송합니다. 제가 현재 최근 신규 강의 제작과 서비스 운영등으로 이번달은 매우 바쁜 상태임을 양해 부탁드립니다.
우선, 공유해주신 다이어그램과 3가지 고민은 대규모 실시간 시스템을 설계할 때의 DB 병목 과 캐시 정합성에 대한 트레이드 오프입니다.
WebSocket이 바로 Fan-out(pub)을 해버리면 Stale Cache 문제가 생기고, DB를 다녀오자니 병목이 생기는 상태로, 솔루션은 "메시지 수락" 에 대한 파이프라인과 "메시지 확정 및 전파" 파이프라인을 물리적 또는 논리적으로 분리하는 것입니다.
1. WebSocket과 Redis의 1차 검증
다이어그램을 보면 WebSocket에서 Redis를 확인하고 바로 pub을 날리는 구조로 보입니다. 이 흐름은 끊어내고 WebSocket은 가볍고 빠른 1차 필터링만 담당해야 하는 것으로 권장드리고 싶습니다.
Rate Limit, 사용자 인증, 그리고 "명확하게 차단(mute)된 상태"인지 확인한 다음,
Redis에 mute: true가 확실히 캐싱되어 있다면 즉시 전송을 차단하고 사용자에게 에러를 반환합니다. 하지만 캐시가 없거나(Miss), 상태가 불확실하다면 WebSocket이 직접 Fan-out을 하지 않고 다음 단계(검증 파이프라인)로 메시지를 넘깁니다.
2. Worker에서의 추가 검증 및 DB Batch 처리
결론부터 말씀드리면 DB 저장은 Batch로 치는 게 맞습니다. 단, 다른 유저들에게 뿌려지는(Fan-out) 시점보다 '최종 검증'이 먼저 끝나야 해요. 지금 Worker 하나가 검증도 하고 DB 저장도 하려다 보니 꼬이는 건데, 이걸 좀 나눠보면 어떨까 싶습니다.
DB 저장은 Batch로 처리하되, 최종 검증은 Fan-out(다른 사용자에게 전달) 전에 반드시 끝나야 합니다.
현재 고민하시는 부분은 Worker 하나가 검증과 DB 저장을 모두 하려다 보니 생기는 문제이기 때문에, Worker의 역할을 분리하면 Batch 처리의 이점을 살리면서 검증도 할 수 있을 것으로 기대를 할 수 있습니다.
Validation Worker (메시지 서비스)
Kafka에서 1차 통과된 걸 꺼내서 DB나 권한 캐시로 확실하게 최종 검증(Mute 여부 등)을 합니다. 그리고, 통과된 메시지만 '검증 완료 Kafka 토픽'으로 다시 발행하도록 하는 것 입니다.
DB Save Worker
검증 끝난 토픽 '검증 완료 토픽'에서 메시지를 읽어 DB에 Batch 단위로 저장합니다. (병목 해결)
Fan-out Worker
똑같이 '검증 완료 토픽'에서 메시지를 읽어 다른 접속자들에게 실시간으로 뿌려줍니다. (정합성 보장)
3. Mute 비동기 전파와 Stale Cache 극복 방안
Mute 처리를 Outbox + Kafka를 통해 비동기로 전파하는 설계(우측 다이어그램)는 아주 합리적이고 훌륭한 방안으로 생각이 듭니다. 하지만, 말씀하신 대로 네트워크 장애 시 Redis가 갱신되지 않아 WebSocket이 Stale Cache를 믿어버리는 문제가 남습니다.
이를 해결하기 위해 최근 상태 변경 플래그 Recent Policy Change Flag 기법을 활용하시는 것을 권장드리고 싶습니다.
쉽게 말씀드려보면, 메인 권한 캐시와는 별도로 상태가 방금 변경되었음을 알리는 매우 가벼운 마커(Marker) 데이터를 Redis에 삽입하는 것 입니다.
예를 들어, Mute API 호출 시에는
Mute API 호출 시 DB 저장과 함께 API 서버는 비동기 이벤트 발행과 별개로, Redis에 즉시
recent_policy_change:{roomId} = true라는 Key-Value를 설정합니다. 이때 TTL(Time-To-Live)을 30초로 짧게 둡니다.
메시지 전송 시 (Read Path) 에는
WebSocket 서버가 메시지를 수신하면 가장 먼저 Redis에서 recent_policy_change:{roomId} 키의 존재 여부를 확인합니다.
조건 A, 플래그가 존재할 때 (Mute 직후 30초 이내)
WebSocket 서버는 "현재 이 방의 메인 권한 캐시는 비동기 갱신 중이라 신뢰할 수 없다(Stale 상태일 수 있다)"고 기술적으로 판단합니다. 따라서 캐시 조회를 건너뛰고(Bypass), 메시지를 바로 Kafka로 넘겨 Validation Worker가 DB를 직접 조회하도록(강한 정합성 보장) 라우팅합니다.
조건 B, 플래그가 없을 때 (평상시 또는 30초 이후)
30초가 지나 TTL이 만료되어 해당 키가 삭제되었다면, WebSocket 서버는 "비동기 캐시 갱신이 이미 완료되어 현재 캐시가 최신 상태(Eventually Consistent 상태에 도달)임이 보장된다"고 판단합니다. 이때는 기존처럼 Redis 캐시만 보고 DB 접근 없이 빠르게 메시지를 처리합니다.
이렇게 하면 평소에는 Redis 캐시를 믿고 DB 병목 없이 빠르게 처리하면서도, Mute가 발생한 직후의 위험한 구간에서는 강한 정합성을 지킬 수 있습니다.
따라서, 기존 아키텍처를 아래처럼 조금만 다듬으시면 요구사항을 충분히 만족하실 수 있을 겁니다
[1차 검증]
클라이언트 전송 → WebSocket 서버 (Redis 빠른 검증, 명확한 Mute는 여기서 차단)
[메시지 큐]
1차 통과된 메시지 → Kafka (Raw Topic)
[최종 검증]
Validation Worker 가 Raw Topic 소비 → 강한 검증(DB/캐시) → 통과된 메시지만 Kafka (Validated Topic) 에 발행
[병렬 처리]
DB Worker
Validated Topic 소비 → DB Batch 저장 (효율성 극대화)
Fan-out Worker
Validated Topic 소비 → 다른 사용자들의 WebSocket 세션으로 메시지 전파 (안전한 Fan-out)
이렇게 설계하면 모든 메시지가 DB 커넥션을 맺지 않아도 되며, 차단된 유저의 메시지가 다른 사람에게 전달되는 일 즉, Stale Cache 문제도 막을 수 있을 것으로 생각됩니다.
제안해 드린 파이프라인 분리 구조에서, 만약 Validation Worker 단계에서 최종적으로 'Mute 상태'임이 판명되어 메시지가 드랍된다면, 처음 메시지를 보낸 사용자(Client)에게 "전송 실패" 피드백을 비동기로 어떻게 돌려주는 것이 좋을지도 고려해보신다면, 더 완벽한 설계가 될 것 같습니다.
감사합니다.
좋은 하루 되세요!
0
죄송하지 않으셔도 됩니다! 어려운 문제에 관해 같이 논의할 수 있는 분이 있다는 것만으로도 감사합니다.
말씀해주신 내용을 종합하여 메시지 저장 시 검증 흐름은 정리가 되었습니다. 하지만 캐시 일관성은 아직 고민됩니다.
최근 상태 변경 플래그를 사용하면 mute/unmute 이벤트 전파 지연 동안에도 정합성을 지킬 수 있을 것 같습니다. 하지만 이 경우에도 네트워크 장애가 플래그의 TTL 보다 길어지는 경우에는 정합성의 보장이 어려울 것 같습니다. 예를 들어, 워커 서버의 네트워크 인터페이스가 죽거나 인터넷에서 파티셔닝이 발생한다면 캐시가 언제 갱신될지 모르는 상태가 되지만, 플래그의 TTL은 만료되어 WebSocket 서버는 정상 상황으로 볼 것입니다. 이 상태에서 Redis를 보고 검증한다면 차단된 사용자가 차단 안 된 상태로 보일 것입니다. (참고로 차단 안 됨도 Redis에 캐시하는 것으로 생각했습니다. 대부분이 차단 안 된 상태이므로 이를 캐싱하지 않는다면 병목이 줄어들지 않을 것으로 생각했습니다.) WebSocket 서버가 이 상황을 알고 반응할 수 있다면 괜찮을 수도 있을 것 같지만, 이게 가능한지는 모르겠습니다.
요약하면 다음과 같습니다.
최근 상태 변경 플래그의 TTL 내에 캐시 갱신 워커와 Redis 간 장애가 복구되지 않는다면 일관성을 어떻게 보장해야 할까?
앞서 말씀해주신 것처럼 별도의 캐시 동기화 시스템을 사용하는 수밖에 없는 걸까요? 하지만 이 경우에도 수렴 속도에 대한 보장은 할 수 없지 않을까요?
결국은 캐시 갱신 책임을 어디에 두던 네트워크 문제로 일반적으로 기대하는 시간 내에 갱신하지 못 할 수 있다는 점이 고민의 근원인 것 같습니다.
별개로 mute/unmute 시에 상태 변경 플래그를 수정한다면, API 서버가 DB에는 저장 성공하지만 Redis의 최근 상태 변경 플래그 쓰기에는 실패할 수 있을 것 같습니다. 이런 경우에는 주로 어떤 응답을 주며, 전체 일관성은 무엇에 맞추는 게 일반적인가요?
감사합니다!!
1
안녕하세요. 한재현님,
좋은 추가 질문을 해주셨습니다.
말씀해주신 것처럼 recent_policy_change 같은 TTL 기반 플래그는 짧은 전파 지연을 줄이는 데는 도움이 되지만, 캐시 갱신 워커와 Redis 사이의 장애가 TTL보다 오래 지속되는 경우까지 완전히 해결하기는 어렵습니다.
예를 들면, 아래와 같은 상황이 생길 수 있습니다.
mute 상태는 DB에 정상 저장됨
그런데 캐시 갱신 워커와 Redis 사이에 장애가 발생함
Redis에는 아직 예전 값이 남아 있음
recent_policy_change 플래그는 TTL이 지나서 사라짐
WebSocket 서버는 Redis 값을 정상 캐시처럼 신뢰함
결과적으로 mute된 사용자가 mute되지 않은 것처럼 보일 수 있음
따라서 recent_policy_change 플래그는 보조 장치일 뿐이고, 이것만으로 장기 장애 상황의 정합성을 보장하기는 어렵습니다.
그렇기 때문에 이 문제는 “캐시를 항상 최신으로 만들 수 있느냐”보다는, 캐시가 지금 신뢰 가능한 상태인지를 시스템이 판단할 수 있느냐를 따져보는 것이 더 적절합니다.
분산 시스템에서 일부 노드나 서비스들이 서로 통신할 수 없는 상태, 즉 네트워크 파티션 또는 캐시 갱신 경로 장애가 발생하면 아래 세 가지를 동시에 만족시키기는 어렵다고 볼 수 있습니다.
항상 최신 mute 상태를 본다.
항상 메시지 전송을 빠르게 처리한다.
모든 메시지마다 DB를 조회하지 않는다.
이 부분은 결국 성능, 가용성, 정합성 사이의 트레이드오프입니다.
CAP 이론으로 보면, 네트워크 파티션 상황에서는 consistency와 availability 사이의 선택이 필요한 구간이라고 볼 수 있습니다.
그래서 현실적인 설계는 “캐시 불일치를 절대 만들지 않겠다”가 아니라, 캐시가 불확실한 상태를 감지하고, 그때는 다른 경로로 전환하는 방향으로 설계하는 것이 좋습니다.
예를 들면 WebSocket 서버나 Message Service가 단순히 이렇게 판단하면 위험합니다.
Redis에 muted=false가 있음 → 메시지 전송 허용
조금 더 안전한 방법은 아래와 같이 설계할 수 있습니다.
Redis에 muted=false가 있음
그리고 동시에,
해당 캐시가 최근까지 정상적으로 갱신되고 있음
캐시 동기화 지연이 허용 범위 안임
해당 값의 version이 신뢰 가능한 상태임
이런 조건을 함께 보고 메시지 전송 허용 여부를 판단하는 방식입니다.
반대로 캐시 갱신 워커가 멈췄거나, Redis 갱신이 실패했거나, 캐시가 오래되었다고 판단되면 muted=false 값을 그대로 믿으면 안 됩니다.
이때 선택지는 보통 두 가지입니다.
fallback
→ 캐시가 불확실한 경우에만 DB 또는 권한 서비스로 다시 확인
fail-closed
→ 확인이 불가능하면 메시지를 허용하지 않고 거절하거나 보류
mute나 차단처럼 잘못 허용되면 사용자 신뢰 문제가 생기는 기능은 일반적으로 fail-open, 즉 “불확실해도 허용”보다는 fallback 또는 fail-closed에 가깝게 설계하는 것이 더 안전합니다.
말씀해주신 것처럼 not muted도 Redis에 캐싱할 수는 있습니다.
대부분의 사용자는 mute되지 않은 상태이기 때문에, 이를 캐싱하지 않으면 DB 조회가 너무 많아질 수 있습니다.
다만 not muted 캐시는 특히 조심해야 합니다.
오래된 muted=true
→ 메시지를 과하게 막는 문제
오래된 muted=false
→ 원래 막아야 할 메시지를 허용하는 문제
두 번째가 더 위험할 수 있기 때문에, not muted를 캐싱하더라도 캐시가 최신이라고 믿을 수 있는 상태에서만 사용하는 것이 좋습니다.
그래서 not muted를 캐싱한다면, policy_version을 붙이거나 room/user shard 단위의 cache sync 상태를 함께 확인하는 방식이 필요할 수 있습니다.
즉, cache updater가 최신 이벤트까지 따라온 상태에서만 not muted를 신뢰하고, cache sync 상태가 불확실하면 not muted 캐시를 믿지 않는 방향입니다.
별도로 질문 주신 “DB 저장은 성공했는데 Redis flag 쓰기에 실패하면 어떻게 응답해야 하는가?”에 대해서는, 보통은 DB를 기준으로 source of truth를 잡습니다.
다만 응답 정책은 서비스 요구사항에 따라 달라질 수 있습니다.
DB 저장 성공이면 성공 응답
DB를 원본으로 보는 방식
Outbox/CDC를 통해 Redis 갱신은 재시도
대신 send path에서 캐시 상태가 불확실하면 fallback/fail-closed 필요
DB 저장 그리고 캐시/이벤트 전파까지 성공해야 성공 응답
사용자 입장에서는 더 즉시 반영되는 것처럼 보임
대신 mute API의 지연 시간과 실패율이 증가
DB 저장 성공, 캐시 반영 실패 시 “처리 중” 상태로 응답
DB에는 반영되었지만 전파가 아직 완료되지 않은 상태
내부적으로 propagation pending 상태를 남기고 재시도
대규모 시스템에서는 보통 DB commit을 기준으로 source of truth를 잡고, 캐시 갱신은 Outbox/CDC 기반으로 재시도 가능한 구조를 둡니다.
다만 mute 또는 차단처럼 즉시성이 중요한 기능에서는, API 응답만 성공으로 끝내는 것이 아니라 메시지 전송 경로에서 해당 room/user의 캐시를 더 보수적으로 다뤄야 합니다.
예를 들면 DB 저장은 성공했지만 Redis flag 쓰기에 실패했다면, 최소한 outbox에 남은 이벤트를 기준으로 cache updater가 재시도를 해주어야 합니다.
그리고 해당 shard의 캐시 동기화 상태는 healthy가 아니라 degraded로 보이게 만드는 것이 좋습니다. 그러면 WebSocket 서버나 Message Service가 그 캐시를 무조건 신뢰하지 않게 됩니다.
이와 비슷한 고민은 빅테크 캐시 인프라에서도 볼 수 있습니다.
다만 이런 사례들이 채팅 mute/차단 구현을 직접 설명하는 것은 아니라는 점은 주의해서 보셔야 합니다. 대신 대규모 시스템에서 캐시 불일치를 어떻게 다루는지 보여주는 참고 사례로 보시면 좋습니다.
예를 들어 AWS DAX는 DynamoDB 앞단의 캐시지만, strongly consistent read 요청은 DAX가 직접 처리하지 않고 DynamoDB로 전달하며, 그 결과도 캐시에 저장하지 않습니다.
즉, 강한 정합성이 필요한 요청은 캐시만으로 처리하지 않고 원본 저장소 쪽으로 보내는 구조입니다.
따라서 캐시는 빠른 조회를 위한 계층이지만, 캐시가 항상 최신이라고 가정하면 안 됩니다.
강한 정합성이 필요한 경우에는 캐시 신뢰도를 판단하거나, 캐시를 우회하는 경로를 준비하는 방향으로 설계를 진행하는 것이 좋습니다.
정리해보면 다음과 같습니다.
Redis에 muted=false가 있다는 이유만으로 메시지를 허용하지 않는다.
Redis 값이 있고, 그 값의 version이 신뢰 가능하고, 해당 shard의 cache sync 상태가 healthy일 때만 캐시 결과를 신뢰하는 방향이 더 안전합니다.
결론적으로는 다음과 같이 이해하시면 좋을 것 같습니다.
recent_policy_change TTL만으로는 장기 장애 상황의 정합성을 보장하기 어렵습니다.
그리고 별도의 캐시 동기화 시스템을 둬도 네트워크 파티션이나 캐시 갱신 경로 장애 중에는 수렴 시간을 100% 보장하기 어렵습니다.
그래서 핵심은 캐시가 최신이라고 믿어도 되는 상황과 믿으면 안 되는 상황을 구분하는 것입니다.
믿으면 안 되는 상황에서는 fallback 또는 fail-closed로 전환해야 합니다.
결국 이 문제는 Redis를 어떻게 갱신할 것인가만의 문제가 아니라, Redis가 오래된 상태일 때 시스템이 그 사실을 감지하고 어떻게 행동할 것인가의 문제라고 보시면 좋을 것 같습니다.
좋은 질문 감사합니다.
이 정도 고민은 실제 대규모 시스템 설계에서도 매우 중요한 지점입니다.
감사합니다.
좋은 하루 되세요.
0
안녕하세요, 인프런 AI 인턴입니다. 한재현님께서는 WhatsApp 채팅 아키텍처의 설계 관련하여 검증 로직에 대한 질문을 주셨습니다. 이 질문은 백엔드 시스템 설계에서 메시지 전송을 위한 검증 로직의 위치를 결정하는 중요한 주제입니다.
검증 로직의 위치
웹소켓(ws) 서버:
- 즉각적인 피드백 제공: 사용자가 메시지를 보낼 때, 즉각적인 피드백을 제공하기 위해서는 웹소켓 서버에서 가능한 검증을 수행하는 것이 유리할 수 있습니다. 예를 들어, 채팅방 참가 여부와 같은 간단한 검증은 웹소켓 서버에 캐싱된 데이터를 이용하여 빠르게 처리할 수 있습니다.
- 즉각적인 피드백 제공: 사용자가 메시지를 보낼 때, 즉각적인 피드백을 제공하기 위해서는 웹소켓 서버에서 가능한 검증을 수행하는 것이 유리할 수 있습니다. 예를 들어, 채팅방 참가 여부와 같은 간단한 검증은 웹소켓 서버에 캐싱된 데이터를 이용하여 빠르게 처리할 수 있습니다.
미들웨어 또는 API 게이트웨이:
- 공통 검증 로직 적용: 여러 서비스가 연결되는 환경에서는 미들웨어나 API 게이트웨이를 통해 검증 로직을 관리할 수 있습니다. 이는 코드 중복을 줄이고 공통 로직을 한 곳에서 관리하게 해줍니다.
- 공통 검증 로직 적용: 여러 서비스가 연결되는 환경에서는 미들웨어나 API 게이트웨이를 통해 검증 로직을 관리할 수 있습니다. 이는 코드 중복을 줄이고 공통 로직을 한 곳에서 관리하게 해줍니다.
Lambda 또는 Stream 처리 전에:
- 최종 검증 단계: Lambda나 Stream 처리 전에 최종 검증을 통해 데이터의 무결성을 한 번 더 확인할 수 있습니다. 이 단계에서 데이터베이스 조회와 같은 복잡한 검증이 수행될 수 있습니다.
- 최종 검증 단계: Lambda나 Stream 처리 전에 최종 검증을 통해 데이터의 무결성을 한 번 더 확인할 수 있습니다. 이 단계에서 데이터베이스 조회와 같은 복잡한 검증이 수행될 수 있습니다.
피드백 제공
즉각적인 피드백이 필요한 검증 로직은 요청이 처리될 때마다 클라이언트에게 바로 결과를 반환하는 구조가 필요합니다. 이는 비동기적으로도 구현할 수 있으며, 사용자 친화적인 경험을 제공합니다.
관련된 질문과 답변을 통해 더 많은 정보를 얻고자 하신다면 아래 링크를 참고하세요:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
Substack 1년 제공
1
51
3
특별 학습 자료 프로모션 1년 멤버십 무료 제공 문의드립니다
0
39
2
특별 학습 자료 프로모션 1년 멤버십 제공 관련 문의 드립니다.
0
80
2
simcached를 검색을 했을때 해당 자료가 나오지 않는데 혹시 MemcachedGPU 라고 불리는 기술을 말씀하시는건가요?
0
75
1
유튜브 예제에서 흐름 관련 질문있습니다
0
58
2
Spotify 서비스 설계에서 transcoder service에 대해 문의 드립니다.
0
80
1
특별 학습 자료 프로모션 1년 멤버십 무료 제공 지원 확인 방법
0
108
2
[위치 이름 기반으로 호텔을 조회하는 메서드] 코드 질문 드립니다.
0
60
2
14강. 영화 DVD 대여 시스템 데이터베이스 스키마 설계에서 Inventory 테이블 질문있습니다.
0
88
2
Matching Service의 MQ 필요성, Cassandra의 필요성, Cassandara - RDB 동기화
0
99
2
Imgur 이미지 호스팅 서비스 질문
0
96
2
쿠폰 문의
0
100
1
Redis 랜덤 값 추가 시 메타데이터 저장
0
96
1
Blob Storage에서 파일 업로드에 대한 동시성을 어떻게 제어할 수 있을까요?
0
118
2
채팅을 영속할 DB로 RDB를 선택한 이유도 궁금합니다
0
297
2
URL 단축 서비스에서 redis counter를 사용하는 이유가 무엇인지 궁금합니다.
0
222
2
long url을 파티션키로 지정했을때 장점이 생각 안나네요 ㅎㅎ;;
0
146
2
실제 인터뷰에서도 Object Oriented Design 을 이런 과정으로 하는걸까요?
0
176
1
able to get all students who got a "d+" grade or lower 요구사항
0
125
1
number of copies 컬럼은 Inventory Table에 들어가야하지않을까요?
0
179
3
Spotify 서비스 시스템 디자인 및 스케일링에서 백업 클러스터 사용에 대한 문의
0
100
2
URL 단축 서비스 시스템 디자인 및 스케일링 질문있습니다.
0
201
2
예시 두개가 납득이 잘 가지않네요 ㅠㅠ
1
218
2
강의자료는 제공 불가능할까요?
0
232
1





