Skip to main content
paginacion cassandra

Consultas paginadas en Cassandra con CQL y Spring DATA​

1 estrella2 estrellas3 estrellas4 estrellas5 estrellas (Ninguna valoración todavía)
Cargando…

La imagen de carácter de esta entrada es un fotograma de la película “El Nombre de la Rosa” en el que el prota, interpretado por Sean Connery, está muy preocupado por la paginación.

El tema de hoy es cómo paginar los resultados de las consultas realizadas en Cassandra al estilo de Spring DATA utilizando CQL.

En nuestra anterior entrada planteamos dos temas con los que continuar: criterios a considerar en la elección de una base de datos, tanto relacional como NoSQL; y cómo resolver la paginación en Cassandra. Pues bien, por aclamación popular, y sólo por un voto de diferencia (cero a uno); hoy explicaremos cómo realizar consultas paginadas en Cassandra.

Anteriormente comentamos que con Spring DATA, las búsquedas paginadas y por ​columna del estilo findBy<Columna>, funcionan perfectamente en MongoDB y Oracle con JPA, pero en Cassandra no conseguimos que funcionasen, y nuestra intención era dar una funcionalidad común independientemente del store utilizado; así que con esta entrada vamos a ver cómo resolver estos dos asuntos en Cassandra.

Lo primero que tenemos que tener claro es que Cassandra está pensado para almacenar x-illones de datos, así que pensar que vamos a presentar todos estos datos a un usuario para que pueda pasarse media vida viéndolos es un tanto naif; pero está claro que como dijo el sabio “hay gente pa to”.

Veamos el caso de Google, si buscamos “mundial 2014” nos dice que tiene “Aproximadamente 630.000.000 resultados (0,42 segundos)”, y en el pie de página vemos el clásico índice de páginas:

gfi_blog_cas_pag_17

Si echamos cálculos, 630 millones entre 10 resultados por página, y tirando de calculadora, nos salen 63 millones de páginas; pues bien, que levante la mano el que alguna vez haya pasado de la página 10. Existe una vieja leyenda que dice que aquellos que han ido más allá de la página diez, no se ha vuelto a saber de ellos. Lo que quiero decir con todo esto es que ​podemos hacer cosas muy bonitas y muy sofisticas pero lo que tenemos que preguntarnos es, ¿verdaderamente tienen valor para los usuarios?, ¿el coste computacional va a merecer la pena?​

​Dicho esto, y retomando el tema que nos ocupa; veamos cómo podemos paginar nuestros resultados en Cassandra. La paginación Cassandra tiene otra utilidad más allá de la de presentación de los resultados a los usuarios, y es que Cassandra prefiere que le preguntemos muchas veces por pocos datos, a que le preguntemos una única vez por muchos.

Otro aspecto a considerar es que dado que Cassandra puede tener miles de millones de columnas -creo que el máximo que se ha alcanzando es de 2K millones si mal no recuerdo-, la paginación también se puede aplicar a nivel de columna, se podría decir que es una paginación en dos dimensiones; y de hecho es la manera correcta de hacerlo con Astyanax: en lugar de pedir todas las columnas asociadas a una fila, es mejor pedir por ejemplo de 1000 en 1000 columnas.

Empecemos con la práctica. Todo lo que vamos a hacer usa CQL que es un lite SQL-like de Cassandra. En SQL tenemos las funciones TOP, LIMIT y ROWCOUNT que nos permiten resolver la paginación en la propia query, pero el CQL de momento no es tan sofisticado, así que vamos a ver qué nos ofrece para conseguir nuestro propósito.

Partamos de una Column Family llamada users y rowkey username, en la que vamos a insertar 10.000 filas con username igual a ‘ALBERTO + <CONTADOR>’ -nada que ver con el ciclista- y CONTADOR va desde 0 a 9.999:

gfi_blog_cas_pag_02

Las consultas en CQL tienen un límite por defecto de 10.000 que se puede cambiar en la propia SELECT pero esto lo único que nos va a dar son los primeros 10.000 resultados; si nuestra CFamily tiene más resultados, tendremos que aumentar el límite. En versiones anteriores, solía pasar que al aumentar el límite de resultados, la query devolvía un error de timeout y lo que teníamos que hacer es aumentar el timeout, pero la cuestión es: ¿cuál es un TTL de consulta válido y razonable si nuestros datos no paran de crecer: 1 segundo, 1 hora, 1 día, …? Cassandra 2.0 ha resuelto el problema del tiemout a través de un sistema de cursores.

​​Si consultamos los datos, lo primero que nos vamos a dar cuenta es que las filas no aparecen ordenadas por el CONTADOR​, es decir, en el orden en el que fueron insertadas,​ entonces ¿qué criterio sigue Cassandra para devolvernos los datos?​

gfi_blog_cas_pag_03

Pues la clave está en …… la clave, en nuestra rowkey. Cassandra, para conseguir una dispersión homogénea de los registros entre todos los nodos del cluster, utiliza funciones hash; son los conocidos como particionadores (partitioners) -en MongoDB se utiliza el concepto de sharding-. Lo que pretenden es que todos los nodos del cluster almacenen el mismo número de filas de una misma CFamily. Imaginaos en nuestro caso que el particionado se hiciera por el nombre, de manera que todos los nombres que empiezan por ‘A’ fueran a un mismo nodo; sería una catástrofe porque toda nuestra CFamily sería ubicada en un único nodo con lo cual todas las capacidades que nos ofrece Cassandra de clustering, que es su mayor valor, no nos valdrían para nada.

Hemos dicho que a partir de la rowkey se aplican funciones hash, ¿y qué resultado tiene una función hash?, un token; ¿y cómo se llama la función de CQL que tenemos que utilizar para obtener el token? …… TOKEN -me estoy quedando calvo por momentos-.

Volvamos a realizar la consulta anterior incluyendo la función TOKEN:

gfi_blog_cas_pag_04

Lo que vamos a hacer para resolver la paginación es utilizar el token como clave de paginación de man​era que la siguiente página empieza a partir del token del último registro de la página en la que nos encontramos, siguiendo con nuestro ejemplo:

gfi_blog_cas_pag_05

Siendo limit el tamaño de nuestra página.

Veamos cómo llevar esto a Spring DATA. En Spring DATA la paginación se consigue a través de los interfaces:

  • Page extends Slice: Representa una página consultada con sus resultados.

gfi_blog_cas_pag_06 gfi_blog_cas_pag_07

  • Pageable:​ ​Representa una página que se quiere consultar.

gfi_blog_cas_pag_08

​Pues bien, para resolver la paginación en Cassandra con Spring DATA hicimos lo siguiente:

  • ​​Implementar el interface Page para CQL: El campo partitionKey representa el token de la última fila consultada y pageSize el tamaño de la página a devolver.

gfi_blog_cas_pag_09

Para los curiosos, CassandraPojo lo que nos permite es que las consultas realizadas con CQL devuelvan pojos de nuestras propias clases; es similar a cómo funciona Astyanax. ​

  • ​​​Implementar el interface Pageable para CQL:

gfi_blog_cas_pag_10

  • Implementar el repositorio CQL similar a los repositorios en Spring DATA y con los mismos métodos que ofrecen:

gfi_blog_cas_pag_11

​​Para los curiosos, RowPojonator permite cargar una fila en un pojo de la clase T. Recibe su nombre en reconocimiento al profesor Dr. Doofenshmirtz​, creador ​de ​numerosos inators ​y​ fuente inagotable de inspiración.​​

​CassandraPojo ​contiene los campos necesarios que indican cómo se ha de llevar a cabo la carga: fieldId contiene el​ ​nombre del campo clave, nuestro rowkey, indicado en nuestra clase pojo con la notación org.springframework.data.annotation.Id.class​;​​ ​y tableName​​, nuestra CFamily, ​​que se indica con la anotación org.springdata.cassandra.mapping.Table.class​.​​​​​​

gfi_blog_cas_pag_12

Veamos el método findAll con el que obtener todos los registros de una CFamily de manera paginada:

gfi_blog_cas_pag_13

Lo que hacemos es ejecutar una query de CQL igual que la que hemos visto anteriormente haciendo uso de las funciones TOKEN y LIMIT; cargar las filas obtenidas en pojos; y devolverlo todo en un objeto de la clase CqlPage.

Para solicitar la siguiente página, bastará con invocar al método nextCqlPageable que devuelve un objeto CqlPageable en el que la partitionkey se corresponde con el token de la última fila de la última página consultada.

gfi_blog_cas_pag_14

Probemos todo el tinglado, para ello tenemos un estupendo test con el que vamos a consultar nuestros 10.000 registros en páginas de tamaño 13 para fastidiar:

gfi_blog_cas_pag_15

Como podéis ver, en cuatro líneas lo tenemos todo resuelto​ de la misma manera que lo tendríamos resuelto con Spring DATA​.

El resultado son 769 páginas con 13 registros, y la página 770 que tiene 3, en total 10.000 registros consultados ​en algo más de 7 segundos. Las pruebas están hechas desde un único portátil con un sólo nodo de Cassandra y la memoria bastante saturada así que no está nada mal.

gfi_blog_cas_pag_16

​Obviamente esta solución tiene sus limitaciones y es que sólo se puede pasar de una página a la siguiente; ​​si queremos hacer un sistema más sofisticado será a costa de penalizar el rendimiento y supongo que esa es la razón por la que Google ha decidido resolverlo así.

​Para siguientes blogs, además de lo que ya tenemos pendiente, podemos contar algo de Java Generics y Reflection que son ​de ​las cosas más útiles que tenemos en Java.​

​Hasta pronto.​

 

 

Alberto Rodríguez Berzal

Alberto Rodríguez Berzal

Arquitecto software, especialista SOA&BPM, programador multilenguaje, experto J2E, administrador Weblogic y más. Con más de 15 años de experiencia profesional y 25 como programador, pero solo uno haciendo cerveza casera.

Alberto Rodríguez Berzal ha escrito 2 entradas


Alberto Rodríguez Berzal

Alberto Rodríguez Berzal

Arquitecto software, especialista SOA&BPM, programador multilenguaje, experto J2E, administrador Weblogic y más. Con más de 15 años de experiencia profesional y 25 como programador, pero solo uno haciendo cerveza casera.

Deja un comentario

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