포스트

Dragonfly로 Redis를 바꿔도 될까: 호환성, 멀티코어, 롤백 검증

Dragonfly의 shared-nothing 샤드, DashTable, VLL을 따라가며 Redis 프로토콜 호환성, 다중 키, Lua, 영속성, 실제 부하 벤치마크와 안전한 이관을 설명합니다.

Dragonfly로 Redis를 바꿔도 될까: 호환성, 멀티코어, 롤백 검증

Dragonfly는 멀티코어를 활용하는 shared-nothing 구조와 Redis 호환 프로토콜을 내세워 단일 노드의 인메모리 처리량을 높이려는 데이터스토어입니다. 그러나 엔드포인트만 바꾸면 모든 Redis 워크로드와 운영 기능이 동일하게 동작한다고 볼 수는 없습니다. 마이그레이션은 명령, 정합성, 영속성, 장애 복구와 실제 p99를 검증한 뒤 되돌릴 수 있는 트래픽부터 진행해야 합니다.

shared-nothing 구조는 무엇을 바꾸는가

단일 실행 흐름이 대부분의 명령을 처리하는 구조에서는 한 코어가 병목이 될 때 노드를 더 나누는 방식으로 확장할 수 있습니다. Dragonfly의 접근은 코어별 실행 경로와 데이터 샤드를 두어 여러 코어가 독립적으로 요청을 처리하도록 설계하는 것입니다. 공유 상태와 락 경합을 줄이는 대신 어떤 키가 어느 샤드에 속하는지와 여러 샤드가 관련된 명령을 조정해야 합니다.

단일 키 GET, SET이 고르게 분산되면 샤드 독립성이 잘 작동할 수 있습니다. 반대로 인기 키 하나에 요청이 집중되거나 여러 키를 한 번에 다루는 명령이 서로 다른 샤드에 걸치면 조정 비용이 생길 수 있습니다. “멀티스레드”라는 구조만으로 모든 요청이 코어 수에 비례해 확장된다고 단정할 수 없습니다.

원문은 Seastar의 shared-nothing, 이벤트 루프 개념을 설명에 사용합니다. 실제 Dragonfly 버전의 런타임과 구현 의존성은 Dragonfly 저장소에서 확인해야 합니다. 개념적 유사성과 특정 프레임워크를 그대로 사용한다는 주장을 구분하는 편이 안전합니다.

Redis 호환성은 어떤 목록으로 확인할까

먼저 운영에서 실제 호출한 명령, 옵션과 응답 오류를 수집합니다. GET, SET 같은 기본 명령뿐 아니라 Lua/EVAL, MULTI/EXEC, pub/sub, streams, sorted set, 만료, blocking 명령과 관리자 명령을 포함합니다. 라이브러리의 startup probe나 클러스터 탐색처럼 애플리케이션 코드 밖에서 호출되는 명령도 로그에 나타날 수 있습니다.

호환성은 명령 이름이 존재하는지보다 의미가 같은지로 검사합니다. 만료 시각의 경계, 트랜잭션 중 오류, 연결 종료와 재시도, 스크립트 원자성, 메모리 부족과 eviction 응답을 비교합니다. 클라이언트가 특정 오류 문자열이나 서버 정보에 의존하는지도 봅니다. “RESP를 이해한다”는 사실이 Redis의 모든 서버 동작과 같은 것은 아닙니다.

현재 Dragonfly 문서Redis 문서에서 사용하는 명령의 지원 범위를 대조하고, 자신의 버전으로 contract test를 실행합니다. 문서에 지원된다고 적힌 기능도 값 크기, 키 수와 장애 상황에서 다시 시험해야 합니다. 지원되지 않은 명령을 드물게 쓴다는 이유로 무시하면 월말 배치나 복구 작업에서 늦게 실패할 수 있습니다.

다중 키와 Lua가 중요한 이유는 무엇인가

shared-nothing 구조에서 같은 샤드의 단일 키 작업과 여러 샤드를 가로지르는 작업은 비용 구조가 다릅니다. 장바구니 갱신이나 rate limit처럼 여러 키를 한 스크립트에서 읽고 쓰면 조정과 원자성 구현이 중요합니다. 평균 GET 벤치마크가 좋아도 이런 핵심 경로가 느리거나 의미가 다르면 교체할 수 없습니다.

워크로드에서 키 해시 분포를 재현하고 MGET, set 연산, 트랜잭션과 실제 Lua 스크립트를 그대로 실행합니다. 성공 응답뿐 아니라 경쟁 요청에서 불변식이 유지되는지 봅니다. 예를 들어 재고가 음수가 되지 않는지, 한 번만 발급해야 하는 토큰이 중복되지 않는지를 결과 데이터로 검증합니다.

여러 키 명령이 많은 서비스는 Dragonfly의 특정 기능과 현재 구현 범위를 확인한 뒤 별도 지연 예산을 둡니다. 단순 캐시와 세션, 잠금, 큐처럼 정합성 기대가 높은 사용처를 같은 마이그레이션으로 묶지 않는 것이 좋습니다.

DashTable과 VLL 예제는 무엇을 보여 주는가

원문은 데이터 지역성을 높이는 DashTable과 eviction을 위한 VLL 개념을 다음 의사코드로 설명합니다. 실제 내부 API가 아니라 샤드별 메모리에서 만료 또는 축출 후보를 회수하는 개념적 예로 읽어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Dragonfly 내부 VLL Eviction 동작을 보여주는 의사코드(Pseudo-code)
void VllEvict() {
  // 스레드별로 독립 할당된 메모리 한계치 확인
  while (local_memory_usage > shard_limit) {
    auto& shard = GetLocalShard(); // 락(Lock) 없이 100% 안전한 로컬 접근!
    
    // 포인터 체이싱 없이 DashTable에서 순차적으로 데이터를 가져옴
    auto entry = shard.Dashtable.PopFront(); 
    
    if (entry.IsExpired() || entry.ttl < current_time) {
      shard.Reclaim(entry); // 즉시 메모리 반환
    }
  }
}

운영에서 중요한 질문은 알고리즘 이름보다 메모리 압박 때 어떤 키가 사라지는가입니다. TTL이 없는 키와 있는 키, 작은 인기 키와 큰 일회성 키를 섞고 maxmemory 근처에서 hit rate, eviction 수, 메모리 회수 지연과 p99를 측정합니다. 캐시 모드와 영속 데이터 용도의 정책을 구분해야 합니다.

샤드별로 메모리 사용이 불균형하면 전체 여유가 있어도 특정 샤드가 압박을 받을 수 있는지 확인합니다. hot key가 처리량뿐 아니라 메모리와 eviction 분포에 어떤 영향을 주는지도 봅니다. 동일 데이터 크기만 맞추지 말고 실제 키, 값 크기 분포와 TTL 패턴을 재현해야 합니다.

벤치마크는 어떤 부하로 만들어야 하나

벤더나 블로그의 최대 RPS는 하드웨어, 파이프라인, 값 크기와 영속성 설정이 다르면 비교하기 어렵습니다. 운영 명령 비율, 동시 연결, TLS 여부, 네트워크 위치, pipelining, 키 분포와 데이터 크기를 고정한 뒤 Redis 기준선과 Dragonfly를 같은 장비나 동등한 비용 조건에서 측정합니다.

평균 처리량뿐 아니라 p50, p95, p99, timeout, CPU 코어별 사용량, RSS, 네트워크와 eviction을 기록합니다. snapshot이나 복제, 백업이 동작하는 동안의 지연도 봅니다. 최대 처리량이 높아도 꼬리 지연과 장애 복구가 나쁘면 사용자 경험과 SLO가 개선되지 않습니다.

부하를 단계적으로 올리고 안정 상태와 과부하 뒤 회복을 분리합니다. 클라이언트 재시도 폭주, hot key, 큰 값, 만료가 동시에 몰리는 구간과 연결 재수립을 포함합니다. 코어 수를 늘렸을 때 성능과 비용이 어떻게 변하는지, 작은 데이터셋에서 고정 오버헤드가 있는지도 자신의 환경에서 확인합니다.

영속성과 복제는 무엇을 검증할까

캐시라면 데이터 손실 뒤 원본에서 다시 채울 수 있는지와 stampede를 막을 수 있는지가 중요합니다. 세션, 작업 큐, 상태 저장에 쓴다면 snapshot, 복제와 장애 조치의 의미가 더 엄격합니다. Redis에서 기대하던 RPO, RTO가 Dragonfly의 현재 기능과 설정으로 충족되는지 별도 시험해야 합니다.

프로세스 강제 종료, 노드 전원 차단, 디스크 가득 참, snapshot 중 중단과 replica 지연을 재현합니다. 재시작 뒤 키 수만 맞추지 말고 TTL, 자료구조 내용과 애플리케이션 불변식을 비교합니다. 장애 조치 중 성공 응답을 받은 쓰기가 어느 노드에 남는지도 확인합니다.

백업 파일을 만드는 것과 복구 가능한 것은 다릅니다. 깨끗한 새 노드에 복원하고 애플리케이션 contract test를 수행하며, 사용한 서버 버전과 설정을 기록합니다. 업그레이드와 다운그레이드 경로, 이전 Redis로 돌아갈 때 데이터 형식을 어떻게 옮길지도 마이그레이션 전에 정합니다.

컨테이너 예제로 바로 교체해도 될까

원문에는 Redis 이미지 대신 Dragonfly 이미지를 같은 6379 포트로 실행하는 다음 예가 있습니다. 이는 로컬 연결을 확인하는 시작점일 뿐 무중단 교체 절차가 아닙니다.

1
2
3
4
5
6
7
8
9
# 기존 docker-compose.yml에서 이미지 이름만 쓱 바꿔치기하면 끝납니다.
version: '3.8'
services:
  cache:
    # image: redis:7.0-alpine
    image: docker.dragonflydb.io/dragonflydb/dragonfly
    ports:
      - "6379:6379"
    command: ["--maxmemory=8GB", "--cache_mode=true"]

실제 환경에는 인증, TLS, 네트워크 정책, health check, 메모리 제한, 볼륨, snapshot과 모니터링이 필요합니다. 컨테이너 메모리 제한과 –maxmemory의 관계를 확인하고 OOM 때 프로세스와 데이터가 어떻게 되는지 시험합니다. 이미지 태그를 고정하지 않으면 재배포 때 다른 버전이 올라올 수도 있습니다.

엔드포인트가 같아도 클라이언트 pool, timeout과 재시도 설정이 새 서버의 지연 특성과 맞는지 봅니다. 준비되지 않은 서버를 healthy로 표시하거나 연결 성공만으로 쓰기 가능하다고 판단하지 않습니다. 배포 템플릿은 기능, 장애 시험을 통과한 설정의 결과여야 합니다.

안전한 마이그레이션은 어떤 순서인가

첫 단계는 명령 인벤토리와 contract test입니다. 운영 트래픽 샘플을 민감 정보 없이 재생해 응답과 최종 상태를 비교하고, 지원되지 않거나 의미가 다른 경로를 제거합니다. 그다음 Dragonfly를 보조 캐시나 되돌리기 쉬운 읽기 용도로 배치해 hit rate와 지연을 봅니다.

이중 쓰기나 복제를 쓴다면 순서, 실패와 재시도의 의미를 정의합니다. 두 서버 중 하나만 성공했을 때 어느 쪽을 정답으로 볼지, 차이를 어떻게 감지, 복구할지 필요합니다. 읽기 shadow는 사용자 결과를 바꾸지 않고 응답 차이를 찾을 수 있지만 TTL과 시간 의존 값을 비교할 때 허용 오차를 정해야 합니다.

전환은 일부 인스턴스나 키 공간부터 시작하고 p99, 오류, 데이터 차이와 자원 사용이 기준을 넘으면 Redis로 돌립니다. 롤백 동안 Dragonfly에만 들어간 쓰기를 어떻게 처리할지도 미리 정합니다. 전체 트래픽 전환 뒤에도 snapshot, 복원과 버전 업그레이드 훈련을 통과해야 운영 완료입니다.

Redis를 유지하는 편이 나은 경우는 언제인가

현재 Redis가 SLO와 비용을 만족하고 데이터셋이 작으며 복잡한 Lua, 모듈, 클러스터 기능에 크게 의존한다면 교체 위험이 이득보다 클 수 있습니다. 팀이 Redis 장애 대응과 운영 도구에 익숙하고 Dragonfly의 현재 기능에서 같은 지원 경로를 찾지 못했다면 먼저 병목을 계측하는 편이 낫습니다.

반대로 단일 노드의 CPU 병목이 확인되고 워크로드가 지원 명령 안에 있으며 멀티코어 scale-up이 운영 복잡도를 줄일 가능성이 있다면 파일럿 가치가 있습니다. 결정은 “Redis가 오래됐다”거나 “멀티스레드가 새롭다”가 아니라 같은 비용에서 SLO, 정합성, 복구가 실제로 좋아지는가입니다.

원문과 버전 확인

함께 읽으면 이해가 이어지는 글

자주 묻는 질문

Dragonfly는 Redis와 100% 호환되나요?

프로토콜과 많은 명령의 호환을 목표로 하지만 사용하는 명령, 옵션, Lua, 트랜잭션, 클라이언트 동작과 운영 기능이 모두 같다고 가정하지 말고 실제 워크로드로 확인해야 합니다.

멀티코어 서버라면 Dragonfly가 항상 더 빠른가요?

아닙니다. 키 분포, 값 크기, 다중 키 명령, Lua, 연결 수, 영속성과 네트워크가 결과를 바꾸므로 평균 처리량뿐 아니라 p99, 메모리, 오류, 복구를 같은 장비에서 비교해야 합니다.

Redis에서 무중단으로 전환하려면 어떻게 하나요?

명령 인벤토리와 호환성 테스트를 통과한 뒤 복제 또는 이중 쓰기의 의미를 검증하고 일부 읽기 트래픽부터 전환하며, 데이터 차이와 지연이 기준을 넘으면 기존 Redis로 돌아갈 경로를 유지해야 합니다.

References

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.