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 사용량 차이가 크게 날 수 있다는 것을 고려하여 서비스 구축 시 잘 설계해야겠습니다.