Un problema habitual al trabajar con Redis es cómo realizar una búsqueda sobre las claves en función de los atributos almacenados en su valor (ej: Strings que almacenan JSON), de forma rápida y eficiente, especialmente sobre grandes colecciones de datos, algo que en Redis se puede resolver con diferentes técnicas, como Object Inspection, Faceted Search, y Hashed Keys, cada una de las cuales tiene unas implicaciones de rendimiento y almacenamiento.
Continuando con nuestra serie sobre Redis, ahora que ya hemos hablado sobre las diferentes estructuras de datos que soporta Redis (Strings, Hashes, Lists, Sets, y Sorted Sets), vamos a tratar uno de los problemas más comunes y las principales formas de resolverlo: la búsqueda sobre claves en función de los atributos almacenados en su valor. Para ello, vamos a partir del siguiente conjunto de datos de ejemplo:
set player:1001 '{"name":"Tom","team":"real-madrid","position":"defender","active":true}'
set player:1002 '{"name":"Scott","team":"real-madrid","position":"goalkeeper","active":true}'
set player:1003 '{"name":"Sam","team":"real-madrid","position":"midfielder","active":true}'
set player:1004 '{"name":"Ralph","team":"athletic-bilbao","position":"forward","active":true}'
set player:1005 '{"name":"Kirk","team":"athletic-bilbao","position":"midfielder","active":true}'
set player:1006 '{"name":"Sam","team":"athletic-bilbao","position":"goalkeeper","active":true}'
set player:1007 '{"name":"Tim","team":"athletic-bilbao","position":"defender","active":false}'
set player:1008 '{"name":"Fred","team":"real-madrid","position":"forward","active":false}'
Object Inspection – SCAN + GET
Aunque este método no es recomendable, especialmente para grandes volúmenes de datos, también es muy sencillo de comprender y suficiente cuando se trata de volúmenes de datos muy pequeños. Consiste en:
- Ejecutar comandos SCAN para recuperar las claves (keys) que necesitamos analizar (ej: player:*).
- Para cada clave (key) obtenerla con un comando GET, y procesarla, es decir, recuperar su contenido como un JSON y procesarlo para comprobar si cumple las condiciones de la consulta que deseamos.
Faceted Search (Sets + SINTER)
Este método consiste en crear claves adicionales (Sets), que van a tener una función algo similar a la que tienen los índices en la bases de datos relacionales. Es decir, para los campos por los que deseamos poder realizar las búsquedas, crearemos conjuntos (Sets) para cada distinto valor de cada una de los campos que deseamos indexar. Esto nos permitirá poder obtener la intersección de los mismos, cuando necesitemos ejecutar consultas que dependen del valor de varios campos, con el inconveniente de crear conjuntos (Sets) adicionales, que en el caso de atributos que pueden tener multitud de valores diferentes supone crear multitud conjuntos (Sets). Esto implica que:
- Cada vez que creamos, actualizamos o borramos una nueva clave (ej: player:*), tendremos que mantener los conjuntos (Sets) auxiliares que utilizamos para poder acelerar las consultas.
- Ofrece un rendimiento mejor, que con Object Inspection, pero requiere también de claves adicionales (Sets) que pueden llegar a ser muchas, y por lo tanto de un almacenamiento mayor (si abusamos mucho de este método en nuestras aplicaciones, tendremos que asegurarnos de que tenemos memoria suficiente).
Vamos a verlo con un ejemplo. Suponiendo que nos interese poder buscar por los campos team y/o active, crearíamos los siguientes conjuntos (Sets) auxiliares, donde el pre-fijo fs simboliza Faceted Search:
sadd fs:player-active:true 1001 1002 1003 1004 1005 1006
sadd fs:player-active:false 1007 1008
sadd fs:player-team:real-madrid 1001 1002 1003 1008
sadd fs:player-team:athletic-bilbao 1004 1005 1006 1007
Realizado, podríamos ejecutar las consultas que deseemos sobre dichos campos, consultando dichos conjuntos auxiliares, por ejemplo con un comando SINTER que nos devuelva los registros para los que el campo active era true y team era real-madrid. Fácil y sencillo.
sinter fs:player-active:true fs:player-team:real-madrid
Si además necesitásemos el detalle completo, para cada valor devuelto ejecutaríamos un comando GET para obtener la clave completa con el JSON en formato de String que almacen en su interior.
Hashed Keys (Sets + Hashed Keys)
Este método consiste en crear claves adicionales (Sets), que van a tener una función algo similar a la que tienen los índices en la bases de datos relacionales, y cuya clave contendrá el valor Hash (ej: MD5, SHA256, etc) de la condición de búsqueda, y almacenará referencias a las claves que cumplen dicho criterio. Esto implica que:
- Cada vez que creamos, actualizamos o borramos una nueva clave (ej: player:*), tendremos que mantener los conjuntos (Sets) auxiliares que utilizamos para poder acelerar las consultas.
- Ofrece un rendimiento mejor, que con Object Inspection, pero requiere también de claves adicionales (Sets) que pueden llegar a ser muchas, y por lo tanto de un almacenamiento mayor (si abusamos mucho de este método en nuestras aplicaciones, tendremos que asegurarnos de que tenemos memoria suficiente).
Vamos a verlo con un ejemplo. Si calculamos el Hash MD5 para las condiciones «active=true:team=real-madrid» y «active=false:team=real-madrid», obtenemos los valores 3ab1490561163540868691df7bf15c8d y ef1e192dac3ee30751f862c459635d9a respectivamente.
Podemos crear dos conjuntos (Sets) para cada uno de ambos criterios de búsqueda, que contengan las referencias a las claves que satisfacen el criterio correspondiente, como se muestra en los siguientes comandos, donde el pre-fijo hfs simboliza Hashed Faceted Search. Los nombres de las claves, utilizando un hash, no son fácilmente entendibles por los humanos, pero resulta relativamente sencillo de manipular mediante cualquier lenguaje de programación.
sadd hfs:player:3ab1490561163540868691df7bf15c8d 1001 1002 1003
sadd hfs:player:ef1e192dac3ee30751f862c459635d9a 1008
Para realizar una búsqueda, podríamos obtener el Hash del criterio de búsqueda, para seguidamente ejecutar un comando SMEMBERS o un comando SSCAN (bueno, realmente podrían ser varios comandos SSCAN, en batches, si es un conjunto grande) para obtener los miembros del conjunto.
smembers hfs:player:3ab1490561163540868691df7bf15c8d
sscan hfs:player:3ab1490561163540868691df7bf15c8d 0 match *
Si además necesitásemos el detalle completo, para cada valor devuelto ejecutaríamos un comando GET para obtener la clave completa con el JSON en formato de String que almacen en su interior.
Despedida y Cierre
Hasta aquí llega este Post, en el que hemos visto las principales alternativas para enfrentarse en Redis a la búsqueda sobre claves en función de los atributos almacenados en su valor (Object Inspection, Faceted Search, y Hashed Keys), y comprender las ventajas e inconvenientes de cada método.
Poco más por hoy. Como siempre, confío que la lectura resulte de interés.