JSON 페이로드를 redis에 저장하고 싶습니다. 이 작업을 수행 할 수있는 방법은 두 가지가 있습니다.
-
하나는 간단한 문자열 키와 값을 사용합니다.
key : user, value : payload (100-200KB 일 수있는 전체 JSON Blob)SET user:1 payload
-
해시 사용
HSET user:1 username "someone"
HSET user:1 location "NY"
HSET user:1 bio "STRING WITH OVER 100 lines"
해시를 사용하면 값 길이를 예측할 수 없습니다. 위의 바이오 예제와 같이 모두 짧은 것은 아닙니다.
어느 쪽이 더 효율적인 메모리입니까? 문자열 키와 값을 사용하거나 해시를 사용합니까?
답변
데이터에 액세스하는 방법에 따라 다릅니다.
옵션 1로 가십시오.
- 대부분의 액세스에서 대부분의 필드를 사용하는 경우.
- 가능한 키에 차이가있는 경우
옵션 2로 가십시오.
- 대부분의 액세스에서 단일 필드 만 사용하는 경우
- 어떤 필드를 사용할 수 있는지 항상 알고 있다면
추신 : 일반적으로 대부분의 사용 사례에서 더 적은 쿼리가 필요한 옵션을 선택하십시오.
답변
이 기사는 여기에 많은 통찰력을 제공 할 수 있습니다 : http://redis.io/topics/memory-optimization
Redis에 객체 배열을 저장하는 방법에는 여러 가지가 있습니다 ( 스포일러 : 대부분의 경우 옵션 1을 좋아합니다).
-
전체 객체를 단일 키에 JSON 인코딩 문자열로 저장하고 세트 (또는 더 적절한 경우 목록)를 사용하여 모든 객체를 추적합니다. 예를 들면 다음과 같습니다.
INCR id:users SET user:{id} '{"name":"Fred","age":25}' SADD users {id}
일반적으로 이것은 대부분의 경우에 가장 좋은 방법 일 것입니다. 오브젝트에 많은 필드가 있고 오브젝트가 다른 오브젝트와 중첩되지 않고 한 번에 작은 필드 서브 세트에만 액세스하는 경향이있는 경우 옵션 2를 사용하는 것이 좋습니다.
장점 : “좋은 습관”으로 간주됩니다. 각 오브젝트는 완전한 Redis 키입니다. JSON 구문 분석이 빠릅니다. 특히이 객체의 여러 필드에 한 번에 액세스해야 할 때 특히 그렇습니다. 단점 : 단일 필드에만 액세스해야 할 때 속도가 느려집니다.
-
각 객체의 속성을 Redis 해시에 저장하십시오.
INCR id:users HMSET user:{id} name "Fred" age 25 SADD users {id}
장점 : “좋은 습관”으로 간주됩니다. 각 오브젝트는 완전한 Redis 키입니다. JSON 문자열을 구문 분석 할 필요가 없습니다. 단점 : 객체의 모든 / 대부분의 필드에 액세스해야 할 때 속도가 느려질 수 있습니다. 또한 중첩 된 객체 (객체 내의 객체)를 쉽게 저장할 수 없습니다.
-
각 오브젝트를 Redis 해시에 JSON 문자열로 저장하십시오.
INCR id:users HMSET users {id} '{"name":"Fred","age":25}'
이를 통해 비트를 통합하고 많은 키 대신 두 개의 키만 사용할 수 있습니다. 명백한 단점은 TTL (및 기타 항목)을 전체 사용자 Redis 키가 아닌 Redis 해시의 필드이기 때문에 각 사용자 Object에 TTL (및 기타 항목)을 설정할 수 없다는 것입니다.
장점 : JSON 구문 분석이 빠릅니다. 특히이 객체의 여러 필드에 한 번에 액세스해야하는 경우에 특히 그렇습니다. 기본 키 네임 스페이스의 “오염”이 적습니다. 단점 : 많은 오브젝트가있을 때 # 1과 동일한 메모리 사용량. 단일 필드에만 액세스해야하는 경우 # 2보다 느립니다. 아마도 “좋은 습관”으로 간주되지 않을 것입니다.
-
각 객체의 각 속성을 전용 키에 저장하십시오.
INCR id:users SET user:{id}:name "Fred" SET user:{id}:age 25 SADD users {id}
위의 기사에 따르면이 옵션은 거의 선호되지 않습니다 (Object의 속성에 특정 TTL 또는 다른 것이 필요하지 않은 한 ).
장점 : 객체 속성은 완전히 새로 워진 Redis 키이므로 앱에 과도하지 않을 수 있습니다. 단점 : 느리고 메모리를 많이 사용하며 “모범 사례”로 간주되지 않습니다. 기본 키 네임 스페이스가 많이 오염되었습니다.
전체 요약
옵션 4는 일반적으로 바람직하지 않습니다. 옵션 1과 2는 매우 유사하며 둘 다 일반적입니다. 옵션 1 (일반적으로 말하면)을 선호합니다. 더 복잡한 객체 (여러 계층의 중첩 등)를 저장할 수 있기 때문에 옵션 3은 기본 키 네임 스페이스를 오염시키지 않도록 주의 할 때 사용됩니다 (예 : 원하지 않는 경우). 데이터베이스에 많은 키가 있어야하며 TTL, 키 샤딩 또는 기타 항목에 대해서는 신경 쓰지 마십시오.
여기에 잘못된 점이 있으면 의견을 남기고 다운 보트하기 전에 답변을 수정하도록하십시오. 감사! 🙂
답변
주어진 답변에 몇 가지 추가 사항 :
우선 Redis 해시를 효율적으로 사용하려면 키 수 최대 개수와 최대 크기를 알아야합니다. 그렇지 않으면 해시-최대-ziplist 값 또는 해시-최대-ziplist 항목을 분류하면 Redis는이를 실제적으로 변환합니다 후드 아래에서 일반적인 키 / 값 쌍. (해시 -max-ziplist-value, hash-max-ziplist-entries 참조) Redis 내부의 각 일반적인 키 / 값 쌍은 쌍당 +90 바이트를 사용하기 때문에 해시 옵션에서 후드를 깨는 것은 정말 나쁜 일입니다.
옵션 2로 시작하여 실수로 max-hash-ziplist-value를 벗어나면 사용자가 가지고있는 각 속성마다 +90 바이트를 얻게됩니다! (실제로 +90은 아니지만 +70은 아래 콘솔 출력 참조)
# you need me-redis and awesome-print gems to run exact code
redis = Redis.include(MeRedis).configure( hash_max_ziplist_value: 64, hash_max_ziplist_entries: 512 ).new
=> #<Redis client v4.0.1 for redis://127.0.0.1:6379/0>
> redis.flushdb
=> "OK"
> ap redis.info(:memory)
{
"used_memory" => "529512",
**"used_memory_human" => "517.10K"**,
....
}
=> nil
# me_set( 't:i' ... ) same as hset( 't:i/512', i % 512 ... )
# txt is some english fictionary book around 56K length,
# so we just take some random 63-symbols string from it
> redis.pipelined{ 10000.times{ |i| redis.me_set( "t:#{i}", txt[rand(50000), 63] ) } }; :done
=> :done
> ap redis.info(:memory)
{
"used_memory" => "1251944",
**"used_memory_human" => "1.19M"**, # ~ 72b per key/value
.....
}
> redis.flushdb
=> "OK"
# setting **only one value** +1 byte per hash of 512 values equal to set them all +1 byte
> redis.pipelined{ 10000.times{ |i| redis.me_set( "t:#{i}", txt[rand(50000), i % 512 == 0 ? 65 : 63] ) } }; :done
> ap redis.info(:memory)
{
"used_memory" => "1876064",
"used_memory_human" => "1.79M", # ~ 134 bytes per pair
....
}
redis.pipelined{ 10000.times{ |i| redis.set( "t:#{i}", txt[rand(50000), 65] ) } };
ap redis.info(:memory)
{
"used_memory" => "2262312",
"used_memory_human" => "2.16M", #~155 byte per pair i.e. +90 bytes
....
}
TheHippo 답변의 경우 옵션 1에 대한 의견이 잘못되었습니다.
모든 필드 또는 여러 개의 get / set 작업이 필요한 경우 hgetall / hmset / hmget을 복구합니다.
BMiner의 답변입니다.
세 번째 옵션은 실제로 정말 재미 있습니다 .max (id) <has-max-ziplist-value 값이있는 데이터 세트의 경우이 솔루션에는 O (N) 복잡성이 있습니다. 놀랍게도 Reddis는 작은 해시를 길이 / 키 / 값의 배열과 같은 컨테이너로 저장하기 때문입니다 사물!
그러나 많은 경우 해시는 몇 개의 필드 만 포함합니다. 해시가 작을 때 대신 길이가 미리 지정된 키 값 쌍을 가진 선형 배열처럼 O (N) 데이터 구조로 인코딩 할 수 있습니다. N이 작을 때만이 작업을 수행하므로 HGET 및 HSET 명령의 상각 시간은 여전히 O (1)입니다. 해시가 포함 된 요소 수가 너무 많아지면 실제 해시 테이블로 변환됩니다.
그러나 걱정하지 않아도됩니다. 해시-최대-지퍼리스트 항목을 매우 빠르게 중단하면 실제로 솔루션 번호 1에 도달하게됩니다.
두 번째 옵션은 질문에 따르면 다음과 같은 이유로 네 번째 솔루션으로 갈 것입니다.
해시를 사용하면 값 길이를 예측할 수 없습니다. 위의 바이오 예제와 같이 모두 짧은 것은 아닙니다.
이미 말했듯이 네 번째 솔루션은 각 속성 당 가장 비싼 +70 바이트입니다.
이러한 데이터 세트를 최적화하는 방법에 대한 제안 :
두 가지 옵션이 있습니다.
-
첫 번째 솔루션보다 일부 사용자 속성의 최대 크기를 보장 할 수없고 메모리 문제가 중요한 경우 redis에 저장하기 전에 사용자 json을 압축하는 것보다 중요합니다.
-
모든 속성의 최대 크기를 강제 할 수있는 경우 당신은 레디 스 가이드의이 주제에서 해시 메모리 최적화 사용자 표현 당 또는 하나의 해시로 중 해시 최대-ziplist-항목 / 값 및 사용 해시를 설정할 수 있습니다보다 : https://redis.io/topics/memory-optimization 및 사용자를 json 문자열로 저장하십시오. 어느 쪽이든 긴 사용자 속성을 압축 할 수도 있습니다.