02 · 발표 전사 — Scaling Valkey the Right Way: Kubernetes at XL Scale
이 문서는 KubeCon + CloudNativeCon Europe 2026 발표 Scaling Valkey the Right Way: Kubernetes at XL Scale(Sarthak Aggarwal · Madelyn Olson, AWS ElastiCache)의 전사 정리본입니다. YouTube 자동 자막(타임스탬프 942줄)을 원본으로 삼되 발표자 본인의 슬라이드 덱과 대조해 오인식을 교정했습니다. Valkyrie→Valkey, Madeline/Merlin/Marlin→Madelyn, “as a stateless set”→StatefulSet, “something like that, CD”→etcd, “16,000 슬롯”→16384, “REST protocol”→RESP, “Bin Bin”→Binbin.
이 문서는 기록입니다. 무엇을 말했고 무엇이 슬라이드에 있었는지만 정리했고 그 수치가 어디서 왔는지·무엇을 증명하는지·Kubernetes에 얼마나 전이되는지는 판단하지 않습니다. 그 판단은 자매 문서 01 2,000노드에서 부러지는 것에 있습니다.
원본: 발표 영상 · 세션 페이지 · 슬라이드(PPTX)
00:00 인사와 도입 — Pokémon Go 이야기
발표는 Madelyn Olson이 엽니다. Amazon ElastiCache의 principal engineer이자 Valkey 오픈소스 메인테이너입니다. 함께 무대에 선 Sarthak Aggarwal은 같은 팀 엔지니어이자 Valkey 오픈소스 프로젝트 기여자입니다. Madelyn은 자신과 Sarthak을 “낯선 땅의 이방인"이라 소개합니다. 유럽에 온 미국인이라는 뜻이기도 하고 Amazon 출신이라 VM 기반 세계에 익숙하다는 뜻이기도 합니다. 그래서 Kubernetes를 보면 “이 기술 대단하다, 더 쓰고 싶다"는 생각이 든다고 말합니다. 오늘은 Amazon ElastiCache(Amazon의 인메모리 매니지드 캐싱 서비스)에서 초대형 클러스터를 만들며 얻은 교훈을 공유하겠다고 합니다.
주제는 대형 클러스터 스케일링입니다. Madelyn은 2016년 Pokémon Go 열풍에서 출발합니다. Niantic은 예상 사용량을 계산한 뒤 베이스라인의 4~5배를 최악의 시나리오로 잡았는데 실제 피크는 예상의 거의 50배까지 치솟았습니다. 그런데도 Kubernetes와 cloud native 기술 위에서 그 전체를 감당해냈다는 게 이 이야기의 요지입니다.
이어서 Madelyn이 던지는 전제가 발표 전체의 톤을 정합니다. 2,000노드 규모의 Valkey 클러스터는 매일 돌릴 물건이 아닙니다. 예민하고 수직으로 더 키울 여지가 거의 없습니다. 그래도 언젠가 그런 상황에 놓일 수 있으니 오늘은 그 상황에 대비하는 모범 사례를 이야기하겠다고 합니다.
슬라이드 노트: 슬라이드 3에 이 전제가 문장 그대로 적혀 있습니다. “You shouldn’t run a 2000 node Valkey cluster, but you might have to.”
02:59 Valkey란 무엇인가
Madelyn이 청중에게 묻습니다. Redis를 아는 사람? Valkey를 알고 온 사람? 손이 많이 올라가자 놀랍니다. 작년 비슷한 발표에서는 방의 4분의 1 정도만 손을 들었다며 이 부분은 짧게 넘기겠다고 합니다.
Valkey는 CNCF 프로젝트가 아니라 Linux Foundation 프로젝트입니다. Redis 오픈소스 7.2의 fork로 만들어졌기 때문에 Redis에서 쓰던 기술은 거의 그대로 Valkey에서도 동작합니다. API가 호환됩니다. 유지보수 주체는 다릅니다. Redis 오픈소스 프로젝트에 몸담았던 기여자들이 만들고 관리합니다.
프로덕션 배치 방법은 standalone과 cluster로 나뉩니다. standalone은 단일 인스턴스 배치입니다. 대부분 StatefulSet으로 띄우고 Valkey가 내놓은 Helm 차트를 그대로 쓰면 됩니다. 단순한 캐시, 단순한 서비스면 이걸로 충분히 잘 동작합니다. 노드가 죽으면 교체되고 캐시는 비어서 다시 시작하지만 대부분의 사용자에게는 그 정도로 괜찮습니다.
오늘 다룰 건 그쪽이 아닙니다. Valkey의 수평 확장 모드인 cluster 모드입니다.
03:55 cluster bus와 gossip
Valkey는 Redis 오픈소스의 fork라서 cloud native 기술보다 역사가 깁니다. 그만큼 배치 모델이 살짝 낯섭니다. cloud native에 딱 맞아떨어지진 않지만 어떻게든 맞춰 쓸 수는 있습니다.
Valkey는 작업을 shard로 나눠 분산합니다. shard는 전체 데이터를 수평으로 자른 조각이고 각 shard 안에는 데이터를 서빙하는 primary가 있습니다. 여기에 primary에서 데이터를 받아 읽기 요청까지 처리할 수 있는 replica가 붙습니다. 용량이나 처리량을 늘리려면 shard를 더합니다. 그러면 쓰기와 읽기 capacity가 모두 수평으로 늘어납니다.
이걸 가능하게 하는 게 cluster bus입니다. 좀 더 cloud native다운 방식이라면 이 토폴로지 정보를 etcd 같은 외부 저장소에 둘 텐데 Valkey는 그렇게 하지 않습니다. 각 shard가 자기 상태를 스스로 소유하고 노드들끼리 끊임없이 서로 통신합니다.
덕분에 클러스터를 구성하기가 쉽습니다. primary 하나로 시작해서 두 번째 노드를 replica로 붙이면 서로를 찾아 하나의 클러스터로 합류합니다. 세 번째 노드를 추가하면 다른 노드들과 gossip을 시작하고 자동으로 풀메시에 합류합니다. 네 번째 노드가 들어와도 마찬가지로 클러스터의 다른 노드들을 자동으로 학습합니다.
이건 풀메시입니다. 모든 노드가 서로 통신합니다. 그래서 어느 시점부터는 이 클러스터링 링크 자체가 스케일링의 한계가 됩니다. 대략 1,000노드 근처에서 이 gossip에 CPU 1~2% 정도가 소모되는 걸 본다고 합니다. 그게 클러스터가 얼추 어디까지 갈 수 있는지를 정하는 수직 한계입니다. 이 부분을 개선하는 작업은 진행 중이지만 지금은 그렇게 동작합니다.
05:53 장애 감지와 자동 failover
Valkey는 장애를 자동으로 감지합니다. 모든 노드가 끊임없이 서로 대화하니 노드 하나가 죽으면 몇 초 안에 감지됩니다.
이 감지 시간을 통제하는 값이 cluster-node-timeout입니다. 다른 노드들이 이 노드의 ping 응답을 얼마나 기다릴지 정합니다. 기본값은 15초이고 2,000노드 규모까지는 꽤 잘 맞습니다. 더 작은 클러스터라면 1초, 500밀리초까지 낮출 수 있지만 스케일이 커질수록 그만큼 시간이 더 걸린다는 점은 감안해야 합니다.
노드 장애가 감지되면 replica가 자동으로 자신을 primary로 승격시킵니다. 별도 오퍼레이터 없이, 모든 노드가 풀메시로 연결돼 있는 한 저절로 일어납니다. 그런데 죽은 노드를 실제로 교체하는 일은 여전히 뭔가가 해줘야 합니다. 클러스터에 재합류한 노드는 자동으로 자신을 replica로 설정합니다. replica 없는 primary를 스스로 찾아 붙는 내장 로직이 있습니다.
Madelyn은 여기서 짧게 언급합니다. 완전히 자동화돼 있지 않은 지점, 이를테면 자동 스케일링 같은 건 오퍼레이터가 개입해야 합니다. 지금 개발 중인 오퍼레이터를 조금 뒤에 다룰 것이고 자리에 남아 있다면 바로 다음 세션에서 Joe가 이 얘기를 이어받습니다.
07:23 availability zone 배치
스케일에서 신경 써야 할 또 하나는 zone입니다. AWS, GCP, Digital Ocean 같은 클라우드 네이티브 시스템에는 모두 availability zone이 있습니다. 확인해야 할 것은 primary 분산과 replica 배치입니다.
primary가 zone 사이에 고르게 분산돼 있을까요? failover가 일어나려면 살아있는 primary들의 quorum이 필요합니다. 슬라이드 예시는 세 zone에 primary가 고르게 나뉘어 있어서 하나가 죽어도 failover가 성립합니다.
replica는 자기 primary와 다른 zone에 있어야 합니다. 그래야 zone 하나가 통째로 죽었을 때 데이터 사본을 전부 잃는 상황을 피합니다. 슬라이드의 나쁜 예시에서는 어떤 shard의 replica가 자기 primary와 같은 AZ에 있습니다. 미리 옮겨 놔야 하는 배치입니다. Valkey 자체에는 이걸 강제하는 내장 기능이 없고 개발 중인 오퍼레이터가 이 부분을 도와줄 예정입니다.
08:20 클라이언트는 프록시를 거치지 않는다 — RESP와 토폴로지
다음 주제는 프록시입니다. 일부 사용자는 Envoy 같은 프록시를 Valkey/Redis 클러스터 앞에 두고 토폴로지를 신경 쓰지 않으려 합니다. Madelyn은 이걸 낡은 발상이라 부릅니다. 과거 Redis/Valkey에 붙는 클라이언트가 꽤 부실했던 시절의 습관인데 Valkey가 쓰는 프로토콜인 RESP가 좀 독특한 것도 그 이유 중 하나입니다.
클라이언트는 클러스터 안의 노드 하나에 직접 붙는 방식으로 Valkey에 연결합니다. Kubernetes에서는 보통 headless service로 항상 정해진 노드 하나를 두고 거기 접속해 클러스터 토폴로지를 받아옵니다. 슬라이드는 CLUSTER SLOTS 응답을 그대로 보여줍니다. 단일 shard의 토폴로지 상태입니다.
이 응답에 담긴 정보를 하나씩 훑습니다. 먼저 slot입니다. 아직 설명하지 않았지만 Valkey에서 샤딩의 실제 원자 단위입니다. 모든 키는 특정 slot에 매핑되고 slot은 다시 노드에 할당됩니다. 여기가 대형 클러스터에서 또 하나 부딪히는 한계입니다. slot 수는 고정돼 있고 16384개뿐입니다. 2,000노드 근처로 가면 slot을 옮기려 할 때 slot 사이의 부하 편차가 너무 커집니다. 균일한 클러스터를 유지하기 어려워지고 스파이크가 나타나기 시작합니다.
shard는 slot 범위를 할당받고 각 노드에는 무작위 식별자가 붙습니다. 클러스터가 임의로 배정하는 값입니다. pod와 연관되게 직접 지정할 수도 있지만 프로덕션에서 그렇게 쓰는 사례는 많이 못 봤다고 합니다.
그 다음 유용한 정보는 엔드포인트입니다. IP와 포트, 그리고 hostname입니다. hostname은 클러스터에서 보이는 pod name일 때가 많은데 사용자 상당수는 CoreDNS 지연을 피하려고 DNS 조회 없이 IP로 직접 접속하는 쪽을 택합니다. 그래서 가능하면 IP 직결을 권장한다고 합니다. hostname은 보통 애플리케이션이 TLS 검증을 켰을 때만 쓰입니다.
마지막으로 유용한 정보가 availability zone입니다. 최신 Valkey 클라이언트는 대부분 zone-local read를 인식할 만큼 똑똑합니다. 캐싱에서는 중요하고 세션 스토어나 스트리밍처럼 성능이 앞서는 용도에서는 상대적으로 덜합니다. cross-AZ 비용과 지연을 피할 수 있다면 zone 내 읽기가 낫습니다.
이 프로토콜은 HTTP보다 이점이 많습니다. Envoy처럼 L7 계층에 맞춰 만들어진 걸 쓰면 RESP로 엔진에 직접 말할 때 얻는 이런 내부 이득을 못 얻습니다.
11:30 클라이언트 모범 사례
AWS에서 본 모범 사례 몇 가지를 소개합니다. 먼저 connection pooling을 쓸 것. Kubernetes 클러스터에서 가장 흔히 보는 문제는 pod가 수천 개인데 전부 클러스터에 연결을 새로 열어 개별 노드를 주저앉히는 것입니다. Envoy 같은 걸 앞에 두고 그 계층에서 connection pooling을 해주는 게 실제로 괜찮은 이유이기도 합니다. 개별 캐시 노드 각각에 수천 개씩 연결하지 않아도 됩니다.
connection retry를 제한하는 것도 중요합니다. 노드가 죽으면 다들 한꺼번에 연결을 다시 맺으려 드는 경우를 많이 봅니다. 방금 본 토폴로지 커맨드는 실행 비용이 꽤 큽니다. 엔진 안에서 이걸 최적화하는 데 시간을 많이 썼고 응답을 캐싱해서 빠르고 안정적으로 서빙되게 만들었습니다. 요청이 몰려도 서버가 죽지 않게 하려는 것입니다. Valkey와 Redis는 메인 스레드가 여전히 싱글스레드라는 점으로 악명이 높습니다. 대부분의 경우엔 상관없지만 연결이 한꺼번에 몰리는 워크로드에는 영향을 줍니다.
topology refresh를 제한하는 것도 있습니다. 새 클라이언트 Valkey Glide를 만든 이유이기도 합니다. 개별 연결마다 각자 topology refresh를 하는 대신 하나로 모아서 처리합니다. 예전 clustering 클라이언트에서 문제를 겪었다면 Glide로 옮기길 권합니다. 이런 최신 관행을 반영해 만든 클라이언트입니다.
13:09 관측성 — CLUSTER SLOT-STATS
다음은 리소스 관리입니다. AWS가 특히 개선하고 싶었던 건 대형 클러스터 안의 observability였습니다. 예전에는 pod 하나의 CPU나 네트워크가 높으면 “이 노드가 문제"라는 정도만 알 수 있었습니다. 그 시절 observability는 거기까지였습니다. 그래서 slot 단위로 더 깊이 들여다볼 수 있는 정보를 만들었습니다.
그 결과가 CLUSTER SLOT-STATS 커맨드입니다. 클러스터 내부를 들여다봐 실제 병목이 뭔지 가려냅니다. 고성능 인메모리 워크로드는 대개 네 가지 병목 중 하나에 걸립니다. network bytes in, network bytes out, CPU, 메모리입니다. Amazon 내부에서 실제 클러스터를 봤을 때 대부분은 메모리에 걸려 있었습니다. 그래서 key-count를 메모리 압박의 가장 좋은 근사치로 봤습니다.
어떤 노드가 뜨겁게 달아올라서 그 열을 다른 곳으로 옮기고 싶을 때, 곧 재샤딩을 하고 싶을 때 실제로 옮겨야 할 건 두 번째로 뜨거운 slot입니다. 직관과는 좀 어긋납니다. 다들 가장 뜨거운 slot을 옮겨야 한다고 생각하지만 오히려 가장 뜨거운 slot을 옮기는 게 작업량이 가장 큽니다. 그러니 노드에서 가장 뜨거운 slot이 아닌 것부터 먼저 덜어내는 쪽이 맞습니다.
이 커맨드는 fork 이후 Valkey 8부터 쓸 수 있습니다. 라이선스 변경 이후 Valkey에 넣으려 해온 기술 중 하나라고 소개합니다.
14:39 리소스 관리와 migration 도구
리소스 관리를 개선하며 본 또 다른 유용한 패턴이 있습니다. Kubernetes 클러스터에서 캐시를 CPU 집약적인 워크로드와 co-locate하려는 사람을 많이 봅니다. 캐싱은 대체로 메모리 지배적이고 웹 서비스나 배치 작업류는 CPU 지배적입니다. Kubernetes로 이 둘을 co-locate하면 양쪽 다 좋은 활용률을 얻습니다. 대신 캐시에 CPU limit을 너무 빡빡하게 걸지 않는 게 중요합니다. 동시 커맨드가 몰리면 지연 스파이크가 나기 시작합니다.
마이그레이션 이야기도 다시 나옵니다. 필요하면 가장 뜨거운 정보를 옮기지 말고 그 shard의 나머지를 옮기는 데 집중하라고 합니다. 관련 기술도 새로 만들었다고 합니다. 예전 Redis와 Valkey 시절에는 마이그레이션이 고통스러웠습니다. CLI 도구를 돌려서 한 shard에서 키를 지우고 다른 shard에 넣는 식이었습니다. 이제는 replication에 훨씬 가깝게 동작합니다. 데이터가 클러스터 안 shard 사이를 투명하게 옮겨갑니다.
슬라이드 노트: 슬라이드 20의 발표자 노트가 발표보다 더 직접적으로 말합니다. “대부분의 캐시는 CPU가 아니라 메모리로 스케일합니다. 그러니 co-locate하라. CPU limit은 걸지 마라 — 지연 스파이크가 납니다.” “노이지 네이버"도 함께 생각하라는 메모가 붙어 있습니다.
15:56 바통 터치 — Sarthak, “얼마나 멀리 스케일할 수 있는가”
Madelyn이 여기까지가 Amazon 고객들에게서 배운 내용이라며 바통을 Sarthak에게 넘깁니다. Sarthak은 지난 1년 남짓 시스템을 더 잘 스케일하려고 미리 손써온 조치들을 이야기하겠다고 소개합니다. 그 과정에서 만든 개선 사항도 함께 다룹니다.
Sarthak은 라이드헤일링이나 스트리밍 서비스처럼 스케일이 필요한 사례를 언급하며 질문을 던집니다. Valkey는 대체 얼마나 멀리 스케일할 수 있을까요? 이걸 증명하려고 지난 1년 동안 여러 달을 들여 테스트했다고 합니다. 대량 primary 실패, AZ 통째 실패, 1B RPS 쓰기 부하, TLS/non-TLS, 클라이언트 라이브러리 다섯 종, replica 1개/2개 구성입니다(아래 테스트 설계 절 참고).
16:45 테스트 설계
첫 번째 테스트는 2,000노드 클러스터에서 primary 노드를 하나씩 죽이는 것이었습니다. 1개, 100개, 250개, 330개, 최대 499개까지 서서히 늘렸습니다. 499인 이유가 있습니다. 2,000노드 클러스터(primary 1,000 · replica 1,000)가 스스로 회복하려면 primary 1,000개 중 최소 501개의 primary가 남아 quorum을 유지해야 합니다. 그래서 Valkey를 얼마나 몰아붙여야 스스로 치유하는지를 확인하려 했습니다.
노드를 2AZ, 3AZ 구성으로 분산해 replica와 primary를 고르게 배치한 다음 AZ 자체를 통째로 죽이는 테스트도 했습니다. 그래도 회복되는지 보려는 것이었습니다.
워크로드 쪽에서는 얼마나 밀어붙일 수 있는지를 확인했습니다. 25M RPS로 시작해서 100M RPS, 그리고 서서히 1B RPS까지 올렸습니다. TLS와 non-TLS 클러스터 둘 다 테스트했고 클라이언트 라이브러리도 여럿 검증했습니다. 사용자가 실제로 그렇게 접속하기 때문입니다. valkey-py, valkey-glide, valkey-go 같은 오픈소스 라이브러리가 이 대형 클러스터를 문제없이 지원하는지 확인했습니다. 1 primary / 1 replica 구성과 1 primary / 2 replica 구성도 각각 테스트했습니다.
슬라이드 노트: 발표에서 말하지 않은 숫자와 목록이 슬라이드 22의 노트에 있습니다. AZ 장애 테스트는 3AZ 구성에서 약 666노드를 한꺼번에 죽이는 규모였습니다. 클라이언트 목록도 발표보다 깁니다. valkey-py, valkey-glide, valkey-go에 더해 iovalkey, redisson까지 테스트했습니다. 워크로드는 슬라이드 24 본문에 SET · 512바이트로 적혀 있습니다.
슬라이드 밖 출처: 실험대 사양은 슬라이드에 없고 서면판인 1-billion-rps 블로그에만 있습니다. Valkey 클러스터는 r7g.2xlarge(8코어/64GB, aarch64), 부하 생성기는 c7g.16xlarge 750대입니다.
18:13 무엇이 통했는가 — 1B RPS와 선형 스케일링
Sarthak은 뭐가 잘못됐는지 이야기하기 전에 먼저 뭐가 통했는지부터 꺼냅니다. 몇 버전 전에 IO 스레드로 최적화 작업을 했고 그 결과 단일 노드가 1M RPS까지 스케일된다는 걸 증명했습니다. 이번에는 그 실험을 primary 1,000개로 확장할 수 있는지 확인했습니다. 됐습니다. 2,000노드 클러스터(primary 1,000개)에 쓰기 워크로드를 몰아넣었을 때 1B RPS까지 도달했습니다.
두 번째로 통한 것은 예상했던 대로였습니다. 노드 수가 늘어날수록 노드끼리 cluster gossip으로 통신하고 여기 들어가는 연산과 네트워크 대역폭도 늘어납니다. 노드가 늘어나면 이 리소스 사용량이 선형으로 스케일되길 기대했고 실제로 그렇게 나타났습니다.
슬라이드 노트: 슬라이드 25의 노트는 이 관측을 “steady-state 지표"라고 명시합니다. 정상 상태에서 gossip 메시지가 늘어나는 만큼 리소스 사용률도 선형으로 늘었다는 뜻입니다. 같은 슬라이드의 차트가 앞서 03:55 절의 “1,000노드 부근 CPU 1~2%” 수치의 유일한 근거입니다. 250노드 0.9%, 500노드 1.0%, 1,000노드 1.3%, 2,000노드 2.19%로 찍혀 있습니다. 이 수치는 슬라이드 밖 1차 출처가 없습니다(자매 문서 01 §8 참고).
19:31 재접속 폭풍과 connection throttling
이제부터 테스트 중 실제로 깨진 것들입니다. 첫 번째는 아주 특이한 사례였습니다. primary 노드를 대량으로(예를 들어 415개, 455개씩) 죽이는 중에 살아있는 다른 노드들이 죽은 노드에 계속 재접속을 시도했습니다. 문제는 이 재시도 간격이 100밀리초로 매우 좁았습니다. 연결이 성공하지 않을 걸 알면서도 연결을 열고 해제하는 데 연산이 계속 소모됐습니다.
해법으로 스마트한 connection throttling 메커니즘을 구현했습니다. cluster-node-timeout(앞서 Madelyn이 언급한 기본값 15초) 한 창 안에서 모든 노드가 공평하게 재접속을 시도하되 그 과정에서 Valkey 노드가 과부하에 빠지지 않도록 하는 게 목표였습니다.
슬라이드 노트: 슬라이드 27~29의 노트가 발표보다 구체적입니다. 재접속 시도로 소모된 연산은 최소 5%의 노드에서 100%로 꾸준히 유지됐고 특히 450개 이상의 primary를 죽인 경우 클러스터가 거의 회복하지 못했습니다. 스로틀링을 적용한 뒤에는 엔진 CPU가 100%까지 치솟는 대신 40% 선에서 묶였습니다.
20:30 failure report 처리와 radix tree
두 번째 문제는 그래프에 나타난 두 개의 스파이크입니다. primary 499개를 죽인 직후 순간입니다. 크게 튀었다가 자동으로 가라앉는 모습이 흥미로워서 원인을 팠습니다. 알고 보니 노드가 죽으면 서로 gossip을 계속하는데 그 안에서 failure report를 포함시키고 회수된 뒤 만료시키는 처리가 배후에서 많이 일어났습니다. 짧은 순간이어도 그 자체가 상당한 연산량이었습니다.
이걸 최적화했습니다. 이전에는 failure report를 리스트로 관리해서 O(n) 연산이었고 2,000노드 규모에서는 이게 비쌀 수 있었습니다. 이걸 radix tree로 바꿔서 노드를 타임스탬프 기준으로 묶을 수 있게 했습니다. 개선 폭은 컸습니다. 실질적으로 100%였던 게 죽인 직후 기준 28~30% 수준으로 내려갔습니다.
슬라이드 노트: 슬라이드 30의 노트는 규모를 구체적으로 밝힙니다. 2,000노드 중 499개를 죽인 뒤 남은 1,501개 노드가 이미 failed로 마킹된 노드를 두고도 계속 gossip과 report를 주고받았습니다. 슬라이드 31의 노트는 radix tree가 failure report 시각을 1초 단위로 묶어 저장한다고 밝힙니다. 만료 정리도 그만큼 싸집니다. 노트의 표현은 “rounded to every second"지만 PR 본문은 “rounded up to the nearest second”, 즉 올림이라고 적습니다.
21:35 shard vote 문제와 해법, 복구 시간
세 번째 문제는 아마 대형 클러스터를 스케일하면서 마주친 가장 큰 문제였을 것입니다. shard vote 문제입니다. 5개 shard 시스템을 생각해보겠습니다. 각 노드는 cluster bus로 서로 연결된 완전한 mesh입니다. 그런데 shard 두 개, 예를 들어 shard 3과 shard 1의 primary가 동시에 죽으면 무슨 일이 벌어질까요.
failover 시맨틱상 replica가 primary 자리를 넘겨받아 가용성을 유지합니다. shard 1이 죽으면 그 replica가 살아있는 primary들에게 “내가 primary가 되겠다, 투표해달라"고 요청합니다. 그런데 동시에 shard 3의 replica도 똑같이 투표를 요청합니다. 이 둘이 같은 시점에 투표를 요청하면 살아있는 primary들 사이에서 표가 갈릴 수 있습니다. 예를 들어 shard 2는 shard 1에만 투표하고 shard 4와 5는 shard 3에 투표하는 식입니다. 표가 갈리는 이유는 Valkey 안에서 epoch(시간 단위 하나) 당 primary가 딱 한 번만 투표할 수 있게 돼 있기 때문입니다. 여러 replica가 계속 소유권을 요구하는 루프에 빠지지 않게 하려는 설계입니다. 그래서 표가 갈리면 아무도 quorum을 얻지 못합니다.
Valkey 메인테이너 Binbin이 해법을 냈습니다. failover 요청이 들어오는 순서를 정하고 그 순서를 **사전식(lexicographic)**으로 매겼습니다. shard 1이 먼저 순번을 받으면 shard 1의 replica가 T1 시점에 독립적으로 투표를 요청해 표를 모두 얻고 새 primary로 승격됩니다. 그 다음 순번의 shard(예: shard 3)의 replica도 마찬가지로 표를 모두 얻어 승격됩니다. 이렇게 두 요청 사이에 작은 지터를 둬서 겹치지 않게 하고 표가 갈리는 상황을 없앴습니다. Sarthak은 이 해법이 Valkey 스케일링에 결정적이었다고 말합니다.
이 개선들을 다 넣은 뒤 최악의 경우 클러스터가 얼마나 빨리 회복하는지 다시 테스트했습니다. primary 1개를 죽이는 것부터 499개까지 돌렸습니다. 결과는 1분 이내 회복이었고 클러스터가 이런 장애 상황에서 스스로 치유한다는 것을 확인했습니다.
슬라이드 노트: 슬라이드 36은 이 결과를 그래프로 보여줍니다. 1,000 primary / 1,000 replica 구성에서 1·100·250·330·499개 primary 실패율별 recovery time을 그린 것입니다. 노트는 발표와 같은 문장을 반복합니다. “even that cluster is able to reliably recover in less than a minute.”
24:34 pub/sub 경량 헤더
마지막 개선사항은 pub/sub입니다. 많은 사용자가 Valkey를 pub/sub 메커니즘으로도 쓰고 싶어 하는데 cluster bus 연결을 그대로 재사용할 수 있어 편리하기도 합니다. 누군가 데이터를 발행하면 여러 구독자가 cluster bus 네트워크로 그걸 받습니다.
문제는 100바이트짜리 pub/sub 메시지를 보낼 때 붙는 오버헤드입니다. 이게 2KB나 됩니다. 이 2KB는 사실 클러스터가 공유하는 slot 정보 전체였는데 pub/sub 메시지는 slot을 신경 쓸 필요가 없으니 실제로는 무관한 정보입니다. 그래서 경량 메시지 헤더를 새로 도입해 2KB 헤더를 16바이트로 줄였습니다. 정말 필요한 정보만 남겼습니다. 이 수치들이 스케일에 비례한다는 걸 감안하면 cluster bus 메시지 하나하나마다 얼마나 절약됐는지 짐작할 수 있다고 덧붙입니다.
슬라이드 노트: 슬라이드 37의 노트는 이 작업의 주체를 밝힙니다. “Roshan introduced a light weight message header.” 노트에 따르면 이 개선은 Valkey 9 이전, 커뮤니티가 미리 만들어 둔 개선 중 하나였습니다.
25:43 결론
Sarthak이 발표를 정리합니다. Valkey 클러스터는 2,000노드까지 확장하고 1B RPS도 달성합니다. 여러 번 검증했다고 합니다. 물론 모범 사례를 따르는 게 중요합니다. 이 정도 스케일이 필요 없는 경우도 있겠지만 필요하다면 그 방법이 여기 있습니다.
Valkey 오퍼레이터는 바로 다음 세션에서 Joe가 이어서 다룰 주제라고 소개합니다. GitHub에서 활발히 개발 중이고 Valkey 프로젝트 차원에서 지원될 예정입니다. 참여를 독려합니다.
마지막으로 Redis를 쓴다면 Valkey로 옮기는 걸 고려해달라고 농담 섞어 말합니다. “더 낫다"는 짧은 티키타카가 두 발표자 사이에 오갑니다. Valkey 오퍼레이터는 매주 동기화 미팅을 하며 활발히 개발 중이고 Valkey 코어 자체에 관심이 있다면 프로젝트에 참여해달라고 마무리합니다.
27:15 Q&A
질의응답이 이어집니다.
Q1 — connection pooling. 1B RPS 테스트에 connection pooling을 썼을까요? Sarthak: 그렇습니다, connection pooling을 썼습니다. 그리고 사실 스케일 자체를 크게 망설이지 않고 그냥 노드를 최대한 많이 붙여서 늘렸다고 덧붙입니다. Madelyn이 이어받아 수치로 설명합니다. Valkey에서 요청 하나는 보통 1마이크로초 단위지만 연결 하나를 새로 맺는 데는 수백 마이크로초가 듭니다. 요청마다 새 연결을 만든다면 그만큼 CPU를 낭비합니다.
Q2 — 프로파일링 도구. 이 모든 작업을 하면서 무슨 프로파일링 도구를 썼을까요? Sarthak: 그냥 평범한 perf 도구였습니다. 대부분의 Linux 시스템에 기본으로 깔려 있습니다. 지정한 시간 동안 캡처한 다음 그걸로 flame graph를 만들 수 있습니다. 어떤 함수에 실제로 얼마나 연산이 쓰였는지 알 수 있어서 시스템의 거의 모든 걸 알게 해주는 도구라고 말합니다. 도구 이름은 그냥 perf입니다.
슬라이드 이미지 노트: 발표는 flame graph를 화면에 띄웠지만 콜스택을 말로 설명하지는 않습니다. 슬라이드 이미지에는 재접속 폭풍의 콜스택이 그대로 담겨 있습니다. clusterCron → connSocketConnect → anetTcpGenericConnect → connect → __sys_connect → tcp_v4_connect 경로, 그리고 연결 해제 경로 freeClusterLink → connSocketClose → __close, gossip 처리 경로 clusterReadHandler → clusterProcessPacket → clusterProcessGossipSection입니다. CPU 스파이크 그래프 이미지에서는 100%까지 치솟는 계열이 engine_cpu_percent_p99이고 p90·avg는 한 자릿수에 깔려 있는 것도 읽힙니다. 슬라이드 29의 툴팁 스냅샷은 p99 24.363 / p90 6.2 / avg 5.80입니다. 발표는 이 percentile 구분을 말로 하지 않습니다.
Q3 — 초기화 시 primary/replica 배정. 마지막 질문입니다. Valkey cluster를 쓸 때 초기 구성 시점에 pod들이 전부 primary(master)가 돼버리고 클러스터를 이루지 못하는 상황을 어떻게 막을까요? Madelyn: 오늘 기준으로는 좋은 해법이 없습니다. 바로 오퍼레이터가 풀어야 할 문제라고 합니다. 기본 방식은 Valkey 노드를 전부 클러스터에 넣고 일부는 별도 배포 세트로 두어 스스로 replica로 구성하게 하는 식입니다. 노드를 클러스터에 넣고 replica로 설정해두기만 하면 replica migration 도구 또는 설정이 알아서 적절한 수의 노드를 replica로 맞춰줍니다. replica 없는 primary가 있으면 유휴 노드가 그 자리를 채웁니다. Madelyn은 이걸 “가난한 자의 해법"이라 부르며 더 나은 답은 shard를 first-class 구성요소로 지원하는 오퍼레이터를 써보는 쪽이라고 말합니다.
질문자가 감사를 표하며 “Valkey 9는 꽤 좋은 업데이트"라고 덧붙이고 발표가 마무리됩니다.
원본
- 발표 영상(CNCF, YouTube) — youtube.com/watch?v=t0qax1qQm14
- 세션 페이지(sched.com) — kccnceu2026.sched.com/event/2CW5d
- 슬라이드 덱(PPTX, 54장) — Scaling Large Clusters with Valkey FINAL.pptx