json - hkeys - redis set
Redis strings vs Redis hashes para representar a JSON: ¿eficiencia? (3)
Algunas adiciones a un conjunto dado de respuestas:
En primer lugar, si va a usar el hash de Redis de manera eficiente, debe saber el número máximo de valores y el número máximo de valores. De lo contrario, si se rompen hash-max-ziplist-value o hash-max-ziplist-entries, Redis lo convertirá en prácticamente Pares habituales de clave / valor debajo de una capucha. (vea hash-max-ziplist-value, hash-max-ziplist-entries) Y romper bajo un capó de opciones de hash ES REALMENTE MALO, porque cada par clave / valor habitual dentro de Redis usa +90 bytes por par.
¡Significa que si comienzas con la opción dos y rompes accidentalmente el valor de max-hash-ziplist, obtendrás +90 bytes por CADA ATRIBUTO que tienes dentro del modelo de usuario! (en realidad no es el +90 pero +70 ve la salida de la consola abajo)
# 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
....
}
Para la respuesta de TheHippo, los comentarios sobre la Opción uno son engañosos:
hgetall / hmset / hmget para el rescate si necesita todos los campos o la operación de obtención / configuración múltiple.
Para la respuesta de BMiner.
La tercera opción es realmente muy divertida, para el conjunto de datos con max (id) <has-max-ziplist-value esta solución tiene una complejidad O (N), porque, sorpresa, Reddis almacena hashes pequeños como un contenedor de longitud / clave / valor ¡objetos!
Pero muchas veces los hashes contienen solo algunos campos. Cuando los hashes son pequeños, podemos en cambio codificarlos en una estructura de datos O (N), como una matriz lineal con pares de valores de clave con prefijo de longitud. Dado que solo hacemos esto cuando N es pequeño, el tiempo amortizado para los comandos HGET y HSET sigue siendo O (1): el hash se convertirá en una tabla hash real tan pronto como el número de elementos que contenga crezca demasiado
Pero no debe preocuparse, romperá hash-max-ziplist-entries muy rápido y ya está, en realidad está en la solución número 1.
La segunda opción probablemente irá a la cuarta solución bajo un capó porque, como dice la pregunta:
Tenga en cuenta que si uso un hash, la longitud del valor no es predecible. No son todos cortos, como el ejemplo bio arriba.
Y como ya ha dicho: la cuarta solución es la más cara de +70 bytes por cada atributo, sin duda.
Mi sugerencia de cómo optimizar dicho conjunto de datos:
Tienes dos opciones:
Si no puede garantizar el tamaño máximo de algunos atributos de usuario, puede buscar la primera solución y si la memoria es crucial, comprima el usuario json antes de almacenar en redis.
Si puedes forzar el tamaño máximo de todos los atributos. Puede configurar hash-max-ziplist-entries / value y usar hashes como un hash por representación de usuario O como optimización de hash memory de este tema de la guía de Redis: https://redis.io/topics/memory-optimization y almacenar usuario como cadena json. De cualquier manera, también puede comprimir atributos de usuario largos.
Quiero almacenar una carga útil JSON en redis. Hay realmente 2 formas en que puedo hacer esto:
Uno utilizando una cadena simple y valores.
clave: usuario, valor: carga útil (el blob JSON completo que puede ser de 100-200 KB)SET user:1 payload
Usando hashes
HSET user:1 username "someone"
HSET user:1 location "NY"
HSET user:1 bio "STRING WITH OVER 100 lines"
Tenga en cuenta que si uso un hash, la longitud del valor no es predecible. No son todos cortos, como el ejemplo bio arriba.
¿Cuál es la memoria más eficiente? ¿Usando claves y valores de cadena, o usando un hash?
Depende de cómo acceda a los datos:
Ir a la Opción 1:
- Si utiliza la mayoría de los campos en la mayoría de sus accesos.
- Si hay variación en las claves posibles
Ir a la opción 2:
- Si usa solo campos en la mayoría de sus accesos.
- Si siempre sabes qué campos están disponibles.
PD: Como regla general, vaya a la opción que requiere menos consultas en la mayoría de sus casos de uso.
Este artículo puede proporcionar mucha información aquí: http://redis.io/topics/memory-optimization
Hay muchas formas de almacenar una matriz de Objetos en Redis ( spoiler : me gusta la opción 1 para la mayoría de los casos de uso):
Almacene el objeto completo como una cadena codificada en JSON en una sola clave y realice un seguimiento de todos los objetos usando un conjunto (o lista, si es más apropiado). Por ejemplo:
INCR id:users SET user:{id} ''{"name":"Fred","age":25}'' SADD users {id}
En términos generales, este es probablemente el mejor método en la mayoría de los casos. Si hay muchos campos en el Objeto, sus Objetos no están anidados con otros Objetos, y usted tiende a acceder solo a un pequeño subconjunto de campos a la vez, podría ser mejor usar la opción 2.
Ventajas : se considera una "buena práctica". Cada objeto es una clave Redis en toda regla. El análisis JSON es rápido, especialmente cuando necesita acceder a muchos campos para este objeto a la vez. Desventajas : más lento cuando solo necesitas acceder a un solo campo.
Almacena las propiedades de cada objeto en un hash redis.
INCR id:users HMSET user:{id} name "Fred" age 25 SADD users {id}
Ventajas : se considera una "buena práctica". Cada objeto es una clave Redis en toda regla. No hay necesidad de analizar cadenas JSON. Desventajas : posiblemente sea más lento cuando necesite acceder a todos / la mayoría de los campos en un Objeto. Además, los objetos anidados (objetos dentro de objetos) no se pueden almacenar fácilmente.
Almacena cada Objeto como una cadena JSON en un hash Redis.
INCR id:users HMSET users {id} ''{"name":"Fred","age":25}''
Esto le permite consolidar un poco y usar solo dos teclas en lugar de muchas claves. La desventaja obvia es que no puede establecer el TTL (y otras cosas) en cada Objeto de usuario, ya que es simplemente un campo en el hash Redis y no una clave Redis completa.
Ventajas : el análisis JSON es rápido, especialmente cuando necesita acceder a muchos campos para este objeto a la vez. Menos "contaminante" del espacio de nombres clave principal. Desventajas : sobre el mismo uso de memoria que el # 1 cuando tiene muchos Objetos. Más lento que el # 2 cuando solo necesita acceder a un solo campo. Probablemente no se considera una "buena práctica".
Almacena cada propiedad de cada Objeto en una clave dedicada.
INCR id:users SET user:{id}:name "Fred" SET user:{id}:age 25 SADD users {id}
De acuerdo con el artículo anterior, esta opción casi nunca se prefiere (a menos que la propiedad del Objeto deba tener un TTL específico o algo así).
Ventajas : Las propiedades de los objetos son claves Redis en toda regla, que pueden no ser excesivas para su aplicación. Desventajas : lento, usa más memoria y no se considera "mejor práctica". Gran cantidad de contaminantes del espacio de nombres clave principal.
Resumen total
La opción 4 generalmente no es la preferida. Las opciones 1 y 2 son muy similares, y ambas son bastante comunes. Prefiero la opción 1 (en términos generales) porque le permite almacenar objetos más complicados (con múltiples capas de anidamiento, etc.) La opción 3 se usa cuando realmente le importa no contaminar el espacio de nombres de la clave principal (es decir, no quiere que esté ahí). para ser una gran cantidad de claves en su base de datos y no le importan cosas como TTL, fragmentación de claves, o lo que sea).
Si me equivoco de algo aquí, por favor considere dejar un comentario y permitirme revisar la respuesta antes de votar. ¡Gracias! :)