Redis 에 값을 저장할 때 key 하나 하나에 들어가는 overhead는 50 bytes 정도 (Redis 3.2 기준) 로 생각보다 큽니다.
만약 관리의 편리성 때문에 String type 을 많이 쓰는 서비스가 있다면 다른 자료구조를 사용할 때 보다 많은 Key를 사용하게 될 것인데
이때 String 을 Hash 로 바꾸면 데이터는 온전히 보전하면서 Key 자체 개수를 줄여 메모리를 좀 더 효율적으로 쓸 수 있지 않을까? 하는 생각에 테스트를 해보게 되었습니다.
data Type 별 Key Memory usage
아래는 Redis 5.0.8 기준으로 테스트 진행했습니다.
Key Overhead는 Redis 버전마다 다르고 5.0.8 에서는 정확한 수치는 찾을 수 없었지만 대략 40 byte 라고 가정했습니다.
계산식은 http://redisgate.kr/redis/configuration/server_memory.php 을 참고했습니다.
- String
127.0.0.1:6001> set "" ""
OK
127.0.0.1:6001> type ""
string
127.0.0.1:6001> memory usage ""
(integer) 46
=> (Key Overhead 40 Byte) + key size + (String Overhead ?? Byte) + value size
- List
127.0.0.1:6001> lpush "" ""
(integer) 1
127.0.0.1:6001> type ""
list
127.0.0.1:6001> memory usage ""
(integer) 129
=> (Key Overhead 40 Byte) + key size + (List Overhead ?? Byte) + value size
- Set
127.0.0.1:6001> sadd "" ""
(integer) 1
127.0.0.1:6001> type ""
set
127.0.0.1:6001> memory usage ""
(integer) 200
=> (Key Overhead 40 Byte) + key size + (Set Overhead ?? Byte) + value size
- Hash
127.0.0.1:6001> hset "" "" ""
(integer) 1
127.0.0.1:6001> type ""
hash
127.0.0.1:6001> memory usage ""
(integer) 59
=> (Key Overhead 40 Byte) + key size + field size + (Hash Overhead ?? Byte) + value size
Redis data type 별로 overhead 가 달라 계산식은 다르지만 공통적으로 Key Overhead 가 꽤 크니
몇 억개의 String 을 쓰는 서비스가 Hash type으로 data type을 변경할 수 있다면 이에 따른 메모리 절감 효과가 있을 거라고 생각했습니다.
String vs Hash Memory usage test
- String
127.0.0.1:6001> set kimdubi1 test1
OK
127.0.0.1:6001> set kimdubi2 test2
OK
127.0.0.1:6001> memory usage kimdubi1
(integer) 57
127.0.0.1:6001> memory usage kimdubi2
(integer) 57
=> String type Key 두개 일 때 114 bytes 사용
- Hash
127.0.0.1:6001> hset kimdubi kimdubi1 test1
(integer) 1
127.0.0.1:6001> memory usage kimdubi
(integer) 77
127.0.0.1:6001> hset kimdubi kimdubi2 test2
(integer) 1
127.0.0.1:6001> memory usage kimdubi
(integer) 94
=> Hash type 사용 시 data 하나 저장했을 땐 Hash type 의 메모리 사용량이 더 컸지만 ( 57 < 77)
하나의 Key에 데이터를 두개 저장하면서부터 Hash type이 더 적은 메모리를 사용하는 것을 확인할 수 있습니다. ( 57*2 > 94)
- 백만 건 비교
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
import uuid
conn_redis = redis.Redis(host="localhost", port=6379, password="qhdks123",db=0)
### String
for i in range(0,1000000):
conn_redis.set("kimdubi"+str(i), str(uuid.uuid4())+str(i))
### Hash
value=0
for i in range(0,2000):
for j in range(0,500):
conn_redis.hset("kimdubi"+str(i),"kimdubi"+str(value),str(uuid.uuid4())+str(value))
value=value+1
=> 위와 같은 간단한 코드를 통해 String type, Hash type 에 각각 데이터 백만건을 넣었습니다.
Hash type 같은 경우에는 hash-max-ziplist-entries 512 설정에 의해 하나의 key에 field 개수가 512를 초과하는 경우
ziplist -> hashtable로 관리되어 사이즈가 커지는 것을 고려하여 하나의 key에 field 500개만 저장했으며 이를 직관적으로 구현하다보니
위의 코드처럼 String 과 Hash 간 key의 생김새가 조금 달라지게 되었지만 memory usage 비교하는데에는 큰 영향이 없습니다
- String
# Keyspace
db0:keys=1000000,expires=0,avg_ttl=0
# Memory
used_memory:113243808
used_memory_human:108.00M
- hash
# Keyspace
db0:keys=2000,expires=0,avg_ttl=0
# Memory
used_memory:65704496
used_memory_human:62.66M
String (108MB) 을 Hash (62.66MB) 로 저장 시 대략 40% 효과가 있었습니다.
물론 실제 서비스에서 적용하려면 Hash 로 완벽하게 호환이 되는지 여러가지 고려사항을 따져봐야겠지만
똑같은 데이터를 저장하더라도 data type에 의해 memory 사용량 차이가 크게 날 수 있다는 것을 고려하여 서비스 구축 시 잘 설계해야겠습니다.