채널톡은 분산 캐시 일관성 문제를 이렇게 해결합니다
Perry • Backend Engineer
안녕하세요, 채널톡 백엔드 엔지니어 페리입니다.
빠르게 성장하는 서비스는 필연적으로 성능 문제에 직면하게 됩니다. 늘어나는 사용자 요청을 빠르고 안정적으로 처리하기 위해 우리는 다양한 기술을 사용하는데, 그중 가장 대표적인 것이 바로 캐싱(Caching)입니다. 자주 사용되거나 계산 비용이 비싼 데이터를 메모리와 같이 빠른 저장소에 미리 보관해두고 재사용함으로써, 응답 속도를 높이고 데이터베이스와 같은 백엔드 시스템의 부하를 줄여주죠.
하지만 캐시가 모든 문제를 해결해주는 만능 열쇠는 아닙니다. 특히 현대의 많은 서비스처럼 여러 대의 서버 인스턴스가 함께 요청을 처리하는 분산 환경에서는 새로운 고민거리가 생깁니다. 더 많은 트래픽을 처리하기 위해 서버 인스턴스를 늘리는 스케일 아웃(Scale-out) 전략은 효과적이지만, 각 인스턴스가 자신만의 로컬 캐시(Local Cache)를 가질 때 문제가 발생할 수 있습니다.
바로 캐시 데이터 불일치(Cache Inconsistency) 문제입니다. 어떤 인스턴스에서는 데이터가 변경되어 캐시가 갱신되었지만, 다른 인스턴스는 여전히 예전 데이터를 캐시에 가지고 있는 상황(Stale data)이 발생할 수 있는 것이죠.
이 글에서는 채널톡이 서비스 성장 과정에서 겪었던 바로 이 분산 캐시 불일치 문제를 어떻게 정의하고 해결해 나갔는지 공유하고자 합니다. 특히, 이 문제를 해결하기 위해 저희가 직접 설계하고 구현한 DistributedCachedDao
의 아이디어 도출 과정과 핵심적인 설계 내용을 1편에서 다룰 예정입니다.
채널톡은 수많은 고객사들이 매일 사용하는 서비스인 만큼, 안정적이고 빠른 응답 속도를 제공하는 것이 매우 중요합니다. 이를 위해 저희는 여러 대의 서버 인스턴스가 로드 밸런서(Load Balancer) 뒤에서 함께 사용자 요청을 처리하는 분산 아키텍처를 사용하고 있습니다. 이는 트래픽 증가에 유연하게 대응할 수 있는 효과적인 구조입니다.
이러한 분산 환경에서 성능을 높이기 위해 채널톡은 오랫동안 각 서버 인스턴스 내부에 로컬 캐시를 두는 방식을 사용해왔습니다. 저희는 CachedDao
라는 자체 구현체를 사용하는데요, 이는 자바 진영에서 널리 쓰이는 인메모리(In-memory) 캐시 라이브러리인 Caffeine을 기반으로 합니다.
이 방식의 가장 큰 장점은 단연 속도입니다. 네트워크 통신 없이 각 서버 인스턴스의 메모리에서 직접 데이터를 읽어오기 때문에 매우 빠른 응답 속도를 보장할 수 있습니다. 데이터베이스까지 요청이 도달하는 횟수를 크게 줄여주므로, 시스템 전반의 부하를 낮추는 데도 기여합니다. 마치 자주 사용하는 물건을 책상 바로 위 선반에 두는 것처럼, 빠르고 편리하게 데이터에 접근할 수 있는 것이죠.
하지만 이 편리한 로컬 캐시 방식에는 치명적인 약점이 숨어있었습니다. 바로 여러 서버 인스턴스 간의 데이터 불일치(Inconsistency) 문제입니다. 각 인스턴스가 자신만의 독립적인 캐시 저장소를 가지기 때문에 발생하는 문제죠.
구체적인 예를 들어보겠습니다. 채널톡에는 고객 응대를 자동화하는 '워크플로우'라는 기능이 있습니다. 매니저는 이 워크플로우의 설정을 언제든지 변경할 수 있죠. 만약 매니저가 설정을 변경하는 요청이 인스턴스 'A'로 전달되었다고 가정해 봅시다.
인스턴스 'A'는 DB에서 워크플로우 설정을 변경합니다.
인스턴스 'A'는 자신의 로컬 캐시에 있는 해당 워크플로우 정보도 최신 설정으로 업데이트합니다.
하지만, 다른 인스턴스 'B'와 'C'는 이 변경 사실을 전혀 알지 못합니다. 여전히 변경 전의 워크플로우 설정을 로컬 캐시에 그대로 가지고 있습니다.
이 상황에서 만약 사용자의 다음 요청이 인스턴스 'B'나 'C'로 전달된다면 어떻게 될까요? 사용자는 분명 설정을 변경했지만, 변경 전의 이전 설정대로 작동하는 워크플로우를 경험하게 됩니다. 데이터베이스에는 분명 최신 정보가 저장되어 있지만, 로컬 캐시가 오래된 정보(Stale data)를 반환하기 때문입니다.
이러한 데이터 불일치는 단순히 특정 기능의 오작동을 넘어 채널톡 서비스 전반에 걸쳐 다양한 문제로 이어질 수 있었습니다.
사용자 경험 저하: 사용자는 예측 불가능하거나 잘못된 서비스 동작(예: 변경 전 워크플로우 실행)을 경험하게 되어 혼란과 불만을 느낄 수 있습니다.
데이터 정합성 문제: 시스템 전체적으로 데이터의 일관성이 깨져 예상치 못한 버그나 오류의 원인이 될 수 있습니다. 특히, 워크플로우처럼 복잡한 로직이 얽힌 기능에서는 더욱 심각한 문제를 야기할 가능성이 있었습니다.
성능 저하 및 DB 부하 증가: 로컬 캐시의 효율성이 현저히 낮아지는 문제가 발생했습니다. 예를 들어 워크플로우 캐시의 경우 짧은 만료 시간(TTL, Time-To-Live)을 설정하여 데이터 정합성 문제를 완화해두었는데 캐시 히트율(Hit Rate)이 기대치에 크게 못 미치면서 불필요한 데이터베이스 조회가 빈번하게 발생했습니다. 워크플로우 기능은 쓰기(Write) 요청보다 읽기(Read) 요청이 압도적으로 많은 특성(Read-Heavy)을 가지고 있음에도 불구하고, 캐시가 제 역할을 다하지 못해 데이터베이스에 상당한 부하를 주고 있었습니다.
느린 변경 반영: 일부 기능에서 위와 같이 TTL을 짧게 유지하여 데이터 일관성을 유지하면, 필연적으로 일정 시간의 지연을 발생시켜, 즉각적인 반영이 필요한 사용자 시나리오에서는 문제가 되었습니다.
결국, 각 서버 인스턴스가 자신만의 캐시라는 외로운 섬을 가지면서 발생하는 정보 비대칭 문제는 더 이상 외면할 수 없는 과제였습니다. 로컬 캐시의 빠른 속도라는 장점은 유지하면서도, 여러 인스턴스가 일관된 데이터를 바라볼 수 있는 새로운 방법이 필요했습니다.
로컬 캐시의 한계를 명확히 인지한 저희는, 여러 인스턴스가 캐시 변경 사항을 효과적으로 공유할 수 있는 메커니즘을 구축하는 것을 목표로 삼았습니다. 이 해결책은 다음과 같은 요구사항을 만족해야 했습니다.
성능: 로컬 캐시만큼은 아니더라도, 충분히 빠른 응답 속도를 제공해야 합니다. 특히 읽기 요청이 많은 상황에서 성능 저하가 크지 않아야 합니다. (목표: 수십 ms 이내 응답)
데이터 일관성: 데이터 변경 시, 최대한 빠르게 다른 인스턴스에 변경 사항이 전파되어야 합니다. 완벽한 실시간 동기화는 어렵더라도, 기존의 TTL(예를 들어 1분 정도)만큼 지연보다는 훨씬 개선되어야 했습니다. (목표: 쓰기 후 최대 수 초 내 동기화)
안정성 및 확장성: 채널톡의 증가하는 트래픽을 안정적으로 처리할 수 있어야 하며, 향후 서비스 확장에도 유연하게 대응할 수 있어야 합니다.
이러한 요구사항을 바탕으로 몇 가지 대안을 검토했습니다.
가장 먼저 떠올릴 수 있는 방법은 Redis와 같은 별도의 캐시 서버를 글로벌 캐시로 사용하는 것입니다. 모든 인스턴스가 이 중앙 집중형 캐시 서버에만 데이터를 읽고 쓰도록 하는 방식이죠.
장점: 모든 인스턴스가 동일한 캐시 서버를 바라보므로 데이터 일관성 문제는 확실히 해결됩니다.
단점:
네트워크 지연: 모든 캐시 요청이 네트워크를 통해 외부 캐시 서버로 전달되므로, 로컬 메모리에서 직접 읽는 것보다 응답 속도가 느려집니다.
중앙 집중형 병목: 모든 캐시 부하가 글로벌 캐시 서버로 집중됩니다. 채널톡처럼 읽기 요청이 매우 많은 경우, Redis 서버 자체가 성능 병목 지점이 될 수 있습니다. 과거 채널톡에서 특정 기능을 글로벌 캐시로 전환했다가 Redis CPU 사용률 급증으로 롤백했던 경험도 있었습니다. 모든 요청이 Redis를 거치는 것은 성능과 비용 측면에서 부담스러웠습니다.
다음으로 로컬 캐시와 글로벌 캐시를 함께 사용하는 투-레벨 캐싱 방식을 고려했습니다. 먼저 로컬 캐시를 확인하고, 없으면 글로벌 캐시에서 데이터를 가져와 로컬 캐시에 저장하는 방식입니다.
장점: 로컬 캐시 히트 시 빠른 응답 속도를 누릴 수 있고, 글로벌 캐시를 통해 어느 정도 일관성을 확보할 수 있습니다.
단점:
구현 복잡성: 로컬 캐시와 글로벌 캐시 간의 데이터를 어떻게 동기화할지 관리하는 로직이 복잡해집니다.
여전한 불일치 가능성: 글로벌 캐시가 업데이트되어도 각 인스턴스의 로컬 캐시가 즉시 업데이트되지 않으면, 짧은 시간 동안 데이터 불일치가 발생할 수 있습니다.
글로벌 캐시와 투-레벨 캐싱 모두 명확한 단점을 가지고 있었습니다. 우리는 로컬 캐시의 빠른 읽기 속도라는 장점을 포기하고 싶지 않았고, 동시에 데이터 일관성 문제도 해결해야 했습니다. 여기서 저희는 Redis의 Pub/Sub 기능에 주목했습니다.
Redis Pub/Sub(Publish/Subscribe)은 메시지 발행/구독 모델을 구현한 기능입니다. 특정 주제(채널톡에서는 '채널'이라고 부릅니다)에 메시지를 발행(Publish)하면, 해당 주제를 구독(Subscribe)하고 있는 모든 클라이언트에게 메시지가 전달되는 방식입니다. 마치 라디오 방송국(Publisher)이 특정 주파수(Channel)로 방송을 송출하면, 해당 주파수에 맞춰진 라디오 수신기(Subscriber)들이 방송을 듣는 것과 비슷합니다.
특징:
비동기 메시지 전달: 발행자는 메시지를 보내고, 수신 여부를 기다리지 않습니다. (Fire-and-Forget)
간단한 구조: 비교적 간단하게 발행/구독 기능을 구현할 수 있습니다.
다중 전파: 하나의 메시지를 다수의 구독자에게 쉽게 전파할 수 있습니다.
채널톡은 이미 Redis를 주요 인프라로 활발하게 사용하고 있었습니다. 따라서 기존 인프라를 그대로 활용할 수 있다는 점이 매력적이었습니다. Redis Pub/Sub을 활용하면 여러 인스턴스에 변경 사항을 전파할 수 있을 것 같았습니다. 이제 구체적으로 어떤 정보를 전파할지 결정해야 했습니다.
Pub/Sub을 이용하여 캐시 일관성을 유지하는 방식은 크게 두 가지로 생각해볼 수 있었습니다.
키+값 동기화 방식: 데이터가 변경되면, 변경된 데이터의 키(Key)와 새로운 값(Value)을 함께 Pub/Sub 메시지로 발행하는 방식입니다. 메시지를 수신한 다른 인스턴스들은 즉시 자신의 로컬 캐시에 해당 키의 값을 새로운 값으로 업데이트(동기화)합니다.
장점: 메시지를 받은 즉시 최신 데이터로 캐시가 업데이트되므로, 다음번 조회 시 캐시 미스가 발생하지 않습니다.
단점:
구현 복잡성: 값을 메시지로 보내려면 직렬화(Serialization)/역직렬화(Deserialization) 과정이 필요합니다. 데이터 타입이 복잡하거나 다양할 경우 이 과정이 번거로울 수 있습니다. 또한, 서로 다른 버전의 애플리케이션 인스턴스 간에 데이터 형식이 호환되지 않을 위험도 있습니다.
동시성 문제: 여러 인스턴스에서 거의 동시에 같은 데이터에 대한 변경이 발생하고 메시지가 전파될 경우, 메시지 도착 순서에 따라 최종 캐시 상태가 달라질 수 있는 동시성 문제를 고려해야 합니다.
네트워크 부하: 키뿐만 아니라 값 데이터까지 전송해야 하므로 네트워크 트래픽이 증가합니다.
키 무효화 방식: 데이터가 변경되면, 변경된 데이터의 키(Key)만 Pub/Sub 메시지로 발행하는 방식입니다. 메시지를 수신한 다른 인스턴스들은 자신의 로컬 캐시에서 해당 키를 단순히 제거(무효화)합니다.
장점:
단순함: 키(주로 문자열)만 전달하므로 구현이 훨씬 간단하고 직렬화/역직렬화나 데이터 타입 호환성 문제를 걱정할 필요가 없습니다.
동시성 문제 완화: 단순히 캐시를 지우기만 하므로, 값 동기화 방식에서 발생할 수 있는 복잡한 동시성 문제에서 비교적 자유롭습니다.
네트워크 부하 감소: 값 데이터를 보내지 않으므로 네트워크 트래픽 부담이 적습니다.
단점: 메시지를 수신한 인스턴스는 해당 키가 무효화된 후 처음으로 데이터를 조회할 때 캐시 미스(Cache Miss)가 발생하여 DB에서 데이터를 다시 읽어와야 합니다. (Lazy Loading)
저희는 두 방식의 장단점을 비교한 결과, 키 무효화 방식을 선택했습니다. 약간의 캐시 미스를 감수하더라도 구현의 단순성, 안정성, 낮은 네트워크 부하라는 장점이 더 크다고 판단했습니다. 특히, 값 동기화 방식의 복잡성과 잠재적인 동시성 문제는 운영 부담을 가중시킬 수 있다고 보았습니다. 캐시의 핵심 역할은 DB 부하를 줄이는 것이고, 무효화 후 발생하는 첫 조회 시의 부하는 감수할 만하다고 판단했습니다.
결론적으로 저희는 다음과 같은 최종 방향을 설정했습니다.
읽기 성능: 기존처럼 로컬 캐시(Caffeine)를 기본으로 사용하여 빠른 읽기 속도를 확보한다.
일관성 확보: 데이터 변경이 발생하면, 변경이 일어난 인스턴스는 자신의 로컬 캐시를 무효화함과 동시에, Redis Pub/Sub을 통해 다른 모든 인스턴스에게 해당 데이터의 키(Key)만 전달하여 각자의 로컬 캐시를 무효화하도록 지시한다.
이 아이디어를 구체화하여 탄생한 것이 바로 DistributedCachedDao
입니다. 로컬 캐시의 속도와 Redis Pub/Sub을 이용한 '키 기반 캐시 무효화' 전파 메커니즘을 결합한 새로운 형태의 캐시 DAO입니다.
DistributedCachedDao
의 탄생앞선 고민과 논의 끝에, 우리는 로컬 캐시의 속도와 Redis Pub/Sub을 이용한 캐시 무효화 전파 메커니즘을 결합한 새로운 솔루션, DistributedCachedDao
를 설계하고 구현했습니다.
DistributedCachedDao
는 기본적으로 기존의 로컬 캐시(CachedDao
)와 유사하게 작동합니다. 각 애플리케이션 인스턴스는 여전히 Caffeine 기반의 인메모리 로컬 캐시를 가지고, 대부분의 데이터 조회(get
) 요청은 이 로컬 캐시를 통해 빠르게 처리됩니다.
여기에 더해, DistributedCachedDao
는 Redis Pub/Sub을 이용한 동기화 메커니즘을 내장하고 있습니다. 각 DistributedCachedDao
구현체는 고유한 Redis 채널을 구독(Subscribe)하고 있으며, 캐시 무효화가 필요할 때 이 채널을 통해 메시지를 주고받습니다.
DistributedCachedDao
의 핵심은 캐시 무효화 정보를 모든 인스턴스에 효과적으로 전파하는 데 있습니다. 특정 인스턴스에서 데이터 변경으로 인해 캐시를 무효화해야 할 때, 다음과 같은 과정이 진행됩니다.
변경 발생 및 invalidate(key)
호출: 데이터베이스에서 특정 데이터(예: 키가 "user:123"인 사용자 정보)가 변경되고, 해당 변경을 수행한 인스턴스 A에서 invalidate("user:123")
메서드가 호출됩니다.
로컬 캐시 무효화: 인스턴스 A는 우선 자신의 로컬 캐시에서 "user:123" 키에 해당하는 데이터를 즉시 제거(무효화)합니다.
무효화 메시지 발행 (Publish): 인스턴스 A는 자신이 구독하고 있는 Redis Pub/Sub 채널 (예: "cache-updates" 채널)에 "INVALIDATE user:123"과 같은 형식의 무효화 메시지를 발행합니다. 중요한 점은 이때 값(Value)이 아닌 키(Key) 정보만 메시지에 담아 보낸다는 것입니다.
메시지 수신 (Subscribe): 동일한 "cache-updates" 채널을 구독하고 있는 다른 인스턴스 B와 C는 Redis로부터 이 무효화 메시지를 수신합니다.
원격 로컬 캐시 무효화: 메시지를 수신한 인스턴스 B와 C는 메시지에 포함된 키 정보("user:123")를 확인하고, 각각 자신의 로컬 캐시에서 해당 키를 찾아 데이터를 제거(무효화)합니다.
이 과정을 통해 한 인스턴스에서 발생한 데이터 변경(및 캐시 무효화)이 Redis Pub/Sub을 통해 다른 모든 관련 인스턴스에게 전파되어, 각 인스턴스의 로컬 캐시가 일관된 상태를 향해 나아가게 됩니다.
DistributedCachedDao
는 기존 CachedDao
의 인터페이스를 최대한 유지하면서 필요한 부분을 확장하거나 변경했습니다.
get(String key)
: 데이터를 조회하는 기본 메서드입니다. 먼저 로컬 캐시(Caffeine)에서 키에 해당하는 값을 찾습니다. 값이 존재하고 유효하면 즉시 반환합니다(Cache Hit). 만약 값이 없거나 만료되었다면(Cache Miss), DistributedCachedDao
생성 시 정의된 로더(Loader)를 통해 데이터베이스 등 원본 데이터 소스에서 데이터를 읽어와 로컬 캐시에 저장한 후 반환합니다. 이 과정은 기존 CachedDao
와 동일합니다.
invalidate(String key)
: 특정 키에 대한 캐시를 무효화하는 메서드입니다. 기존 CachedDao
의 동작을 확장하여, 로컬 캐시에서 해당 키를 제거하는 것과 동시에 Redis Pub/Sub 채널에 무효화 메시지(INVALIDATE-{key})를 발행하는 두 가지 동작을 수행합니다.
clearCache()
: 해당 DAO의 로컬 캐시 전체를 비우는 메서드입니다. 마찬가지로 로컬 캐시를 비움과 동시에, 다른 모든 인스턴스에게도 캐시를 비우라는 신호(CLEAR 메시지)를 Redis Pub/Sub을 통해 발행합니다. 이는 예를 들어 전체 캐시 전략 변경이나 데이터 마이그레이션 등 모든 캐시를 강제로 리셋해야 할 때 유용하게 사용될 수 있습니다.
put(String key, T value)
(지원 안 함): DistributedCachedDao
는 캐시에 직접 값을 쓰는 put
메서드를 의도적으로 지원하지 않습니다. 이는 앞서 'Pub/Sub 활용 방식'에서 논의했듯이, 값 동기화 방식의 복잡성과 동시성 문제를 피하고 '키 무효화 후 로딩(Invalidate-then-load)' 패턴을 강제하기 위한 설계적 결정입니다. 캐시를 업데이트해야 할 경우, 해당 키를 invalidate(key)
로 무효화하고, 이후 get(key)
호출 시 로더를 통해 새로운 값이 자연스럽게 로드되도록 유도해야 합니다.
이렇게 설계된 DistributedCachedDao
는 로컬 캐시의 성능 이점을 유지하면서 분산 환경에서의 데이터 일관성 문제를 해결하기 위한 저희 팀의 핵심적인 접근 방식입니다. 하지만 이 솔루션을 실제로 구현하고 안정적으로 운영하기 위해서는 몇 가지 중요한 기술적 고려사항들을 자세히 살펴볼 필요가 있습니다.
DistributedCachedDao
는 로컬 캐시의 빠른 성능과 분산 환경에서의 데이터 일관성이라는 목표를 달성하기 위한 효과적인 접근 방식이지만, 실제 시스템에 적용하기 위해서는 몇 가지 중요한 기술적 측면과 내재된 한계점을 신중하게 고려해야 했습니다.
가장 핵심적인 특징은 DistributedCachedDao
가 강력한 일관성(Strong Consistency) 이 아닌 결과적 일관성(Eventual Consistency) 모델을 따른다는 점입니다. 이는 기반 기술인 Redis Pub/Sub이 메시지 전달을 100% 보장하지 않는 'At-most-once' 방식으로 동작하기 때문입니다. 네트워크 지연, 일시적인 Redis 서버 불안정 등의 이유로 캐시 무효화 메시지가 일부 인스턴스에 전달되지 않거나 지연될 수 있습니다.
결과적으로, 모든 인스턴스의 캐시가 항상 실시간으로 완벽하게 동기화되는 것은 아니며, 아주 짧은 시간 동안 데이터 불일치가 발생할 가능성이 존재합니다. 하지만 이 방식은 아무런 동기화 메커니즘이 없던 기존 로컬 캐시 방식의 고질적인 문제(긴 불일치 시간)를 획기적으로 개선하며, 대부분의 시나리오에서 충분히 실용적인 수준의 일관성을 제공합니다.
결과적 일관성 모델에서 가장 주의해야 할 시나리오는 개별 메시지 유실보다 Redis Pub/Sub 구독 연결 자체가 끊어지는 경우입니다. TCP 연결의 불안정성이나 Redis 서버 문제로 구독이 예기치 않게 종료되면, 해당 인스턴스는 이후의 모든 캐시 무효화 메시지를 놓치게 되어 데이터 불일치가 심화될 수 있습니다.
이러한 심각한 상태를 방지하기 위해 DistributedCachedDao
는 다음과 같은 자동 복구 메커니즘을 내장하고 있습니다:
연결 상태 감지: 내부적으로 Redis Pub/Sub 연결 및 구독 상태를 지속적으로 확인합니다.
자동 재연결 및 재구독: 연결 단절이나 비정상적인 구독 종료가 감지되면, 즉시 자동으로 재연결 및 재구독을 시도하여 구독 상태를 복원합니다.
방어적 캐시 클리어: 재구독 성공 시, 연결이 끊어져 있던 동안 놓쳤을 수 있는 무효화 메시지로 인한 잠재적 불일치를 해소하기 위해 해당 인스턴스의 로컬 캐시 전체를 즉시 비웁니다 (super.clearCache()
). 이를 통해 다음 데이터 접근 시 반드시 최신 상태를 반영하도록 강제합니다.
수동 개입을 위한 관리 API: 자동 복구 메커니즘으로 대부분의 문제를 해결할 수 있지만, 드물게 발생하는 메시지 유실이나 데이터베이스 직접 수정 등으로 인해 캐시 불일치가 발생하는 예외적인 상황에 대비해야 합니다. 이를 위해 운영상 필요한 경우 특정 캐시 키를 모든 인스턴스에서 강제로 무효화할 수 있는 내부 관리자용 API를 제공하여 수동으로 데이터 일관성을 맞출 수 있는 수단을 마련했습니다.
키 타입 제한 (String
): 구현의 단순성과 Pub/Sub 메시지 처리의 편의성을 위해, DistributedCachedDao
에서 사용하는 캐시 키(Key) 타입은 현재 String
으로 제한됩니다. 이는 별도의 직렬화/역직렬화 없이 키 문자열을 바로 메시지로 전송하기 위함입니다. 여러 필드를 조합한 복합 키가 필요한 경우, 각 필드를 구분자('-' 등)로 연결한 단일 문자열 형태로 만들어 사용해야 합니다.
이러한 기술적 고려사항들은 DistributedCachedDao
설계 과정에서 저희가 마주했던 현실적인 트레이드오프를 보여줍니다. 강력한 일관성 대신 결과적 일관성을 선택하고, 연결 안정성을 위한 자동 복구 메커니즘을 구현하며, 운영 편의성을 위한 부가 기능을 추가하는 등, 성능과 안정성, 그리고 구현 복잡성 사이에서 실용적인 균형점을 찾고자 노력했습니다. 이 접근법 덕분에 채널톡의 분산 환경에서 캐시 불일치 문제를 효과적으로 해결할 수 있는 기반을 마련할 수 있었습니다.
이번 1편에서는 채널톡이 분산 환경에서 겪었던 캐시 불일치 문제를 어떻게 정의하고, 로컬 캐시의 한계를 넘어 일관성을 확보하기 위한 다양한 고민 끝에 Redis Pub/Sub 기반의 DistributedCachedDao
라는 해결책을 설계하게 되었는지 그 아이디어 도출 과정과 핵심 설계 원칙을 상세히 살펴보았습니다. 이로써 우리는 분산 환경에서의 캐시 문제를 해결하기 위한 이론적 토대를 마련했습니다.
다음 2편에서는 이 이론적인 설계를 실제로 구현하고 적용하는 여정을 따라갑니다. 채널톡의 핵심 기능 중 하나인 워크플로우 캐싱 시스템에 DistributedCachedDao
를 도입하며 겪었던 구체적인 리팩토링 과정과 그로 인해 얻을 수 있었던 놀라운 성능 개선 효과, 그리고 실제 운영 경험을 공유할 예정이니 많은 기대 부탁드립니다.
채널톡에서는 서비스가 성장함에 따라 발생하는 다양한 기술적 과제들을 깊이 있게 분석하고, 최적의 해결책을 찾아나가는 과정을 중요하게 생각합니다. 이러한 도전적인 문제 해결 과정에 함께하며 성장하고 싶으신 분들은 언제든지 채널톡의 문을 두드려 주세요!
We Make a Future Classic Product
채널팀과 함께 성장하고 싶은 분을 기다립니다