Skip to content

redis

Redis (y 2)

Continuando con el repaso de las características básicas de Redis, en este artículo se revisan los tipos que representan estructuras agregadas de elementos.

Hash

Este tipo almacena pares claves-valor asociados a un nombre. Se comporta como los arrays asociativos presentes en la mayoría de lenguajes de programación.

El caso de uso más habitual que implementan es almacenar información relacionada con una entidad, a modo de atributos de un objeto.

Los comandos disponibles para trabajar con hashes siguen la notación característica de Redis para estructuras, donde cada comando empieza con una letra que hace referencia al tipo con el que operan. Por ejemplo, los comandos que empiezan por H operan con hashes.

Comandos específicos para el tipo hash:

  • HSET, establece el valor de un campo
  • HSETNX, establece el valor de un campo, pero sólo si no existía
  • HMSET, establece el valor de una serie de campos
  • HGET, obtiene el valor de un campo
  • HGETALL, obtiene todos los campos y valores
  • HMGET, obtiene los valores de los campos dados
  • HDEL, borra un campo
  • HINCRBY, incrementa el valor entero de un campo por un valor dado
  • HINCRBYFLOAT, incrementa el valor decimal de un campo por un valor dado
  • HSTRLEN, obtiene la longitud de un valor almacenado en un campo
  • HEXISTS, comprueba si un campo existe
  • HLEN, obtiene el número de campos
  • HKEYS, obtiene todos los nombres de campos
  • HVALS, obtiene todos los valores
  • HSCAN, itera por los campos

Los hashes de Redis de tamaño reducido realizan un uso muy eficiente de la memoria y se recomienda su uso cuando el número de claves, y sus valores, sean de reducido tamaño.

List

Las listas en Redis están implementadas como listas enlazadas, lo que quiere decir que la inserción y borrado son operaciones rápidas que se ejecutan en un tiempo constante, frente el acceso a los elementos por su índice que es una operación más costosa.

El caso de uso más habitual es almacenar las últimas N ocurrencias de un determinado evento o tipo de objeto. Por ejemplo, se pueden utilizar listas para almacenar los IDs de los últimos artículos consultados en la web de una tienda, de forma que puedan recuperarse muy rápidamente sin necesidad de realizar una consulta costosa sobre una base de datos relacional.

Comandos específicos para el tipo list:

  • LPUSH, inserta elementos por la cabecera
  • LPUSHX, inserta un elemento en la cabecera, pero sólo si la lista existe
  • RPUSH, inserta elementos por la cola
  • RPUSHX, inserta un elemento por la cola, pero sólo si la lista existe
  • LPOP, obtiene y borra el elemento cabecera
  • RPOP, obtiene y borra el elemento de la cola
  • RPOPLPUSH, mueve el elemento de la cola a otra lista
  • LINSERT, inserta un elemento en un índice dado
  • LSET, establece el valor de un elemento en un índice dado
  • LINDEX, obtiene el elemento de un índice dado
  • LRANGE, obtiene los elementos de un rango dado
  • LREM, elimina elementos
  • LTRIM, elimina elementos en un rango dado
  • LLEN, obtiene el número de elementos
  • BLPOP, obtiene y borra el elemento cabecera de forma bloqueante
  • BRPOP, obtiene y borra el elemento de la cola de forma bloqueante
  • BRPOPLPUSH, mueve el elemento de la cola a otra lista de forma bloqueante

Los comandos permiten tratar las listas como pilas y colas añadiendo y eliminando elementos por los dos extremos, operaciones ambas muy rápidas al estar implementadas como listas enlazadas. Los nombres de los comandos utilizan la letra L (left) para indicar que la operación afecta a la cabecera de la lista y R (right) para indicar que afecta a la cola.

Notar además que los comandos que acceden a un rango de elementos utilizan el cero como índice para hacer referencia al primer elemento, y un índice negativo para hacer referenciar a los elementos por el final.

Un caso de uso especial de las listas es su utilización como colas siguiendo un modelo de suscriptores/consumidores. Los comando BLPOP y BRPOP extraen un elemento de una lista, bloqueando al cliente hasta que haya un elemento disponible, si la lista está vacía, o hasta que se sobrepase un tiempo de espera indicado como parámetro de la operación. Usando 0 como tiempo de espera el cliente espera indefinidamente.

Para probar el funcionamiento de Redis como cola de mensajes bloqueante basta con abrir un segundo cliente desde línea de comandos y ejecutar el comando BLPOP o BRPOP sobre una lista creada desde el primer cliente. Si la lista tiene elementos el segundo cliente retornará inmediatamente, pero si no lo tiene se bloqueará hasta que el primer cliente añada un nuevo elemento o se supere el tiempo de espera. Es realmente sencillo e inmediato. Para casos de usos más sofisticados se puede utilizar el comando BRPOPLPUSH, que extrae un elemento de una lista y lo inserta en otra, lo que puede resultar de utilidad para evitar la pérdida de mensajes, almacenándolos en una lista de trabajo temporal.

Set

Los conjuntos en Redis son colecciones desordenadas de elementos. Un mismo elemento sólo puede añadirse una única vez a un mismo conjunto.

El caso de uso habitual para los conjuntos es su utilización como almacén de referencias de unas entidades relacionadas con otras. Como las foreign keys en una base de datos relacional. Por ejemplo, para almacenar los ids de las etiquetas (tags) de las entradas (posts) de un blog.

Comandos disponibles con el tipo list:

  • SADD, añade un elemento
  • SMEMBERS, obtiene todos los elementos
  • SISMEMBER, comprueba si un elemento pertenece a un conjunto
  • SRANDMEMBER, obtiene elementos de forma aleatoria
  • SPOP, obtiene y borra elementos de forma aleatoria
  • SMOVE, mueve un elemento a otro conjunto
  • SREM, elimina elementos
  • SUNION, obtiene la unión de una serie de conjuntos
  • SUNIONSTORE, obtiene la unión de una serie de conjuntos y la almacena en otro
  • SINTER, obtiene la intersección de una serie de conjuntos
  • SINTERSTORE, obtiene la intersección de una serie de conjuntos y la almacena en otro
  • SDIFF, obtiene la diferencia de un conjunto contra una serie de conjuntos
  • SDIFFSTORE, obtiene la diferencia de un conjunto contra una serie de conjuntos y la almacena en otro
  • SCARD, obtiene el número de elementos
  • SSCAN, itera por los elementos

Los casos de uso de los conjuntos a veces parecen un poco forzados, como si se hubieran implementado y luego buscado una utilidad para los mismos. En la práctica se pueden utilizar para realizar uniones, intersecciones y diferencias entre conjuntos. Así como para obtener elementos aleatorios, incluso con repetición.

Sorted Set

Los conjuntos ordenados en Redis son colecciones de elementos a los que se puede asociar un valor o puntuación (score). Dicho valor se utiliza para ordenar los elementos retornados por las operaciones de consulta sobre los mismos.

El caso de uso más habitual es la implementación de listas con ranking (leader boards). Por ejemplo, para implementar una lista de jugadores con las mayores puntuaciones, u obtener la posición en el ranking de un jugador dado.

Comandos específicos para el tipo sorted set:

  • ZADD, añade elementos con puntuación
  • ZINCRBY, incrementa la puntuación de un elemento
  • ZSCORE, obtiene la puntuación de un elemento
  • ZRANK, obtiene el ranking de un elemento
  • ZREVRANK, obtiene el ranking de un elemento de forma descendente
  • ZPOPMIN, obtiene y elimina elementos con la menor puntuación
  • ZPOPMAX, obtiene y elimina elementos con la mayor puntuación
  • ZREM, elimina elementos
  • ZREMRANGEBYSCORE, elimina elementos en un rango de puntuaciones dado
  • ZREMRANGEBYRANK, elimina elementos en un ranking dado
  • ZREMRANGEBYLEX, elimina elementos en un rango lexicográfico dado
  • ZRANGE, obtiene elementos en un rango de puntuaciones dado
  • ZREVRANGE, obtiene elementos en un rango de puntuaciones dado ordenados de forma descendente
  • ZRANGEBYSCORE, obtiene elementos en un rango de puntuaciones dado ordenados por puntuación
  • ZREVRANGEBYSCORE, obtiene elementos en un rango de puntuaciones dado ordenados por puntuación de forma descendente
  • ZRANGEBYLEX, obtiene elementos en un rango de puntuaciones dado ordenados lexicográficamente
  • ZREVRANGEBYLEX, obtiene elementos en un rango de puntuaciones dado ordenados lexicográficamente de forma descendente
  • ZUNIONSTORE, obtiene la unión de varios conjuntos ordenados y la almacena en otro
  • ZINTERSTORE, obtiene la intersección de varios conjuntos ordenados y la almacena en otro
  • ZCARD, obtiene el número de elementos
  • ZCOUNT, obtiene el número de elementos en un rango de puntuaciones dado
  • ZLEXCOUNT, obtiene el número de elementos en un rango lexicográfico dado
  • BZPOPMIN, obtiene y elimina elementos con la menor puntuación de forma bloqueante
  • BZPOPMAX, obtiene y elimina elementos con la mayor puntuación de forma bloqueante
  • ZSCAN, itera por los elementos

En la práctica las puntuaciones se tratan ordenadas de menor a mayor, por lo que la mayoría de los comandos que recuperan rangos de elementos tienen un comando similar pero que recupera en orden inverso. En caso de dos elementos con la misma puntuación se retorna el resultado ordenado en base al orden lexicográfico de los elementos, que en Redis son siempre cadenas de texto. El orden está garantizado en la medida que un conjunto no puede contener dos veces una misma cadena.

El tipo sorted set es utilizado para casos de uso más elaborados, como por ejemplo para obtener productos similares a los consultados por un cliente en la web de una tienda, normalmente en función de las compras realizadas por otros usuarios que también consultaron o compraron los mismos productos.

HyperLogLogs

Este tipo está orientado a obtener el número de elementos distintos de una colección. Su peculiaridad es que no almacena realmente los elementos, por lo que no requiere mucha memoria, y no retorna un valor exacto, sino una aproximación.

Su caso de uso habitual es obtener la cuenta de las distintas ocurrencias de una determinada entidad. Por ejemplo, para obtener el número de las distintas direcciones IP que visitan una página web.

Comandos específicos del tipo HyperLogLogs:

  • PFADD, añade un elemento
  • PFCOUNT, obtiene el número aproximado de elementos
  • PFMERGE, realiza la unión de varios HyperLogLogs y la almacena en otro

Para el caso de uso habitual comentado, contar las distintas IPs que visitan una web, lo ideal sería llevar una cuenta a base de guardar todas las direcciones, incrementando el contador en el caso de que la dirección no estuviera ya guardada. Lógicamente, si se hiciera así, los requerimientos de memoria y tiempo de proceso serían inviables. La solución de Redis es hacer un hash por cada elemento (IP) añadido al conjunto (HyperLogLogs) y contar los distintos hashes. Esto elimina la necesidad de guardar todos los elementos y da una aproximación bastante buena de la cardinalidad (contador) del conjunto. De hecho, según la documentación, con un error de menos del 1%.

Streams

El tipo stream estará disponible a partir de la versión 5 de Redis. Su caso de uso más natural será el de modelar series temporales. Es decir, registrar información asociada a un determinado momento en el tiempo.

Es equivalente a un fichero de log, donde cada línea tiene un timestamp. Y aunque aún es pronto para afirmarlo, puede convertirse en el sistema de referencia para almacenar de forma local las medidas generadas por los sensores de dispositivos IoT (Internet of Things) antes de ser enviadas a la nube para su procesamiento.

Comandos específicos del tipo stream:

  • XADD, añade un elemento
  • XRANGE, obtiene elementos en un rango dado
  • XDEVRANGE, obtiene elementos en un rango dado en orden descendente
  • XLEN, obtiene el número de elementos
  • XREAD, obtiene elementos en un rango dado, opcionalmente de forma bloqueante
  • XREADGROUP, obtiene elementos en un rango para un grupo dado, opcionalmente de forma bloqueante
  • XPENDING, obtiene los elementos pendientes de un grupo

Los streams guardan elementos de igual forma que otras estructuras, pero su implementación está optimizada para su caso de uso más natural. Cuando se añade un elemento se genera automáticamente un ID que se compone de dos partes. La primera parte es un timestamp y la segunda es un secuencial dentro de dicho timestamp. Opcionalmente se puede utilizar un ID propio obviando el generado automáticamente, pero no se espera que sea una opción muy utilizada.

Redis ofrece dos formas de consultar los datos almacenados en un stream. La primera es mediante una consulta por rango, y la segunda es mediante un mecanismo de publicación/subscripción. Esta segunda forma se suma a los mecanismos ya existentes dentro de Redis, como los comandos bloqueantes para extraer elementos de listas, y los comandos más específicos como PUB/SUB para operar con canales. La novedad es que los clientes podrán suscribirse para obtener las entradas registradas a partir de un momento concreto en el tiempo. Con los tipos existentes anteriormente esto no era posible, cuando un cliente se desconectaba y volvía a conectarse no podía acceder a la información generada durante el periodo de desconexión.

En definitiva, un nuevo tipo añadido a Redis que aún está por venir y tiene más características por explotar, como los grupos de consumidores, que garantiza que cada mensaje es enviado a un único consumidor distinto dentro del grupo.

Redis (1)

La introducción de un nuevo tipo de datos en la futura versión 5 de Redis supone una buena oportunidad para escribir un par de artículos sobre este veterano software.

Aunque tradicionalmente vista como una base de datos NoSQL de tipo clave-valor en memoria, útil sólo como cache, en realidad Redis ofrece soluciones para un mayor número de casos de usos a través de sus componentes básicos: tipos y comandos.

Los tipos soportados por Redis son las cadenas de textos, arrays de bits, hashes, listas, conjuntos, conjuntos con peso, e HyperLogLogs (una estructura creada específicamente para estimar el número de elementos distintos de una colección). La futura versión 5 de Redis añadirá además los streams, un tipo especializado para la gestión de series temporal.

Los comandos de Redis son las herramientas a través de la que se construyen estructuras con los tipos soportados y se manipulan sus elementos. La integridad de los resultados está garantizada porque los comandos se ejecutan de forma atómica, pudiendo incluso utilizarse transacciones para ejecutar de forma atómica un conjunto de comandos y hasta ejecutar scripts en Lua.

La documentación de Redis es bastante detallada y su lectura obligada. Una característica de Redis a este respecto es que junto a la definición de los comandos se describen sus casos de uso más habituales, así como los posibles usos que se le puede dar a la herramienta, como por ejemplo su utilización como broker de mensajes mediante colas.

Instalación

No existe una distribución oficial de Redis para Windows, pero se pueden encontrar distribuciones no oficiales, o más simplemente utilizar una imagen de Docker.

En el caso de las distribuciones no oficiales hay que descomprimir el fichero descargado en un directorio y ejecutar redis-server.exe. El fichero de configuración por defecto es redis.conf. Y la forma más sencilla de probar la instalación es usando el cliente de línea de comandos ejecutando redis-cli.exe.

Comentar que Redis soporta de forma nativa su despliegue en una configuración distribuida en cluster con dos modos de persistencia, el modo RDB (Redis Database File), que funciona creando snapshots cada N unidades de tiempo, siempre y cuando se hayan modificado M claves en dicho periodo, y el modo AOF (Append Only File), que funciona almacenando todas las operaciones a fichero de forma inmediata garantizando la integridad de la información a costa de un peor rendimiento.

Claves

Redis almacena todos los datos asociados a una clave única, como las claves primarias en las base de datos relacionales, sólo que las claves en Redis son siempre cadenas.

No hay una forma estándar de escribir claves, sólo unas recomendaciones generales basadas en el uso de namespaces separados por el carácter de dos puntos. Por ejemplo, user:1000:views podría utilizarse para almacenar el número de veces que se ha visto el perfil del usuario con id 1000, pero no deja de ser una recomendación, no una obligación seguir dicho formato.

Todas las claves se tratan siempre en función de su representación a nivel binario. Es decir, no tiene sentido hablar de ningún tipo de codificación, como UTF-8, o juego de caracteres, como ISO-8859-1. Las claves se comparan siempre en base a la secuencia de bytes que las representan.

Comandos específicos para claves:

  • KEYS, obtiene las claves que siguen un patrón
  • SCAN, itera por todas las claves de la base de datos actual seleccionada
  • RANDOMKEY, obtiene una clave de forma aleatoria
  • EXISTS, comprueba si una clave existe
  • RENAME, renombra una clave
  • RENAMENX, renombra una clave, pero sólo la nueva clave no existe
  • DEL, borra una clave
  • UNLINK, borra una clave de forma no bloqueante
  • TYPE, obtiene el tipo del valor almacenado bajo una clave
  • SORT, ordena los elementos de una lista, conjunto o conjunto con pesos
  • EXPIRE, establece el tiempo de vida de una clave en segundos
  • EXPIREAT, establece el tiempo de vida de una clave con un timestamp
  • TTL, obtiene el tiempo de vida de una clave
  • PEXPIRE, establece el tiempo de vida de una clave en milisegundos
  • PEXPIREAT, establece el tiempo de vida de una clave con un timestamp en milisegundos
  • PTTL, obtiene el tiempo de vida de una clave en milisegundos
  • PERSIST, elimina el tiempo de vida de una clave
  • DUMP, obtiene una cadena resultado de serializar una clave
  • RESTORE, restaura una clave a partir de una cadena obtenida con DUMP
  • OBJECT, obtiene un volcado de características internas de una clave
  • TOUCH, modifica la fecha de último acceso a una clave
  • MOVE, mueve una clave a otra base de datos
  • MIGRATE, mueve una clave a otra instancia de Redis

Una característica interesante de Redis es que se puede programar un tiempo de expiración para una clave de forma individual, de forma que pasado ese tiempo Redis la eliminará de memoria automáticamente.

String

Las cadenas de texto son el tipo más básico de Redis. Como ya se ha comentado, es el tipo utilizado para almacenar las claves.

Su caso de uso más habitual es almacenar información a modo de cache. Por ejemplo, para almacenar tokens, cookies, fragmentos estáticos de HTML, o una página completa, utilizando su URL como clave y el HTML como valor.

Comandos específicos para el tipo string:

  • SET, establece el valor de una clave
  • SETNX, establece el valor de una clave, pero sólo si no existe
  • SETEX, establece el valor y tiempo de vida de una clave en segundos
  • PSETEX, establece el valor y tiempo de vida de una clave en milisegundos
  • GET, obtiene el valor de una clave
  • GETSET, establece el valor de una clave y retorna el valor anterior
  • MSET, establece el valor de una serie de claves
  • MSETNX, estable el valor de una serie de claves, pero sólo si ninguna existe
  • MGET, obtiene el valor de una serie de claves
  • INCR, incrementa el valor entero de una clave
  • INCRBY, incrementa el valor entero de una clave por un valor dado
  • INCRBYFLOAT, incrementa el valor decimal de una clave por un valor dado
  • DECR, decrementa el valor entero de una clave
  • DECRBY, decrementa el valor entero de una clave por un valor dado
  • STRLEN, obtiene la longitud del valor de una clave
  • GETRANGE, obtiene una subcadena del valor de una clave
  • SETRANGE, establece una subcadena del valor de una clave
  • APPEND, añade un valor al valor de una clave
  • BITFIELD, ejecuta comandos a nivel de campos de bits sobre el valor de una clave

Comentar que los valores de tipo string en Redis pueden tener una longitud de hasta 512 MB.

Bitmap

Redis no considera bitmap como un tipo, sino como un conjunto de comandos que permiten realizar operaciones a nivel de bit sobre valores del tipo string. Lo que cobra sentido cuando se recuerda que Redis trata las cadenas como secuencias de bytes, no como secuencias de caracteres.

El caso de uso habitual es marcar con un bit a 1 si alguna condición se cumple. Por ejemplo para indicar que el usuario de una aplicación web ha leído y aceptado las condiciones de uso de la misma. O para indicar si quiere recibir correos electrónicos periódicos con las novedades de la misma.

Comandos específicos para el tipo bitmap:

  • SETBIT, establece el valor de un bit
  • GETBIT, obtiene el valor de un bit
  • BITOP, realiza operaciones lógicas a nivel de bit
  • BITCOUNT, cuenta el número de bits a 1
  • BITPOS, obtiene el primer bit a cero o uno

Los bitmaps son adecuados además para señalizar eventos que ocurren con una periodicidad determinada y obtener estadísticas sobre ellos. Por ejemplo, para almacenar si un usuario visita una web cada día, utilizando un bit por día. Siendo lo más habitual distribuir esta información entre varias claves. Por ejemplo, guardando un bitmap por cada año, mes, o periodos más pequeños en función de cada evento concreto.