키-값: Redis·RocksDB·etcd
키로 값을 바로 찾는 단순한 모델이다. 메모리 캐시, 임베디드 저장 엔진, 합의 기반 설정 저장소로 쓰임새가 갈린다.
목차 (8)
Redis: 메모리 위의 자료구조 서버
키-값 데이터베이스의 질문은 하나다. "이 키의 값은?" 조인도 복잡한 조건도 없으니 키만 알면 곧바로 값에 닿고, 그만큼 빠르고 나누기 쉽다. 같은 모델 위에서도 데이터를 메모리에 두느냐, 디스크에 두느냐, 여러 대가 합의하느냐에 따라 쓰임새가 완전히 달라진다. 이 노트의 세 엔진이 그 세 갈래다.
Redis는 모든 데이터를 메모리에 두는 서버다. 값이 바이트 덩어리에 그치지 않고 자료구조라는 점이 특징이다. 문자열, 해시, 리스트, 집합, 점수로 정렬되는 정렬 집합, 추가 전용 로그인 스트림 같은 자료형마다 전용 명령이 있어, "순위표에 점수를 더하고 상위 10명을 달라" 같은 일을 서버 안에서 한 번에 처리한다.
명령은 메인 스레드 하나가 이벤트 루프로 하나씩 처리한다. 락이 필요 없어 명령 하나하나가 저절로 원자적이고, 메모리 접근만 하므로 한 명령은 마이크로초 단위로 끝난다. 병목은 대개 CPU가 아니라 네트워크와 시스템 호출이라 6.0부터는 소켓 읽기·파싱과 응답 쓰기를 I/O 스레드에 나눠 맡길 수 있게 했다. 반대로 말하면 큰 키 하나를 지우거나 KEYS *처럼 전체를 훑는 명령은 그동안 다른 모든 클라이언트를 세운다. 여러 명령을 묶을 때는 MULTI/EXEC 트랜잭션이나 Lua 스크립트를 쓰며, 이것도 실행되는 동안 다른 명령이 끼어들지 않는다는 뜻의 원자성이지 롤백은 없다.
Redis: RDB·AOF 영속성과 복제
메모리에만 두면 꺼질 때 다 사라진다. Redis는 두 가지 영속성을 준다. RDB는 정한 간격마다 메모리 전체를 파일 하나로 떠 두는 스냅샷이다. Redis가 fork()로 자식 프로세스를 만들면 운영체제의 copy-on-write 덕에 자식은 그 순간의 메모리를 그대로 보며 파일을 쓰고, 부모는 계속 요청을 받는다. AOF는 쓰기 명령을 받는 대로 파일 끝에 붙이는 로그로, 다시 켤 때 처음부터 재생한다.
AOF의 내구성은 fsync 정책이 정한다. 기본값 everysec는 1초마다 백그라운드 스레드가 fsync하므로 최악에 1초 분량을 잃고, always는 명령마다 fsync해 잃지 않지만 훨씬 느리다. AOF는 계속 길어지므로 Redis는 현재 상태를 만드는 가장 짧은 명령들로 파일을 다시 쓴다(rewrite). 7.0부터는 이 결과가 base 파일, 그 뒤의 증분 파일, 둘을 묶는 manifest로 나뉜다.
복제는 기본이 비동기다. 복제본이 처음 붙으면 주 서버가 RDB를 만들어 보내고(전체 동기화), 그 뒤로는 쓰기 명령의 흐름을 그대로 보낸다. 잠깐 끊겼다 다시 붙으면 복제 ID와 오프셋으로 놓친 부분만 백로그에서 받아 간다. 주 서버가 쓰기에 응답한 뒤 복제본에 닿기 전에 죽으면 그 쓰기는 사라진다. WAIT 명령으로 복제본 몇 대가 받았는지 기다릴 수 있지만, 그래도 강한 일관성을 약속하지는 않는다. 장애 때 복제본을 승격하는 일은 Sentinel이나 아래의 Cluster가 맡는다.
Redis Cluster: 16384개의 해시 슬롯
한 대의 메모리를 넘으면 데이터를 나눠야 한다. Redis Cluster 명세는 키 공간을 해시 슬롯 16384개로 나눈다. 키의 슬롯은 CRC16(키)를 16384로 나눈 나머지이고, 슬롯 구간을 마스터마다 나눠 맡는다. 노드를 더하면 키를 하나하나 재해시하지 않고 슬롯 단위로 옮긴다.
클라이언트는 슬롯 → 노드 표를 들고 있다가 키를 맞는 노드로 바로 보낸다. 표가 낡았으면 노드가 MOVED 응답으로 바른 노드를 알려 주고, 슬롯을 옮기는 중이면 ASK로 잠시 다른 노드에 물어보게 한다. 여러 키를 한 명령에 쓰는 MGET이나 트랜잭션은 키가 모두 같은 슬롯일 때만 된다. 그래서 함께 다룰 키에는 {user:1}처럼 같은 해시 태그를 붙인다. 노드끼리는 별도 버스로 상태를 주고받다가 마스터가 죽으면 그 복제본 하나를 승격하는데, 복제가 비동기이므로 명세 스스로 "응답을 받은 쓰기도 잃을 수 있다"고 밝힌다.
메모리가 maxmemory에 닿으면 축출 정책(eviction policy)이 정한 대로 한다. 기본값 noeviction은 새 쓰기를 오류로 거부하고, 캐시로 쓸 때는 allkeys-lru나 allkeys-lfu로 덜 쓰는 키를 지운다. 이때 LRU는 모든 키를 줄 세우지 않고 몇 개를 표본으로 뽑아 그중 가장 오래된 것을 지우는 근사다.
Redis 라이선스와 Valkey
Redis는 오랫동안 BSD 라이선스였다. 2024년 3월 20일 Redis사는 7.4부터 RSALv2와 SSPLv1 이중 라이선스로 바꾼다고 발표했다. 둘 다 소스는 공개하지만 클라우드 사업자가 그대로 관리형 서비스로 파는 것을 막는 조건이 있어 OSI 기준의 오픈 소스가 아니다. 8일 뒤인 3월 28일 리눅스 재단이 Valkey를 발표했다. 마지막 BSD 판인 Redis 7.2.4에서 갈라져 나온 포크로, 기존 메인테이너 일부와 여러 클라우드 회사가 참여했고 지금도 BSD-3 라이선스다.
약 1년 뒤 Redis 8부터 AGPLv3가 세 번째 선택지로 더해졌다(2025년 5월 1일). AGPLv3는 OSI가 인정하는 오픈 소스 라이선스지만, 고쳐서 네트워크 서비스로 제공하면 고친 소스를 공개해야 하는 강한 카피레프트다. 그래서 지금은 "Redis 8(AGPLv3 등 셋 중 선택)"과 "Valkey(BSD)"가 나란히 있고, 2024년 이후의 새 기능은 두 쪽이 각자 만든다. 명령과 프로토콜은 대부분 호환되지만 갈라진 뒤에 생긴 기능은 서로 다를 수 있으니, 관리형 서비스를 고를 때 어느 쪽인지 확인한다. (2026년 10월 기준)
RocksDB: 앱 안에 넣는 LSM 저장 엔진
RocksDB는 서버가 아니라 라이브러리다. Facebook(지금의 Meta)이 Google의 LevelDB를 바탕으로 빠른 SSD와 많은 코어에 맞춰 다시 만든 임베디드 키-값 저장소이고, 정렬된 바이트 키와 바이트 값을 Put·Get·Delete·범위 반복자로 다룬다. 다른 데이터베이스의 아래층으로 많이 쓰인다. MySQL의 MyRocks, 분산 키-값 저장소 TiKV, Kafka Streams의 상태 저장소, Ceph BlueStore의 메타데이터가 RocksDB 위에 있다.
구조는 저장 엔진 노트의 LSM 트리 그대로다. 쓰기는 WAL과 메모리의 MemTable에서 끝나고, 디스크의 정렬된 파일(SST)은 한 번 쓰면 고치지 않는다.
Put(k, v). 먼저 WAL 파일 끝에 이어 쓰고, 메모리의 MemTable(기본은 스킵 리스트)에 넣으면 끝이다. 디스크의 정렬된 파일은 건드리지 않아 쓰기가 빠르다.
Column family는 한 DB 안의 독립된 키 공간이다. 저마다 MemTable과 SST 파일, 설정(압축, compaction 방식)을 따로 갖지만 WAL은 함께 쓴다. 그래서 여러 column family에 걸친 쓰기 묶음(WriteBatch)도 원자적으로 커밋되고, 성격이 다른 데이터(예: 자주 바뀌는 메타데이터와 큰 값)를 한 DB 안에서 다르게 튜닝할 수 있다.
RocksDB: compaction 방식 고르기
LSM의 성능은 compaction이 정한다. 같은 데이터를 몇 번 다시 쓰는지(쓰기 증폭), 지워질 옛 값이 얼마나 자리를 차지하는지(공간 증폭), 키 하나를 찾으려 몇 군데를 봐야 하는지(읽기 증폭)는 하나를 줄이면 다른 하나가 는다. RocksDB는 compaction 방식을 골라 이 균형을 정하게 한다.
- Leveled(기본): 레벨마다 전체 크기를 약 10배씩 키우고, L1부터는 한 레벨 안에서 파일 범위가 겹치지 않게 유지한다. 키 하나는 레벨마다 많아야 한 파일에 있어 읽기가 싸고 공간 낭비도 적지만, 같은 데이터가 레벨을 내려갈 때마다 다시 쓰인다.
- Universal: 비슷한 크기의 정렬된 묶음끼리만 합친다(tiered). 다시 쓰기가 적어 쓰기가 아주 많은 일에 맞지만, 합치는 동안 옛 묶음과 새 묶음이 함께 있어 최악에는 공간이 두 배까지 든다.
- FIFO: 합치지 않고 전체 크기가 한도를 넘으면 가장 오래된 파일을 통째로 지운다. 일정 기간만 두는 로그·지표처럼 지우기를 시간이 대신해 주는 데이터에 쓴다.
etcd: 합의로 지키는 설정 저장소
etcd는 작지만 절대 틀리면 안 되는 데이터를 위한 키-값 저장소다. 노드 3대나 5대가 Raft 합의로 같은 순서의 로그를 나눠 갖기 때문에, 과반이 살아 있는 한 어느 노드에 물어도 같은 답(선형화 가능한 읽기)을 준다. Raft 자체의 동작은 복제·샤딩·합의 노트에 있다. 대표 사용자는 Kubernetes다. 클러스터의 모든 객체(파드, 서비스, 설정)가 etcd에 들어 있고, kube-apiserver만 etcd와 직접 이야기한다.
kube-apiserver가 PUT /registry/pods/default/web을 보낸다. 쓰기는 늘 리더가 받는다. 팔로워에 닿으면 리더에게 넘긴다.
etcd의 데이터 모델은 MVCC다. 쓰기가 커밋될 때마다 클러스터 전체에 하나뿐인 revision이 1씩 오르고, 키의 새 판은 (키, revision)으로 B+트리 파일 저장소(bbolt)에 쌓인다. 그래서 "revision 1000 시점의 값"을 읽거나, 값이 내가 읽은 revision 그대로일 때만 바꾸는 트랜잭션(compare-and-swap)을 쓸 수 있다. watch는 이 revision 흐름을 구독하는 API다. 끊겼다 다시 붙어도 마지막으로 본 revision 다음부터 받으므로 변경을 놓치지 않는다. Kubernetes 컨트롤러가 "원하는 상태"와 "지금 상태"를 맞추는 방식이 이 watch 위에 서 있다. 시간이 지나면 끝나는 lease를 키에 붙여 리더 선출이나 생존 신호에도 쓴다.
옛 판은 compaction으로 지우기 전까지 남고, compaction 뒤에도 파일의 빈자리는 defrag를 해야 돌아온다. etcd는 큰 데이터를 담는 용도가 아니다. 공식 한도는 요청 하나 1.5MiB, 저장소 기본 2GiB이고 권장 최대는 8GiB다. 쓰기마다 과반의 디스크 fsync를 기다리므로 디스크 지연이 곧 쓰기 지연이다.
고르는 법
| 기준 | Redis / Valkey | RocksDB | etcd |
|---|---|---|---|
| 형태 | 네트워크 서버 | 앱에 넣는 라이브러리 | 3·5대 클러스터 서버 |
| 데이터 위치 | 메모리(디스크는 복구용) | 디스크(SSD), LSM | 디스크, B+트리(bbolt) |
| 값 | 자료구조(해시·정렬 집합·스트림 등) | 바이트, 정렬된 키로 범위 조회 | 바이트, 판(revision)과 watch |
| 일관성 | 비동기 복제, 장애 때 최근 쓰기 유실 가능 | 한 프로세스 안, 복제는 위층 몫 | Raft 합의, 선형화 가능 |
| 크기 | 메모리 한도(Cluster로 나눔) | 디스크 한도까지 TB 단위 | 수 GiB(권장 8GiB 이하) |
| 잘 맞는 곳 | 캐시, 세션, 순위표, 속도 제한, 큐 | 다른 DB·서비스의 저장 계층, 쓰기가 많은 로컬 저장 | 설정, 서비스 발견, 리더 선출, 분산 락 |
"빠른 키-값"이 필요하면 먼저 데이터를 잃어도 되는지 묻는다. 다시 만들 수 있는 캐시라면 Redis(또는 Valkey)가 거의 늘 답이다. 원본 데이터라면 Redis를 주 저장소로 쓰기 전에 유실 창을 받아들일 수 있는지 따지고, 작지만 모든 노드가 같은 값을 봐야 하는 조정용 데이터라면 etcd 같은 합의 저장소를 쓴다. 직접 저장 엔진을 품은 시스템을 만든다면 RocksDB가 출발점이다.
- Redis Documentation — Redis
- Understand Redis data types — Redis
- Redis persistence — Redis
- Redis replication — Redis
- High availability with Redis Sentinel — Redis
- Redis cluster specification — Redis
- Key eviction — Redis
- Redis Adopts Dual Source-Available Licensing — Redis 블로그(2024.3)
- Linux Foundation Launches Open Source Valkey Community — Linux Foundation(2024.3)
- Redis 8 is now available under AGPLv3 — Redis 블로그(2025.5)
- Valkey — Linux Foundation
- RocksDB Overview — RocksDB Wiki
- Column Families — RocksDB Wiki
- Compaction — RocksDB Wiki
- Operating etcd clusters for Kubernetes — Kubernetes Documentation
- Data model — etcd v3.7 Documentation
- etcd3 API — etcd v3.7 Documentation
- System limits — etcd v3.7 Documentation