Redis – Estructuras de Datos

Redis es una base de datos NoSQL en memoria, sin esquema, single-thread, Open Source, e implementada en lenguaje C, que ofrece un alto rendimiento y resulta especialmente 煤til en casos de uso como el cacheo de datos (optimizando el uso de las bases de datos relacionales), almac茅n de sesiones, e incluso service broker. En este Post vamos a tratar las diferentes estructuras de datos de Redis: Strings, Hashes, Lists, Sets, y Sorted Sets

Comenzamos una nueva serie de Posts, en este caso sobre Redis (acr贸nimo de Remote dictionary server), una de las bases de datos NoSQL que m谩s se usan hoy en d铆a, principalmente para usos como Cach茅, tambi茅n para el almacenamiento de Sesi贸n y Aplicaci贸n en entornos Web, y otros muchos usos. Y como primer art铆culo, qu茅 mejor que hacer una introducci贸n a modo de preliminares y seguir con una explicaci贸n de las diferentes Estructuras de Datos de Redis, para comprender en qu茅 consisten y posibles casos de uso de cada una.

Preliminares

Podemos ejecutar Redis de forma sencilla con Docker (docker run), para luego abrir una sesi贸n de Bash sobre el contenedor Docker de Redis que hemos creado (docker exec), y poder ejecutar comandos como redis-cli (la interfaz de comandos de Redis, muy 煤til). Con esto, podremos trastear de forma sencilla. A continuacion se muestra un ejemplo, que nos servir谩 tanto en Ubuntu, como en Raspbian (Raspberry), etc.

docker run -d -p 6379:6379 --rm --name myredis redis:6.0
docker exec -it myredis /bin/bash
redis-cli

redis-cli es la herramienta de l铆nea de comandos para interaccionar con Redis, que nos permitir谩 ejecutar comandos Redis. Se instala por defecto al instalar un servidor de Redis, aunque tambi茅n la podr铆amos compilar nosotros mismos, si nos aburrimos, su c贸digo est谩 en GitHub.

  • redis-cli se puede ejecutar en modo interactivo. Simplemente, ejecutamos redis-cli desde nuestra shell, y entramos en un prompt donde interaccionar con redis.
  • redis-cli se puede ejecutar en modo imperativo. Por ejemplo, el comando redis-cli get my-key

Podemos hacer muchas cosas m谩s con redis-cli, como cargas masivas (o ejecuciones masivas de comandos Redis), analizar el espacio de claves buscando claves grandes, monitorizar los comandos ejecutados en Redis, o mostrar informaci贸n estad铆stica de forma continua cada segundo (ej: keys, mem, #clients, #connections, etc) para comprobar la salud de nuestro sistema, y muchas cosas m谩s.

redis-cli --pipe < MULTIPLE_COMMANDS_MASSIVE_INSERT.txt
redis-cli --bigkeys > BIGKEYS_REPORT.log
redis-cli MONITOR > MONITOR_COMMANDS.log
redis-cli --stat

Redis utiliza el protocolo RESP (Redis Serialization Protocol) a trav茅s de TCP, lo que hace que sea posible ejecutar comandos Redis desde una simple conexi贸n TCP a trav茅s de telnet o netcat (nc), como se ve en la siguiente pantalla, aunque evidentemente ser谩 mucho m谩s sencillo si utilizamos herramientas como redis-cli o Redisinsight, o las librer铆as de programaci贸n correspondientes al lenguajes que utilicemos, en lugar de trabajar a tan bajo nivel.

Los clientes Redis, pensando en las librer铆as cliente de Redis utilizadas por los diferentes lenguajes de programaci贸n para poder conectarse a un servidor Redis, suelen ofrecer las siguientes funcionalidades para ayudar a optimizar el rendimiento:

  • Connection Pooling. Crear y eliminar conexiones a Redis de forma continuada y repetida en el tiempo, genera una carga innecesaria en el servidor Redis que aumenta el consumo de CPU. Al habilitar Connection Pooling a trav茅s de nuestra librer铆a cliente, se consigue crear un conjunto de conexiones persistentes a Redis, que se mantendr谩n abiertas y se reutilizar谩n por los diferentes threads o procesos, evitando crear y eliminar conexiones repetidas veces, y optimizando el rendimiento.
  • Pipelining. Por defecto, para cada comando Redis se env铆a una petici贸n a Redis, que la procesa y devuelve una respuesta. Este round trip requiere un tiempo (RTT – Round Trip Time), algo que habitualmente sucede en el tiempo de forma secuencial, es decir, si ejecut谩semos 100 comandos el tiempo total ser铆a 100*RTT. La t茅cnica de Pipelining permite enviar varios comandos a la vez a Redis, que devolver谩 varias respuesta a la vez, todo en el mismo round trip, lo que ayuda a minimizar el tiempo total necesario para su procesamiento, al minimizar el n煤mero total de round trips para gestionar un mismo n煤mero de comandos. Adem谩s permite a Redis procesar nuevas peticiones incluso aunque el cliente a煤n no haya le铆do la respuesta de peticiones anteriores.

Otra forma habitual de mejorar el rendimiento en Redis es mediante la incorporaci贸n de una o varias R茅plicas de lectura, de tal modo, que las escrituras vayan contra el Master, y las lecturas contras las R茅plicas de s贸lo lectura, distribuyendo as铆 la carga entre varios servidores, de forma sencilla.

Redis est谩 formado por bases de datos l贸gicas (identificadas por un 铆ndice que comienza en cero) que act煤an como espacios de nombre planos, de tal modo que en diferentes bases de datos pueden existir claves con el mismo nombre, sin llegar a existir un nivel jer谩rquico intermedio, como podr铆an ser las Colecciones.

La base de datos por defecto es la cero (db0), y aunque por defecto tendr铆amos disponible hasta la db15, un Cluster de Redis s贸lo permite trabajar con db0, por lo que en general es recomendable que nos acostumbremos a trabajar s贸lo con db0, en previsi贸n de restricciones como esta (muchas aplicaciones y herramientas asumen trabajar con db0). Y para esto es muy importante definir bien qu茅 nombres vamos a dar a nuestras claves, ya que en aplicaciones grandes con m煤ltiples microservicios, podr铆amos acabar teniendo problemas de colisi贸n, que podemos evitar f谩cilmente siguiendo una convenci贸n de nombres.

Otro detalle importante es que Redis en una base de datos Single-Thread, lo que evita condiciones de carrera y sobrecarga de CPU por context-switching asociado a m煤ltiples hilos. Pero esto tiene varias implicaciones a tener en cuenta:

  • Si durante la ejecuci贸n de un comando, se solicita a Redis la ejecuci贸n de otros dos, por lo que cada comando se ejecutar谩 secuencialmente y de forma at贸mica (cada comando se ejecutar谩 de forma completa e independiente, o en caso de fallo, no aplicar谩n ning煤n cambio a la base de datos para mantener su integridad), por lo tanto los otros dos estar谩n sometidos a esperas, fruto de dicha secuencialidad.
  • No por poner muchos procesadores en el servidor de Redis, vamos a conseguir mejorar el rendimiento. En una aplicaci贸n orientada a micro-servicios ser谩 m谩s eficiente tener un Redis para cada micro-servicio, que un 煤nico Redis para todos (mejor varios Redis para diferentes dominios o prop贸sitos, que uno para todo).

Como cada comando Redis es at贸mico (se ejecuta de forma completa e independiente, o en caso de fallo, no aplicar谩n ning煤n cambio a la base de datos) para mantener la integridad, en algunos casos podemos necesitar el uso de Transacciones en Redis, para de esta forma poder ejecutar varios comandos de forma at贸mica, como si fuera uno solo, y que se ejecuten todos o en caso de fallo no se ejecute ninguno, para as铆 poder garantizar la integridad de nuestros datos.

Sin embargo las Transacciones en Redis no son exactamente igual a como lo son en las bases de datos relacionales. No es posible crear Transacciones anidadas en Redis, y tampoco existe la posibilidad de ejecutar un Rollback (si una transacci贸n incluye un comando con un error de sintaxis, la Transacci贸n completa ser谩 descartada, en cualquier otro caso, ser谩 ejecutada). Esto permite a Redis que pueda ofrecer un m谩ximo rendimiento y m铆nima latencia, y encaja perfectamente con su modelo Single-Thread.

Si lo deseamos, podemos descargar de forma gratuita (s贸lo requiere registro) la herramienta gr谩fica de RedisInsight, disponible para Windows, Linux y Mac, que te ayudar谩 centralizar las conexiones a tus bases de datos, a explorar su contenido, ver estad铆sticas, acceder de forma r谩pida a una CLI de Redis, etc.

Introducci贸n a las Claves (Keys) en Redis

Las claves (Keys) son la forma habitual de almacenar y recuperar datos en Redis, que ser谩n almacenados en memoria RAM, en un espacio de claves plano, donde el desarrollador tiene libertad en c贸mo organizarlo (nomenclatura). Habitualmente se sigue la siguiente convenci贸n en los nombres de las claves en Redis: object-name:identifier:composed-object

Un ejemplo de nombre de una Clave, podr铆a ser el siguiente, para una clave que representa los grupos del usuario 100001: user:100001:groups

Si bien, no es de obligado seguimiento, es recomendable seguir cierta nomenclatura o convenci贸n de nombres, sea esta u otra, y ser consistente en los nombres de todas las claves, especialmente si trabajamos en equipo con m谩s personas, teniendo en cuenta que el nombre de una clave puede ser de hasta 512MB. Es fundamental, ya que es espacio de nombres en una base de datos Redis es plano, al no existir Colecciones o Tablas, y es necesario recurrir a una nomenclatura clara, para evitar colisiones.

Las Claves (Keys) pueden ser de un tipo u otro, que principalmente se refiere a la Estructura de Datos que utiliza, y suele condicionar qu茅 comandos se usan para manipular la Clave. Las principales Estructuras de Datos son Strings, Hashes, Lists, Sets, y Sorted Sets.

Podemos utilizar los comandos SET y GET a trav茅s de redis-cli, para establecer el valor de una clave de tipo String as铆 como obtener su valor, respectivamente. SET crear谩 la clave si no existe, o bien, actualizar谩 su valor si exist铆a previamente. A continuaci贸n se muestra un ejemplo del uso de SET y GET, donde aprovechamos tambi茅n para crear 20 claves de tipo String que usaremos en otros ejemplos.

set user:100001 willie
get user:100001

set user:100002 frank
set user:100003 mary
set user:100004 fred
set user:100005 simon
set user:100006 russel
set user:100007 bart
set user:100008 tom
set user:100009 sam
set user:100010 boris
set user:100011 tim
set user:100012 will
set user:100013 george
set user:100014 bruce
set user:100015 leo
set user:100016 john
set user:100017 brad
set user:100018 matt
set user:100019 rob
set user:100020 sean

Es posible comprobar si existe una clave String utilizando el comando EXISTS, que devolver谩 1 si la clave existe y 0 en caso contrario.

Sin embargo, si queremos crear un clave String s贸lo si no existe, no debemos utilizar un comando EXISTS seguido de un comando SET condicionado al resultado de EXISTS. En casos como este, es m谩s eficiente utilizar un 煤nico comando SET con las opciones NX 贸 XX seg煤n lo que necesitemos:

  • Opci贸n NX del comando SET (non exist). El comando SET se ejecuta s贸lo si la clave no exist铆a previamente.
  • Opci贸n XX del comando SET (exist). El comando SET se ejectua s贸lo si la clave exist铆a previamente.

A continuaci贸n se muestra un ejemplo del uso de las opciones NX y XX del comando SET.

set user:100032 ted NX
set user:100032 rob NX
get user:100032
set user:100032 rob XX
get user:100032

Podemos utilizar los comandos KEYS y SCAN a trav茅s de redis-cli, para obtener una lista de los nombres de claves en la base de datos, ya sea de todos o bien s贸lo de aquellos que cumplan cierto patr贸n. Es muy importante comprender las diferencias entre ambos:

  • KEYS. Siempre bloquea la base de datos hasta que acaba, por lo que no est谩 recomendado su uso en Producci贸n (especialmente con bases de datos grandes). Sin embargo, debido a su facilidad de uso, es habitual utilizarlo en local, o incluso en entornos de desarrollo y de pruebas, pero con precauci贸n.
  • SCAN. Itera la base de datos en bloques (batches) mediante un cursor, lo que evita bloquear completamente la base de datos durante un tiempo prolongado, siendo una alternativa al comando KEYS, para su uso de forma segura en Producci贸n, aunque es un poco m谩s complicado y suele requerir la ejecuci贸n de varios comandos SCAN (varios batches) para obtener el resultado deseado (en lugar de un 煤nico comando KEYS). Cada llamada a SCAN requiere como par谩metro el valor de un cursor, que en la primera llamda ser谩 0. SCAN devolver谩 un valor de cursor 0 cuando ya no queden m谩s claves sobre las que iterar, y un valor distinto cuando a煤n queden claves, para utilizar en la siguiente llamada.

A continuaci贸n se muestra un ejemplo de KEYS y SCAN, donde se puede observar como para obtener el mismo resultado con SCAN es necesario realizar varias llamadas (batches), y como en las sucesivas llamadas a SCAN usamos el valor de cursor devuelto en la llamada anterior.

Podemos utilizar los comandos DEL y UNLINK a trav茅s de redis-cli, para eliminar una clave.

  • DEL. Eliminar谩 la clave y la memoria asociada con la misma, resultando en una operaci贸n que genera un bloqueo.
  • UNLINK. Elimina la asociaci贸n entre la clave y su valor (el espacio de memoria que usaba). La memoria ser谩 liberada mediante un proceso as铆ncrono, lo que permite que no se genera un bloqueo.

Expiraci贸n de Claves (Keys)

Las Claves (Keys) se almacenan en memoria RAM, por lo que suele ser una buena pr谩ctica establecer una expiraci贸n a las mismas (en ms, seg, o un timestamp), tanto en la creaci贸n de la clave (ej: con el comando SET) como posteriormente a su creaci贸n (ej: con los comandos EXPIRE, PEXPIRE, EXPIREAT, 贸 PEXPIREAT). Tambi茅n podemos comprobar el valor de la expiraci贸n de una Clave con los comandos TTL y PTTL, incluso eliminar la expiraci贸n de una Clave con el comando PERSIST. Esto nos ayudar谩 a evitar un consumo excesivo de memoria que pueda ser contraproducente.

Tipo de dato String

El tipo de dato m谩s habitual en Redis es el String, ya que permite almacenar en su interior cualquier tipo de informaci贸n, incluyendo valores num茅ricos, binarios, valores separados por comas (o por cualquier otro separador), JSON serializados, e incluso objetos grandes como im谩genes, sonido, documentos, o video, cualquier cosa. Ya hemos visto en el apartado anterior, algunos ejemplos y comandos con claves de tipo String.

Podemos almacenar en un String, un objeto que ha sido previamente serializado (ya sea en formato binario o texto), aunque en este caso hay que tener en cuenta, que siempre tendremos que acceder al objeto completo (lo que implica transferirlo por la red, as铆 como serializarlo y/o deserializarlo). Si necesitamos poder acceder o modificar s贸lo una propiedad, nos puede interesar almacenarlo como un Hash, en lugar de como un String, para as铆 poder acceder a sus propiedades de forma independiente.

Aunque en todos los casos estamos trabajando con un String en Redis, el valor de un String se almacena con un encoding determinado, que nos permite en ciertos casos conocer la naturaleza de nuestro dato (ej: un INT), y que podemos consultar con el comando OBJECT. De este modo, es como por ejemplo Redis sabe si puede ejecutar un comando INCR sobre una clave, aunque tambi茅n hay que tener en cuenta, que en cualquier momento podr铆amos almacenar un dato de otra naturaleza, y no habr铆a inconveniente (no hay un esquema que fuerce que tipo de valor podemos almacenar, como ocurrir铆a en una base de datos relacional). A continuaci贸n lo podemos ver con un ejemplo.

set user:100071 54
get user:100071
object encoding user:100071
type user:100071
set user:100071 "Out"
object encoding user:100071

La ejecuci贸n de los anteriores comandos en redis-cli producir铆a la siguiente salida, que representa con claridad lo que intentamos explicar.

Su uso m谩s habitual es como cach茅 (aqu铆 resulta especialmente 煤til la expiraci贸n de claves): respuestas de API, respuestas HTML, almacenamiento de sesi贸n, etc., pero tambi茅n se puede utilizar como un contador o secuencia, gracias a comandos como INCR, INCRBY, DECR y DECRBY, o el comando INCRBYFLOAT.

A continuaci贸n se muestra un ejemplo, en el que se cachea en Redis por 2 horas (7200 seg), informaci贸n de un usuario como un JSON en un String, y seguidamento comprobamos con el comando TTL cuandos segundos quedan para que expire.

set user:100074 '{"name":"Tom","region":"south-europe","currency":"EUR","groups":["editor","admin"]}' EX 7200 
ttl user:100074

A continuaci贸n se muestra un ejemplo utilizando los comandos INCR, INCRBY, DECR, y DECRBY. Hay que tener en cuenta, que la variable del ejemplo no exist铆a previamente, por lo que el primer comando la inicializa a 1.

incr player:100075:points
incr player:100075:points
incr player:100075:points
decr player:100075:points
incrby player:100075:points 4
decrby player:100075:points 5

La ejecuci贸n de cada comando, devuelve el valor actualizado de la variable, como se ve en la siguiente pantalla capturada.

Redis permite almacenar datos binarios en un String, un array de bits que puede ser almacenado y manipulado en Redis, permiti茅ndose operaciones a nivel de Bit (ej: comandos SETBIT, GETBIT, BITPOS, y BITCOUNT – que cuenta s贸lo los bits a 1). Adem谩s, el comando BITOP permite operaciones de bit como AND, OR 贸 XOR.

El comando BITFIELD permite obtener, modificar, e incrementar un valor dentro de un campo de bits. A continuaci贸n se muestra un ejemplo, en el que escribimos el valor decimal 65 como un entero sin signo de 8 bits (u8) al comienzo (el offset es 0), que coincide con el car谩cter ASCII de la 芦A禄, y que posteriormente lo incrementamos en 1 convirti茅ndolo en el valor 66 que coincide con la 芦B禄.

bitfield binary-map set u8 0 65
bitfield binary-map get u8 0
get binary-map
bitfield binary-map incrby u8 0 1
get binary-map
type binary-map
object encoding binary-map

A continuaci贸n se muestra el resultado de ejecuci贸n de los anteriores comandos en redis-cli.

Hashes

El tipo de dato Hash permite almacenar una colecci贸n no ordenada de parejas campo-valor (Strings), es un tipo de dato mutable y sin esquema (schemaless) que podemos modificar (a帽adir, modificar, o eliminar sus parejas de campo-valor, con libertad y en cualquier momento). Es un tipo de dato plano (no permite anidar arrays, por ejemplo). Podemos verlo como algo parecido a un JSON, un Diccionario en Python (pero plano, claro), un array de PHP, un fila de una tabla de una base datos relacional, o simplemente modelar una entidad o un objeto ligero.

Si necesitamos trabajar con JSON, podemos utilizar el m贸dulo RedisJSON en lugar de Hashes, que nos permitir谩 anidar valores, y proporciona comandos propios para trabajar con JSON en Redis.

Para trabajar con Hashes utilizaremos principalmente los comandos HSET, HGET, HDEL, HGETALL y HSCAN (HSCAN es m谩s recomendable que HGETALL, de forma similar a como ocurre con KEYS y SCAN), aunque hay otros comandos que nos podr谩n ser de utilidad como HKEYS, HVALS, HEXISTS, HSETNX, HMGET, INCRBY, HINCRBYFLOAT, etc. Podemos obtener o modificar el valor de un campo concreto (ej: stock), a帽adir nuevos campos (ej: discount), o eliminar un campo (ej: type). A continuaci贸n se muestra un ejemplo de comandos que podr铆amos lanzar desde redis-cli:

hset product:tv-samsung-led42 type tv manufacturer samsung size 42 tv-type led price 412 stock 12
hgetall product:tv-samsung-led42
hkeys product:tv-samsung-led42
hvals product:tv-samsung-led42
hget product:tv-samsung-led42 stock
hset product:tv-samsung-led42 stock 11
hget product:tv-samsung-led42 stock
hset product:tv-samsung-led42 discount 15
hexists product:tv-samsung-led42 discount
hdel product:tv-samsung-led42 type

A continuaci贸n se muestra el resultado de ejecuci贸n de los anteriores comandos en redis-cli.

En el caso de querer almacenar objetos complejos (objetos jer谩rquicos con propiedades distribuidas en varios niveles de profundidad), un Hash no es suficiente, y es necesario recurrir a alg煤n mecanismo adicional. Las principales alternativas son:

  • Utilizar un 煤nico Hash, aplanando la jerarqu铆a. Es quiz谩s la opci贸n m谩s simple, aunque la gesti贸n de la relaciones jer谩rquicas puede volverse complicada, y podemos acabar con muchos campos convirti茅ndose en algo tedioso. Es importante jugar con la nomenclatura de los campos, para simular dicha jerarqu铆a. Por ejemplo, podemos tener los siguientes campos dentro del mismo Hash:
    • seats:general:quantity
    • seats:general:price
    • seats:vip:quantity
    • seats:vip:price
  • Utilizar m煤ltiples Hashes, de tal modo que cada objeto (plano) es almacenado en su propio Hash. Se trata de hacer una especie de ejercicio de normalizaci贸n, como si se tratase de un diagrama UML, rompiendo a cada nivel de la jerarqu铆a en un nuevo Hash. Por ejemplo, para un objeto, podr铆amos utilizar los siguientes Hashes:
    • event:1001
    • event:1001:seats:general
    • event:1001:seats:vip
  • Utilizar m煤ltiples Hashes y Sets. Cada objeto (plano) es almacenado en un Hash, como en el caso anterior, pero adem谩s, las relaciones entre objetos se almacenan en Sets adicionales, resultando en una soluci贸n m谩s compleja y que requiere memoria adicional. Siguiendo con el ejemplo del caso anterior, ser铆a necesario el siguiente Set, al cual deber铆amos a帽adir las referencias a los objetos hijos (event:1001:seats:general y event:1001:seats:vip).
    • event:1001:seats

Un caso de uso podr铆an ser una base de datos de productos, donde cada producto se almacene como un Hash con clave product:product-name, y las parejas de campo y valor sean las propiedades del producto.

Otro caso de uso podr铆a ser un sistema de control de peticiones a APIs (rate limit), donde cada API se almacene como un Hash con clave rate-limit:invoince-api, y las parejas de campo y valor sean los endpoints de la API con un valor entero, que estableceremos (HSET) cada cierto tiempo con un valor, y que decrementaremos (HINCRBY) en cada llamada.

Lists

El tipo de dato List permite almacenar una colecci贸n ordenada de valores (Strings) de hasta 4 billones, que podemos utilizar para almacenar simplemente una colecci贸n de valores, pero tambi茅n para implementar pilas o colas. Redis implementa las Listas como una Lista de elementos doblemente enlazadados (no como un Array), donde cada elemento tiene un puntero al elemento anterior y otro al posterior. El orden se garantiza habitualmente en base a la inserci贸n y borrado de elementos (por uno u otro extremo de la Lista, no es habitual insertar elementos en su interior), siendo posible la existencia de elementos duplicados. Podemos verlo como algo parecido a un ArrayList de Java, un array de Java, o una Lista de Python.

  • Podemos a帽adir elementos (Push) a una Lista con los comandos LPUSH 贸 RPUSH, que devuelven el n煤mero de elementos o longitud de la Lista.
  • Podemos sacar elementos (Pop) de una Lista con los comandos LPOP 贸 RPOP, que devuelven el elemento que se ha sacado de la Lista de forma inmediata, o nil en caso de estar vac铆a.
  • Podemos sacar elementos (Pop) de una Lista con los comandos BLPOP 贸 BRPOP, que devuelven el elemento que se ha sacado de la Lista, o en caso de estar vac铆a se queda esperando un tiempo determinado (a diferencia de LPOP o RPOP).
  • Podemos implementar una cola, a帽adiendo elementos por la derecha (RPUSH) y sac谩ndolos por la izquierda (LPOP), o bien al contrario (LPUSH y RPOP).
  • Podemos implementar un pila, a帽adiendo elementos por la derecha (RPUSH) y sac谩ndolos igualmente por la derecha (RPOP), o bien al contrario (LPUSH y LPOP).
  • Podemos obtener el elemento existente en una posici贸n determinada con el comando LINDEX.
  • Podemos insertar un nuevo elemento, en el interior de la Lista (que no sea de los extremos, sea antes o despu茅s de otro existente), con el comando LINSERT.
  • Podemos actualizar el valor de un elemento existente con el comando LSET.
  • Podemos eliminar un elemento existente, del interior de la Lista (que no sea de los extremos), con el comando LREM.
  • Podemos obtener el n煤mero de elementos de una Lista con el comando LLEN.
  • Podemos recortar la Lista especificando un rango de 铆ndices, mediante el comando LTRIM (ojo, no existe ning煤n comando RTRIM), lo que permitir铆a recortar la Lista para reducirla, por ejemplo, a un TOP3. Un valor de 铆ndice negativo, referencia los elementos comenzando desde el final de la lista (desde la derecha), com
  • Podemos obtener un conjunto de elementos de una Lista mediante el comando LRANGE, especificando el 铆ndice del primer y 煤ltimo elemento que deseamos mostrar, lo que nos permitir谩 mostrar los primeros 5 elementos de una lista (ej: LRANGE waitlist:acdc-tour-madrid 0 4), e ir paginando sobre el resto (ej: LRANGE waitlist:acdc-tour-madrid 5 9), etc. Si queremos seleccionar todos los elementos, especificaremos como 铆ndices 0 y -1 (ej: LRANGE waitlist:acdc-tour-madrid 0 -1), por lo que deberemos ser prudentes al trabajar con grandes Listas, para evitar impactar en el rendimiento.

A continuaci贸n se muestra un ejemplo de comandos que podr铆amos lanzar desde redis-cli.

rpush waitlist:acdc-tour-madrid customer:1004 customer:1009 customer:1017
lrange waitlist:acdc-tour-madrid 0 -1
lpop waitlist:acdc-tour-madrid
llen waitlist:acdc-tour-madrid
rpush waitlist:acdc-tour-madrid customer:1023
lindex waitlist:acdc-tour-madrid 1

A continuaci贸n se muestra el resultado de ejecuci贸n de los anteriores comandos en redis-cli.

Es posible implementar colecciones limitadas, con el patr贸n RPUSH + LTRIM, de tal modo que si queremos tener una Lista limitada a 20 elementos, despu茅s de cada inserci贸n (RPUSH), ejecutar铆amos LTRIM para recortar el n煤mero de elementos.

Un caso de uso podr铆a ser una Playlist de m煤sica o de videos, que a fin de cuentas, es un conjunto de elementos implementado en forma de una cola, para su ejecuci贸n o procesamiento en orden.

Otros casos de uso, podr铆an ser una lista de espera (waitlist), una clasificaci贸n (ej: resultado de una competici贸n), o un historial (ej: podr铆amos almacenar el historial de navegaci贸n de nuestros usuarios con una Lista para cada usuario, con clave history:10001 donde el valor num茅rico es el c贸digo del usuario… o un historial de actividad, como nuestra actividad en una red social como Facebook).

Tambi茅n podr铆amos utilizar una Lista para implementar un patr贸n Productor-Consumidor, donde dos procesos se puedan comunicar permitiendo que un consumidor reciba los mensajes o eventos en el mismo orden en que se han producido, para su procesamiento.

Sets

El tipo de dato Set permite almacenar una colecci贸n no ordenada de valores 煤nicos (Strings), es decir, que no contiene duplicados, y que adem谩s permite operaciones matem谩ticas de conjuntos como intersecci贸n, diferencia, y uni贸n.

Algunos comandos 煤tiles para trabajar con Sets, ser铆an los siguientes:

  • Podemos a帽adir elementos con el comando SADD.
  • Podemos sacar un elemento espec铆fico con el comando SREM.
  • Podemos sacar un elemento aleatorio con el comando SPOP.
  • Podemos conocer el n煤mero de elementos con el comando SCARD.
  • Podemos mostrar los elementos con el comando SMEMBERS o tambi茅n con el comando SSCAN (m谩s eficiente).
  • Podemos comprobar si contiene un elemento con el comando SISMEMBER.
  • Podemos obtener la intersecci贸n entre dos conjuntos con el comando SINTER. Tambi茅n podemos usar el comando SDIFF para obtener la diferencia entre dos conjuntos, y el comando SUNION para la uni贸n de ambos.

A continuaci贸n se muestra un ejemplo de comandos que podr铆amos lanzar desde redis-cli.

sadd devices:online 10.0.0.1
sadd devices:online 10.0.0.2 10.0.0.3 192.168.0.1
scard devices:online
smembers devices:online
sscan devices:online 0 match *
sismember devices:online 192.168.0.1
sadd devices:spain 192.168.0.1
sinter devices:online devices:spain

A continuaci贸n se muestra el resultado de ejecuci贸n de los anteriores comandos en redis-cli.

Un caso de uso podr铆a ser implementar un sistema de control de presencia de dispositivos, mediante un Set con clave devices:online que almacene los diferentes dispositivos que est谩n online, lo cuales actualizar铆an su IP en el conjunto cada minuto. Una versi贸n mejora podr铆a trabajar con dos Sets (timed-scoped keys), uno para la franja temporal actual (ej: devices:online:1000), y otra para la siguiente franja temporal (ej: devices:online:1005), de tal modo, que cada vez que un dispositivo actualiza su estado lo hace en ambos Sets, y cada 5 min (o el periodo que se decida), se rotan de tal modo que se elimina el Set de la franja temporal actual sustituy茅ndola por el de la siguiente franja temporal, y se crea un nuevo Set vac铆o para la siguiente franja temporal (realmente, esto lo podemos conseguir con la Expiraci贸n de claves, y simplemente acceder en cada momento a los Sets correctos, en funci贸n de la hora del sistema). As铆 conseguimos reflejar los dispositivos que han dejado de actualizar su estado, y que por lo tanto, no est谩n online.

La nube de Tags de un Blog tambi茅n se podr铆a implementar con un conjunto (Set), o podr铆amos implementar un control de visitantes 煤nicos a una Web, mediante un Set para cada p谩gina, con clave similar a visits:aboutus.html donde a帽adir las IPs de los visitantes.

Sorted Sets

El tipo de dato Sorted Set permite almacenar una colecci贸n ordenada de valores 煤nicos (Strings) – no permite duplicados – con un valor de puntaci贸n (floating point score) asociado a cada miembro que es utilizado como criterio de ordenaci贸n, del m谩s bajo al m谩s alto, aunque los miembros pueden ser accedidos en orden ascendente o descendente. La puntaci贸n (score) de cada miembro, se puede incrementar o decrementar, lo que puede cambiar al orden de los miembros. Adem谩s permite operaciones matem谩ticas de conjuntos como intersecci贸n y uni贸n (la diferencia no era posible, hasta Redis 6.2, que se introdujo ZDIFF).

Algunos comandos 煤tiles para trabajar con Sorted Sets, ser铆an los siguientes:

  • Podemos a帽adir elementos (o actualizar su puntaci贸n/score, si ya exist铆an) con el comando ZADD. Incluye las opciones NX (Nox Exists) y XX (Exists), como ocurr铆a con el comando SET.
  • Podemos conocer el n煤mero de elementos con el comando ZCARD.
  • Podemos incrementar o decrementar la puntaci贸n (score) de un miembro con el comando ZINCRBY.
  • Podemos eliminar un miembro, en funci贸n de su valor (independientemente de su puntaci贸n/score), con el comando ZREM. Tambi茅n es posible utilizar otros comandos similares, como ZREMRANGEBYLEX, ZREMRANGEBYRANK (equivalente a LTRIM en las Listas, ser铆a permitir铆a eliminar todos los elementos excepto el TOP3), y ZREMRANGEBYSCORE.
  • Podemos recorrer los miembros en funci贸n de su posici贸n (su 铆ndice), de principio a fin (de menor a mayor), o en orden contrario, con los comandos ZRANGE y ZREVRANGE. Esto es 煤til para conseguir un TOP3, y otras consultas parecidas.
  • Podemos recorrer los miembros en funci贸n de su puntuaci贸n (score), de principio a fin (de menor a mayor), o en orden contrario, con los comandos ZRANGEBYSCORE y ZREVRANGEBYSCORE.
  • Podemos obtener la posici贸n (ranking) de un miembro con el comando ZRANK. Hay que tener en cuenta que el primer miembro (el de puntaci贸n m谩s baja) ser谩 el cero, el siguiente el uno, y as铆 sucesivamente, pudiendo utilizar el comando ZREVRANK para obtener la posici贸n en orden inverso (primero el miembro con la puntuaci贸n m谩s alta).
  • Podemos obtener la puntaci贸n (score) de un miembro con el comando ZSCORE.
  • Podemos obtener cuantos miembros tienen una puntaci贸n (score) dentro de un rango entre dos puntuaciones (ambas inclusives), con el comando ZCOUNT.
zadd laliga-santander:t21-22 34 real-madrid
zadd laliga-santander:t21-22 33 futbol-club-barcelona
zadd laliga-santander:t21-22 30 atletico-madrid
zadd laliga-santander:t21-22 28 athletic-bilbao
zadd laliga-santander:t21-22 25 valencia 24 real-sociedad
zcard laliga-santander:t21-22
zincrby laliga-santander:t21-22 3 real-madrid
zrevrange laliga-santander:t21-22 0 2 WITHSCORES
zrank laliga-santander:t21-22 atletico-madrid
zscore laliga-santander:t21-22 real-madrid
zcount laliga-santander:t21-22 28 33
zrevrangebyscore laliga-santander:t21-22 33 28 WITHSCORES

A continuaci贸n se muestra el resultado de ejecuci贸n de los anteriores comandos en redis-cli.

Al trabajar con operaciones de conjunto tenemos que tener en cuenta como tratar la puntuaci贸n (score). En el siguiente ejemplo, creamos un nuevo Sorted Set mediante una operaci贸n de intersecci贸n, aplicando la operaci贸n SUM a las puntaciones de los elementos existentes en los conjuntos origen, para calcular su valor en el conjunto final. Es decir, si oscar tiene 24 puntos en la carrera de Madrid y 28 en la de Paris, acabar谩 con 52 puntos.

zadd championship:race-madrid 32 john 28 peter 26 zack 24 oscar
zadd championship:race-paris 31 peter 29 john 28 oscar 27 zack
zinterstore championship:totals 2 championship:race-madrid championship:race-paris aggregate sum
zrange championship:totals 0 -1 withscores

A continuaci贸n se muestra el resultado de ejecuci贸n de los anteriores comandos en redis-cli.

Es posible implementar colecciones limitadas, con el patr贸n ZADD + ZREVRANGEBYRANK, de tal modo que si queremos tener un Sorted Set limitado a 20 elementos, despu茅s de cada inserci贸n (ZADD), ejecutar铆amos ZREVRANGEBYRANK para recortar el n煤mero de elementos.

Un caso de uso t铆pico ser铆a una tabla de clasificaci贸n (leaderboard o top score) en tiempo real, que almacene un conjunto de participantes o jugadores, con su puntaci贸n asociada que puede cambiar a lo largo del tiempo, mateni茅ndose siempre ordenados, y permitiendo obtener de forma f谩cil datos como el TOP3 de la clasificaci贸n, la posici贸n o ranking de cualquier miembro, o la posici贸n de cualquier miembro. Aqu铆 podr铆a ser interesante una colecci贸n limitada, por ejemplo a 20 贸 100 elementos, apoy谩ndonos en ZREVRANGEBYRANK.

Al igual que con las Listas, es posible implementar Colas o Pilas. Pero en este caso, adem谩s podr铆amos implementar Colas o Pilas Priorizadas, aprovechando la capacidad de ordenaci贸n en base a la puntuaci贸n (score), apoy谩ndonos en comandos como ZRANGE sorted-set 0 0 y ZREM sorted-set item1.

Despedida y Cierre

Hasta aqu铆 llega este Post, en el que hemos intentado realizar una introducci贸n a Redias y a las estructuras de datos que soporta Redis (Strings, Hashes, Lists, Sets, y Sorted Sets), as铆 como cierta orientaci贸n en los posibles casos de uso, y comandos de ejemplo, sin entrar en otros tipos de datos o estructuras m谩s complejas, como ser铆a el mecanismo de Publicaci贸n y Suscripci贸n de Redis, o los 铆ndices geoespaciales.

Poco m谩s por hoy. Como siempre, conf铆o que la lectura resulte de inter茅s.

Deja un comentario

Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *

20 − 15 =