Search

채팅 수집 장애 대응 구조: Redis Lock 활용

개요

EKS 기반 환경에서 채팅 수집기를 여러 인스턴스로 운영하는 과정에서, 단순히 DB 상태만을 보고 채널 할당과 장애 복구를 관리하는 방식은 실전에서 심각한 한계가 있다는 점이 드러났다. 특히, 인스턴스 장애나 종료, Pod Crash와 같은 예기치 못한 상황이 발생했을 때, DB는 단일 진실 원본(Single Source of Truth) 역할을 하더라도 즉각적인 상태 동기화가 어려워, 결과적으로 중복 수집이나 채팅 데이터 유실이 빈번하게 발생할 수 있었다. 여기에 더해 모든 인스턴스가 모든 채널을 동시에 수집하는 브로드캐스트형 구조는 중복과 리소스 낭비, 트래픽 폭증을 유발해 운영 효율성을 심각하게 저하시켰다.

고민 과정

가장 먼저, “누가 어떤 채널을 수집하고 있는지”를 실시간으로 정확하게 파악할 방법이 필요했다. DB 상태 기반으로 주기적 업데이트를 하는 방법은 이미 지연(latency) 문제를 겪고 있었고, 장애 상황에서 동기화가 늦어져 중복 수집이 불가피했다. 또, 브로드캐스트 구조를 유지하면 각 채널의 채팅 데이터를 모든 인스턴스가 다 받아야 하니, 네트워크와 CPU 리소스 낭비가 눈에 띄게 컸다. 따라서 중복 방지와 동적 재할당을 동시에 만족하는 구조를 찾는 것이 핵심 과제가 됐다.

선택 이유

이 문제를 풀기 위해 선택한 방식이 Set 기반 채널 풀 + Key 기반 분산 Lock 구조다. Redis를 중심으로 채널 풀(Set)에 모든 채널을 등록하고, 각 수집기가 채널을 할당받을 때는 해당 채널 키에 대해 TTL이 있는 Lock(예: 30초)을 걸어, 오직 Lock을 보유한 인스턴스만 해당 채널의 데이터를 수집하게 했다.
이 방식은 인스턴스가 정상적으로 종료되든, 장애나 네트워크 단절로 비정상 종료되든 TTL이 만료되면 Lock이 자동 해제되기 때문에, 다른 인스턴스가 즉시 Lock을 획득하고 수집을 재개할 수 있다. 이렇게 하면 수동 개입 없이도 장애 복구가 가능하며, 중복 수집도 원천 차단된다. SPOF(단일 장애 지점) 우려가 있는 Redis를 선택한 이유는, 이 구조가 제공하는 복구 속도, 확장성, 관리 편의성이 훨씬 더 중요하다고 판단했기 때문이다.

결론

이 설계는 장애 상황에서 최대 N초 동안의 채팅 데이터 유실 가능성을 구조적으로 내포하지만, 운영 효율과 복구 속도, 그리고 확장성을 우선시하는 전략적 선택이었다. DB 상태 기반 관리로는 해결할 수 없었던 실시간 동기화 문제를 Redis 기반 분산 Lock이 상당 부분 해결했고, 이를 통해 대규모 채팅 수집 환경에서 안정성과 효율성을 동시에 확보할 수 있었다. 실제 운영 과정에서, 이 구조는 중복 수집을 완전히 제거하고 리소스 사용량을 최적화했으며, 장애 발생 시 빠른 복구를 가능하게 함으로써 전체 시스템의 신뢰성을 높일 수 있었다.

Redis Lock 체크용 Bash

#!/bin/bash REDIS_CLI="redis-cli --tls -h 127.0.0.1 -p 16379" CHANNELS=$($REDIS_CLI SMEMBERS channels:all) for CHANNEL_ID in $CHANNELS; do LOCK_KEY="channel:lock:$CHANNEL_ID" LOCK_OWNER=$($REDIS_CLI GET $LOCK_KEY) echo "$CHANNEL_ID : $LOCK_OWNER" done
Bash
복사
(base) dominic@Dominic-MacBook-Pro .aws % keys "channel:lock*" 1) "channel:lock:7c91d4b91a8f980cd6b717bac3a8c7a6" 2) "channel:lock:8a59b34b46271960c1bf172bb0fac758" 3) "channel:lock:0c750f404f6f895e17d3d3a363e5b9c6" 4) "channel:lock:58153c1aa956637fb83241a35c6b235b" 5) "channel:lock:999853fabc8fba728882cd4e885aa882" 6) "channel:lock:8f433fe01faac742a5cf0819e42397de" 7) "channel:lock:b03f799de762c83b0d96ca392136ac24" 8) "channel:lock:bee4b42475b5937226b8b7ccbe2eb2dc" 9) "channel:lock:2caa0a02ae68cd012c2a89e0191403c7" 10) "channel:lock:0dad8baf12a436f722faa8e5001c5011" 11) "channel:lock:58962d51b3c8c50d315a977acff3ab35" 12) "channel:lock:12a3ece632517703c7fa99213e819104" 13) "channel:lock:720b53dfe8d1f8321622c8514eded131" 14) "channel:lock:c22de0fba658c38e5fb4617fa9cc69fe" 15) "channel:lock:8e21e808ba7e8a071752bbad579fd80c" 16) "channel:lock:1e94178c07fdb6543d46d7d31a6e442e" 17) "channel:lock:5a19b598b01a4c9711827276bcbefbab" 18) "channel:lock:041a26b437429f2ce01811e01cc3074f" 19) "channel:lock:5a9a50c3d7c5f5720b050e230d3306dc" 20) "channel:lock:71fe514d31377367a83a8bfaf720c783" 21) "channel:lock:80756c224700a06dba615a3a1ba38003" 22) "channel:lock:47c49bff55111a6cfd30bb299e9d2e5d" 23) "channel:lock:95dce22a7af42be99eaaef19619ebf25" 24) "channel:lock:81a105352d859eb1738c6b3a152a5cd8" 25) "channel:lock:3f2c9243ef3e3e58da5e4520cd45a3f8" 26) "channel:lock:86e8d2b9d7222e9cb17d5b41f41791f4" 27) "channel:lock:57a0a44e7f7ca9dc4f4c180ac7cf9697" 28) "channel:lock:fd4c1d65f7bdea3e4c81eced5d763083" 29) "channel:lock:b24a3bc8d6c5479b20ae69148bd79934" 30) "channel:lock:b7d7941296802eb5d3b597be7021f71a" 31) "channel:lock:9a3ed215c3ac19a3f75159a674485921" 32) "channel:lock:9da4321f21f1133b4f868eb01a848a5f" 33) "channel:lock:8d87e7057474f4506d2db5d0c2769138" 34) "channel:lock:19e3b97ca1bca954d1ac84cf6862e0dc" 35) "channel:lock:d028a08df38e91ba56163fc4f428814e" 36) "channel:lock:7a847332420ea9ec8a2795d2bfd4ecaa" 37) "channel:lock:083d7065d62fe811297963161c89b3ed" 38) "channel:lock:d7ddd7585a271e55159ae47c0ce9a9dd" 39) "channel:lock:89fbb7cda508836842bd4ab79d33d6ff" 40) "channel:lock:7912b1a19900e76219b48f03c8f28100" 41) "channel:lock:cc0a6656be3ea06b1d0a210291e4dcef" 42) "channel:lock:50f557a43a5c19517243a2271882c249" 43) "channel:lock:a9b3e92588a2d5e2ac8afa32d862d03b" 44) "channel:lock:7dc9561a4e60fb5221bdea64346672f1" 45) "channel:lock:ba244c8381cd13781d93e3614165ee3f" 46) "channel:lock:c1aa33d6a5bb07ef1d6d8004bc4c8337" 47) "channel:lock:ef33dc3bb54c6353e8c0b992f839cd5e" 48) "channel:lock:03057fc06e3b182b3c28adbf23f6d3f6" 49) "channel:lock:dcfd3c7cf3c05b41306a4cc249ad5c9f" 50) "channel:lock:6d0d57edef60095667957355294454bc" 51) "channel:lock:e2915f099e4060d8bbfb1d008cb675d2" 52) "channel:lock:d62185a9a4b466b2bd24da17a31ea9fa" 53) "channel:lock:577506b2d214450f65587fb04adc243a" 54) "channel:lock:3aa0be9618364797f3532806ecc4e38b" 55) "channel:lock:b628d1039a84ecc703804e17acee2eb3" 56) "channel:lock:686d397700577e26e48df0fe4bd2cc06" 57) "channel:lock:8f3c95f3e69a5ece6d66c776cc446732" 58) "channel:lock:58a607afac0836deac6fa9c294a0d4b7" 59) "channel:lock:2805c8cb7e8671b3e46e379b15627974" 60) "channel:lock:91954a460f7426e74648851e70005f99"
Bash
복사
현재 EKS 인스턴스는 4개고 인스턴스당 15개 할당되도록 설정되어 있어 최대 60까지 할당된 모습

참조