All posts by Jorge Arroyo

About Jorge Arroyo

Soy Jorge Arroyo, Ingeniero de Software en Valencia.

Como utilizar blpop de Redis como sincronizador de tareas en un entorno distribuido

Hace poco tuve que desarrollar un sistema de cálculo de probabilidades de forma distribuida, de manera que tenía múltiples procesos que estaban esperando inputs para calcular las probabilidades de una parte del dataset total. Cada uno de estos procesos podía realizar sus operaciones independientemente de los otros procesos, pero como es de esperar, era importante que dado un input cualquiera:

  • Lo calculara el primer proceso que estuviera disponible.
  • Solo lo ejecutara un proceso.

La carga a mi sistema llegaba de una forma irregular e impredecible, ya que depende de qué y cuándo quieren hacer algo los usuarios, por lo que los procesos de cálculo tenía que ejecutarlos en modo daemon para que estuvieran siempre escuchando, atentos a cuando llegara el trabajo.

En este punto, en la sincronización de la lectura de los inputs a procesar, es cuando decidí utilizar Redis, y más concretamente la operación blpop (Blocking List Pop).

Redis es una base de datos NoSql cuyas principales características son:

  • Está en memoria.
  • Es key – value.
  • Permite trabajar con un conjunto limitado de tipos de datos (strings, hashes, lists, sets y alguno más).

Y la operación blpop lo que hace es bloquear el cliente que está intentando leer de la lista hasta que hay por lo menos un elemento para recuperar, pudiendo establecer si se quiere un timeout, o dejar esperando “para siempre”.

Con esto, los actores que generan el input que debe ser calculado estadísticamente, acaban insertando en la cola, llamémosla INPUT_TASKS, mediante operaciones RPUSH, es decir, insertando por la derecha (al final de la lista). Por otra parte tenemos N daemons ejecutándose y bloqueados, a la espera de que entren tareas en INPUT_TASKS, y respetando un “orden en la cola” por estricto orden de llegada. Conforme va habiendo tareas en el INPUT_TASKS los daemons van cogiéndolas y procesándolas. Si la lista de tareas es más larga que la capacidad de los daemons, los elementos permanecerán en INPUT_TASKS hasta que los procesos de cálculo vayan terminando y vuelvan a la cola a recuperar la siguiente tarea. De esta forma se van consumiendo todas las tareas hasta que se vacía INPUT_TASKS, y de nuevo los daemons se quedan esperando nuevos inputs de forma bloqueante.

Advertisements

Mi opinión sobre el libro “Relevant Search”

El libro se presenta como la herramienta definitiva para atacar la relevancia de cualquier sistema de búsqueda, alejándose de los aspectos excesivamente técnicos y centrándose en como conseguir llevar los mejores usuarios a cada usuario en cada ocasión.

Empieza con algunas definiciones para ponernos en contexto y consigue enfocar las búsquedas desde un punto de vista interesante, en base a las “features” de los documentos, y las “signals” que tenemos que saber capturar y explotar de forma adecuada.

El capítulo más técnico desde mi punto de vista, es el más útil. Se centra en explicar de manera pormenorizada como Lucene calcula el score con el que se mide la relevancia. Explica muy bien prácticamente todas variables que influyen, y da la herramienta base a partir de la que ponerse a trabajar y analizar el porqué de que un resultado vaya antes que otro.

A partir de aquí, el libro empieza a perder interés, pasando por conceptos, y temas que tienes que conocer obligatoriamente, si has trabajado con Solr o Elasticsearch. Indexación, tokens, análisis, boosting, filtros, facetings … herramientas básicas que seguro habrás utilizado si has puesto en funcionamiento algún buscador. Va resolviendo problemas, pero siempre de una forma demasiado particular para el ejemplo.

Por último intenta establecer como trabajar en el desarrollo del sistema de búsqueda y propone de forma breve algunos sistemas para personalizar y enriquecer la experiencia de búsqueda.

Mi conclusión es que para aquel que haya trabajado algún tiempo con sistemas de búsqueda en el “mundo real”, es decir, poniendo en producción con usuarios reales, este libro no aporta demasiado. Pese a venderse como un libro avanzado sobre la relevancia de la búsqueda, yo lo veo más como un material para empezar en este mundo sentando unas buenas bases, que te ayudarán a ver el problema global y no centrarte demasiado en las cuestiones técnicas. De todas formas, no creo que haya sido tiempo perdido, ya que la lectura de “Relevant Search”, como la mayoría de los libros, ha conseguido que surjan cuatro o cinco ideas que creo que vale la pena probar.

Hadoop, Hive, Pig, Sqoop, Impala, a vista de pájaro

Estoy asistiendo al curso de “Desarrollo de aplicaciones Big Data con Apache Spark y Apache Hadoop” en el COIICV que está impartiendo Mario Renau de una forma magistral. El curso está resultando bastante interesante, y sobre todo estoy consiguiendo hacerme una fotografía de todo, o por lo menos de gran parte, el universo Hadoop.

Hadoop, como parte de fundamental del Big Data, es algo que me ha interesado desde hace tiempo, y a lo que he intentado acercarme en varias ocasiones, tengo que confesar, sin demasiado éxito. Ahora me doy cuenta que el problema que tenía era que no conseguía percibir la “big picture” de todo esto. En seguida empezaba a aparecer mucho software adicional que se integraba con Hadoop, que te vendían que si eran capas de abstracción, que si la solución definitiva, que si con esto lo conseguirás todo … como siempre en estos casos, todo es relativo.

En esta entrada quiero dar una visión muy sencilla de lo que es Hadoop y alguno de estos componentes, y tiene que ser muy sencilla porque en estos momentos, mis conocimientos son muy limitados, los de alguien que está empezando a jugar con todo esto, pero quizás, ahora sea el momento ideal para poder dar este enfoque sencillo, ya que seguramente, cuanto más avance, más complicado se vuelva.

hadoop-bird

Soy consciente de que para un experto, las líneas de conexión no serán exactamente así, que serán mucho más complejas y que habrá toda una serie de condicionantes y circunstancias que quedan fuera del gráfico anterior, pero de nuevo, quiero decir que es una simplificación de y para principiantes.

En el centro de todo está HDFS, que es el Sistema de Ficheros Distribuido de Hadoop. Gracias a él Hadoop puede guardar tanta cantidad de datos para su procesamiento, de forma distribuida, segura y eficiente. HDFS gestiona todas las necesidades de lectura y escritura de una forma transparente, nosotros no nos preocupamos de que nodo guarda tal o cual, trabajamos directamente con el fichero y HDFS se encarga de obtener de los nodos distribuidos lo que le pedimos.

Hadoop Map Reduce es la implementación del modelo teórico Map – Reduce, y que es la base de cualquier proceso en Hadoop. En Hadoop, al final, cualquier tarea se traduce en procesos Map Reduce.

Siguiendo en el centro de la imagen, acabamos con Hadoop YARN que es el encargado de orquestar todo el cluster Hadoop, de asegurar la disponibilidad, rendimiento, acceso óptimo a los datos, etc.

Hasta aquí lo que sería el core de Hadoop, y “simplemente” con esto ya podríamos funcionar, pero si algo hay que reconocer es que Hadoop es complejo y farragoso, de ahí que aparezca software que intenta darle una cara más amable, más parecida a herramientas a las que estamos más acostumbrados, como consultas SQL o lenguajes de scripting. Estas son las cajas en rojo que he dibujado para Hive, HCatalog, Impala, Pig y Scoop.

Hive en mi opinión es realmente interesante, su función principal es hacer que podamos trabajar con todos los ficheros almacenados en el sistema HDFS como si fueran tablas de una base de datos relacional. Muy a groso modo, nos permite definir estructura en forma de tabla para los ficheros, soportando múltiples formatos, para posteriormente poder consultar esa tabla (y otras) con sentencias SQL que seguro conocemos bien. Hive se encarga de traducir las sentencias SQL en procesos Map – Reduce, que se ejecutan directamente en Hadoop para darnos el resultado.

HCatalog es un API Rest sobre Hive cuyo objetivo es facilitar la integración de otras herramientas con Hive mediante el uso de dicho API.

Impala ofrece la misma función que Hive, ofrecer un interfaz SQL sobre Hadoop, pero la diferencia fundamental es que intenta que las consultas se ejecuten más rápidamente mediante un consumo adicional (parece que realmente alto) de memoria. Impala guarda en memoria gran parte de los datos para ofrecer este alto rendimiento.

Pig es un lenguaje de scripting sobre Hadoop con funciones de alto nivel que facilita la programación, reduciendo drásticamente la complejidad y las líneas de código. Cargar datos, filtrar, recorrer, agrupar, son algunos ejemplos. De la misma forma que Hive, traduce las instrucciones de más alto nivel a procesos Map Reduce que se ejecutan en Hadoop.

Por último Sqoop, es una herramienta para importar y exportar datos entre bases de datos relacionales y HDFS.

El universo Hadoop tiene muchísimas más herramientas satélite que ofrecen funcionalidades muy interesantes, pero mi limitada visión llega hasta aquí.

Como favorezco la concentración para mis tareas clave

A lo largo de cualquier día nos encontramos con decenas de tareas de todo tipo, redactar un email, corregir un bug, asistir a reuniones, monitorizar procesos, pequeños desarrollos … pero ninguna de estas son lo que yo considero “tareas clave”. Las tareas clave son aquellas tareas que son realmente importante para nosotros, para nuestros proyectos, aquellas que pueden marcar la diferencia y hacerlos evolucionar. En definitiva, y sin desmerecer las otras tareas, las tareas clave requieren que demos lo mejor de nosotros mismos para llevarlas a cabo.

Para conseguir desempeñar las tareas clave de forma óptima, a mi me gusta preparar el entorno, algo así como activar el “modo concentración”, en el que se que voy a poder trabajar en las mejores condiciones. Esto es lo que yo hago:

Siempre planifico las tareas claves al principio del día, cuando mi mente y mi cuerpo están más frescos y aún no acuso el cansancio de todo el día. El día anterior planifico cuales serán las tareas clave del día siguiente, y cuando llego por la mañana me pongo directamente a ellas, sin revisar el email o cualquier otra cosa.

Abordo una única tarea clave cada vez, no creo en la multitarea.

Intento minimizar las interrupciones técnicas externas, es decir, cierro mi cliente de correo electrónico y cualquier aplicación de mensajería instantánea que tenga en el ordenador. También silencio el móvil y lo pongo boca abajo, para no ver las notificaciones que me puedan llegar. De esta forma intento que ningún aviso o alerta distraiga mi concentración durante la tarea clave.

Si el entorno físico es ruidoso utilizo auriculares y música de concentración para reducir el ruido ambiental.

Me fijo periodos de tiempo de media hora, con un cronómetro visible. Durante esa media hora me obligo a estar concentrado y trabajando en la tarea clave que tengo entre manos. Para mi son importantes estos “micro periodos” porque son abordables, es decir, se que puedo hacerlo, que puedo estar media hora seguida dedicándome exclusivamente a una única cosa, a esa tarea clave que he decidido. Si por el contrario me propusiera dedicar dos horas seguidas a la tarea, soy consciente que difícilmente podría mantener una concentración alta tanto tiempo seguido. Una vez cumplido el periodo de media hora, descanso cinco minutos, haciendo lo que quiera, preferiblemente levantarme y no pensar en nada relacionado con la tarea clave.

Soy consciente que es imposible aislarme completamente, y es fácil que nuevas tareas, ideas o cualquier otra cosa me venga a la cabeza en este momento, no hay problema, simplemente lo gestiono apuntándolo para más tarde.

Como conclusión, quiero remarcar que la concentración se busca, tenemos que perseguirla y favorecerla, ser conscientes de lo preciada que es y aprovecharla para lo que de verdad nos interesa.

 

Utilizar MongoDB como sistema de caché

Prácticamente cualquier servicio online con un mínimo de tráfico y de complejidad requiere de un sistema de cache para aligerar su carga y reducir el tiempo de respuesta de ciertas peticiones, que deben ser aquellas peticiones más comunes y aquellas más costosas en cuanto a recursos que consumen.

En este punto siempre surge la incógnita sobre que sistema de caché utilizar, y factores como el tamaño y la estructura de los datos, o la velocidad de lectura y escritura son variables que debemos tener en cuenta a la hora de elegir uno.

Una muy buena opción desde mi punto de vista es utilizar Mongo para cachear datos. La idea es que tienes una colección por cada tipo de información que quieres cachear:

db.micache = {  
   _id:String,
   datos: { 
      ... todo lo que queremos cachear ...
   },
   fecha:Date
}

Donde:

  • El _id es de tipo String y es una cadena de caracteres en cuya generación participan todas las variables que influyen en el resultado a cachear. Es decir, si por ejemplo queremos cachear los contactos de un usuario, el _id debe ser generado con el identificador del usuario, algo del estilo “contacts_007”. Otro ejemplo sería si quisiéramos cachear los contactos compartidos por dos usuarios diferentes cualesquiera, en este caso generaríamos algo como “shared_007_x_199”, que almacenaría datos diferentes a otra entrada que podría ser “shared_007_x_203”.
  • En datos guardaremos lo que queramos cachear, sea del tipo que sea, aprovechando la flexibilidad de Mongo que nos va a permitir almacenar cualquier cosa sin tener que definir ningún esquema previo. Además otra de las ventajas de Mongo es que los datos guardados pueden ser del tamaño que queramos, sin importar lo grandes que sean.
  • Por último en fecha se almacenará la fecha de creación del objeto cacheado, y además crearemos un índice con tiempo de expiración, para que sea el propio Mongo automáticamente el que borre de caché los objetos que hayan cumplido el tiempo de expiración. En la siguiente línea decimos que cualquier documento guardado expirará a los 14.400 segundos, es decir, estamos creando una cache de 4 horas.
db.micache.createIndex( { "fecha": 1 }, { expireAfterSeconds: 14400 } )

 

Las ventajas que aporta esta solución son:

  • Es extremadamente flexible en cuanto a la estructura de datos a guardar. Texto, números, mapas, listas, cualquier tipo de estructura puede guardarse directamente en Mongo, y será aquel que lo guarde y recupere el que le sepa como manejarlo.
  • Disponemos de una capacidad de almacenamiento muy alto, no limitado a la memoria RAM que tengamos disponible. El disco es más barato que la memoria, y nos va a permitir cachear sin tener que preocuparnos prácticamente por lo que consumamos.
  • Si estamos utilizando un Replica Set de Mongo, tendremos además un sistema de cache con alta disponibilidad y escalable, ya que podemos distribuir las lecturas entre los nodos secundarios. Además si añadimos un nuevo servidor de aplicaciones, o tenemos que reiniciar alguno, tendrá inmediatamente toda la información cacheada a su disposición, lo que no ocurriría si cada servidor de aplicaciones guarda su cache en su memoria local.

El inconveniente que le podemos atribuir es:

  • Al ser acceso a disco no será tan rápido como es el acceso a memoria, pero como siempre vamos a acceder por _id será suficientemente rápido para la mayoría de los casos requeridos.

Yo lo estoy utilizando en sistemas en producción con un excelente resultado.

Solr, uso de cursorMark para paginaciones profundas (deep paging)

Solr tiene un problema de rendimiento cuando empezamos a pedirle páginas muy profundas, lo conocido como deep paging. Es decir, dada una búsqueda, el tiempo de respuesta en devolver las primeras páginas de resultados y el tiempo en devolver las páginas, pongamos, 5000, 5001, etc es muy diferente, siendo este último caso el más penalizado.

Es este magnífico post explican el funcionamiento del cursorMark, cuya idea principal es que Solr se guarda en memoria un cursor apuntando a la página profunda que le has pedido, e identifica dicho cursor con una clave única. Posteriormente si quieres seguir la paginación en ese punto, solo tienes que pasarle de nuevo esa clave única, y Solr irá directamente a la siguiente página de resultados, ahorrando muchísimo tiempo al tenerlo ya en memoria.

El funcionamiento un poco más detallado es el siguiente:

Query a Solr con parámetro cursorMark=*

Al hacer la primera query, con los criterios de búsqueda que se necesiten, se añade un parámetro adicional cursorMark=*

http://localhost:8983/solr/example?q=*:*&rows=100&cursorMark=*

Con esto le estamos indicando a Solr que cree un nuevo cursor para esta consulta.

 

Solr devolverá la clave del cursorMark.

En el response, Solr añadirá la clave con la que identificar el cursor.

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  ...
  </lst>
 </lst>
<result name="response" numFound="32" start="0">
 <doc>
 ...
 </doc>
 </result>
 <str name="nextCursorMark">AoIIP4AAACgwNTc5QjAwMg==</str>
</response>

 

Siguiente query a Solr con parámetro cursorMark=CLAVE

Con la segunda query le pasamos el cursorMark recibido en el response de la primera query, sin especificarle otro parámetro start. Al recibir este parámetro Solr sabrá que tiene que continuar por donde se había quedado y nos devolverá el siguiente bloque de documentos.

http://localhost:8983/solr/example?q=*:*&rows=100&cursorMark=AoIIP4AAACgwNTc5QjAwMg==

Solr devolverá la siguiente clave del cursorMark. En el response, Solr añadirá la nueva clave con la que identificar el cursor para el siguiente bloque de resultados

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  ...
  </lst>
 </lst>
<result name="response" numFound="32" start="0">
 <doc>
 ...
 </doc>
 </result>
 <str name="nextCursorMark">AoIIP4AAACoxMDAtNDM1ODA1</str>
</response>

Y así podemos continuar mientras se quiera.

Yo intenté utilizar este mecanismo de Solr en un proceso batch en el que lanzaba queries que me devolvían muchos resultados, estoy hablando de cientos de miles, y necesitaba procesarlos todos, del primero al último. Pero la gran mejora en rendimiento que se producía inicialmente se iba degradando con el tiempo hasta dejarme el servidor frito por exceder el uso de memoria que tenía asignado.

La cuestión es que todos estos cursores Solr los va almacenando en memoria, de ahí que en caso de utilizarlos vayan tan rápidos, pero si pedimos demasiados, no tendrá nunca suficiente espacio. Por tanto, el cursorMark es una buena opción cuando vamos a reutilizar mucho los cursores, no cuanto tenemos una variabilidad alta.

¿Y como solucioné en mi caso el problema del deep paging? Pues troceando la query original, que me devolvía muchas páginas de resultados, en N “subqueries” cada una de las cuales me devolvía unas pocas páginas. Algo así:

Mi query original era más o menos así:

http://localhost:8983/solr/example?q=expresion de busqueda&start=0&rows=10000

que de esa forma, me devolvía cientos de miles de documentos y por tanto tenía que ir paginando con el consiguiente incremento del tiempo de respuesta cuanto más profunda era la página pedida.

http://localhost:8983/solr/example?q=expresion de busqueda&start=10000&rows=10000
http://localhost:8983/solr/example?q=expresion de busqueda&start=20000&rows=10000
http://localhost:8983/solr/example?q=expresion de busqueda&start=500000&rows=10000
...etc

Para trocearlo en subqueries busqué otro campo que no influyera en cada búsqueda concreta, del que pudiera conocer los valores mínimos y máximos, que me sirviera para lanzar queries por rangos más o menos del mismo tamaño y que me devolviera pocas páginas. Por suerte tenía mi identificador interno de los datos que indexaba, un autoincremental de tipo Long que cumplía estos criterios. Llamemos a este campo myID.

Manos a la obra, cogí myID y transformé la query original en N subqueries con filtro por rango de myID.

http://localhost:8983/solr/example?q=expresion de busqueda&fq=myID:[0 TO 200000]&start=0&rows=10000
http://localhost:8983/solr/example?q=expresion de busqueda&fq=myID:[0 TO 200000]&start=10000&rows=10000
http://localhost:8983/solr/example?q=expresion de busqueda&fq=myID:[2000000 TO 400000]&start=0&rows=10000
http://localhost:8983/solr/example?q=expresion de busqueda&fq=myID:[2000000 TO 400000]&start=10000&rows=10000

Está claro que no me ahorré por completo la paginación, pero si tener que irme a páginas extremadamente profundas para las que Solr tarda mucho tiempo en responder.

Además, al utilizar Filter Queries (fq), aprovechaba el cacheo que hace Solr de dichos Filter Queries, con lo que otras búsquedas del mismo proceso con diferente expresión en la query, pero con los mismos rangos de filter queries, se beneficiaban del cacheo previo.

http://localhost:8983/solr/example?q=otra busqueda&fq=myID:[0 TO 200000]&start=0&rows=10000

Con este enfoque conseguí leer de Solr enormes cantidades de resultados de búsqueda sin colapsar en ningún caso el servidor por uso excesivo de memoria.

 

Limitar el uso de memoria de scripts en Groovy

Cuando lanzas múltiples procesos Groovy en una misma máquina, debes de tener cuidado con el consumo de memoria que cada proceso hace en particular para garantizar el buen uso global de dicha memoria, de manera que no malgastes innecesariamente.

En este aspecto Groovy no se comporta como yo esperaba, ya que aún sabiendo que mis procesos no requerían más de 128Mb, al lanzarlos y medirlos veía que inicialmente se me iban hasta 700Mb y que solo con el tiempo acababan reduciendo su uso por debajo de los 128Mb. Además, este descenso no era inmediato, sino que necesitaba bastante tiempo para producirse, con lo que por el camino era muy fácil que me “petara” la máquina.

Por suerte la solución es sencilla, solamente hay que establecer el uso de memoria del script Groovy en el momento de lanzarlo.

JAVA_OPTS="-Xmx128m -Xms64m -Dfile.encoding=UTF-8" groovy -c utf8 -Dfile.encoding="UTF-8" my_groovy_script.groovy

Con la instrucción anterior le estoy dando al script una memoria inicial de 64Mb, y una memoria máxima de 128Mb, con lo que me aseguro que siempre fluctúa en ese intervalo.

Apunto y olvido … ya lo revisaré más tarde

Cuando estás trabajando, concentrado en una tarea, es muy común que te asalten nuevas ideas, tareas nuevas que tienes que hacer, errores que detectas, hasta cosas que tienes que hacer en casa o que ir a comprar. Nuestro subconsciente es muy traicionero, y está acechando continuamente para intentar interrumpir nuestra concentración, nuestra productividad.

Obviamente es algo que no quieres que se te pase, que no quieres olvidar. Yo en estos casos lo que hago es apuntarlo e intentar olvidarlo lo más rápido posible, de forma que interrumpa mi concentración lo mínimo posible, y poder seguir con la tarea que estaba.

Pero hay que tener algún cuidado en cómo apuntamos estos “pensamientos”:

Tienes que apuntarlo rápido.

Ten a mano siempre aquello que vayas a necesitar para apuntar, sea un software, una libreta o un trozo de papel. No puedes perder tiempo en ponerte a buscar donde apuntarlo cuando lo necesites.

Apúntalo siempre en el mismo sitio.

La “bandeja de entrada” debe ser única. Todo tienes que escribirlo en el mismo sitio, ya que eso te da la seguridad y tranquilidad de que luego lo encontrarás posteriormente, de que no vas a perderlo.

Se fuerte, no caigas en la tentación de hacer caso a lo que te viene a la cabeza.

Debes ser fuerte, y forzarte a ti mismo a olvidar lo que acabas de apuntar. No puedes caer en la tentación de ir a mirar eso que acabas de pensar, ni siquiera un poquito … NO. Debes seguir con el trabajo en el que estabas concentrado y recuperar la concentración lo más rápido posible.

Revisa periódicamente la bandeja de entrada.

Que puedas olvidar instantáneamente lo que acabas de olvidar solo será posible si tienes la seguridad de que posteriormente vas a revisar tu bandeja de entrada. No tiene por que ser dentro de una hora, ni hoy mismo, pero periódicamente tienes que ponerte a revisar todo lo que apuntaste en tu inbox y procesarlo.

Yo, al pasar la mayoría del tiempo delante del ordenador, utilizo Evernote como herramienta para “apuntar y olvidar”.Te ofrece unas pequeñas utilidades que te facilitan esto y consiguen que sea muy rápido, pudiendo tanto anotar texto como imágenes, capturas de pantalla, artículos, links. Además, si te pilla fuera del ordenador, también tiene apps para móvil que puedes utilizar, con lo que te aseguras que todo va a acabar en la misma bandeja de entrada.

ExtendedDismax de Solr, qué significan los parámetros qf, qs, pf, ps, pf2, ps2, pf3, ps3

El ExtendedDismax de Solr, es decir el que utiliza Solr cuando hacemos una búsqueda con defType=edismax tiene unos parámetros realmente interesantes que afectan al cálculo del score, aunque sinceramente, cuesta un poco comprender exactamente en qué consiste y qué aporta cada uno de ellos.

qf (Query Fields)

Son los campos indexados contra los que va a buscar la expresión que llega en la query (parámetro q), por lo que afecta directamente al matching de los resultados, pero además ya permite hacer boosting para cada uno de los campos que se especifiquen. Supongamos que tenemos dos campos en nuestro schema, que son titulo y descripcion, y buscamos algo asi:

        q=+patata +caliente&qf=titulo^10 descripcion

Solr buscará los términos “patata” y “caliente” en los campos titulo y descripcion, pero si lo encuentra en titulo le otorgará un boosting de 10, mientras que si lo encuentra en descripcion, lo hará con el boosting por defecto de 1.

qs (Query phrase Slop)

Viene a indicar la proximidad que exigirá Solr cuando nos llegue un término compuesto entrecomillado. Afecta al matching. Si por ejemplo buscamos:

q=”patata caliente”&qs=2

Solr buscará los términos “patata” y “caliente” con una proximidad de 2 (valor de qs). Es decir, lo que sería una búsqueda por proximidad: q=”patata caliente”~2. Pero la gracia de qs es que lo hace de forma automática con todo aquello que se pida entre comillas. Otro ejemplo.

        q=+pollo +”patata caliente” +comida +”plato menu dia”&qs=3

Acabaría buscando q=+pollo +”patata caliente”~3 +comida +”plato menu dia”~3 . Recuerda que qs afecta al matching.

pf (Phrase Fields) y ps (Phrase Slop)

Estos dos parámetros en conjunto definen el boosting a aplicar cuando se encuentren todos los términos de búsqueda con una proximidad determinada. Importante, no afecta al matching, solo afecta al cálculo del score. De este modo, si por ejemplo buscamos:

q=+pollo +patata +caliente +comida +menu&pf=titulo^20 descripcion^5&ps=7

Solr incrementará:

  • con un boosting de 20 si encuentra en el campo titulo todos los términos con una proximidad de 7 ( titulo:(“pollo patata caliente comida menu”~7)^20 )
  • con un boosting de 5 si encuentra en el campo descripcion todos los términos con una proximidad de 7 ( descripcion:(“pollo patata caliente comida menu”~7)^5 )

pf2 (Phrase bigrams Fields) y ps2 (Phrase bigram slop)

Estos dos parámetros en conjunto definen el boosting a apicar cuando se encuentre cada uno de los bigramas de los términos de búsqueda con una proximidad determinada. Estos parámetros tampoco afectan al matching, solo al score. Así si buscamos:

q=+pollo +patata +caliente +comida +menu&pf2=titulo^40 descripcion^15&ps2=3

Solr incrementará:

  • titulo:(“patata caliente”~3)^40 , es decir, con un boosting de 40 (pf2=titulo^40) si encuentra en el campo titulo el bigrama “patata caliente” con una proximidad de 3 (ps2=3).
  • titulo:(“caliente comida”~3)^40, idem.
  • titulo:(“comida menu”~3)^40, idem.
  • descripcion:(“patata caliente”~3)^15, es decir, con un boostingde 15 (pf2=descripcion^15) si encuentra en el campo descripcion el bigrama “patata caliente” con una proximidad de 3 (ps2=3)
  • descripcion:(“caliente comida”~3)^15, idem.
  • descripcion:(“comida menu”~3)^15, idem.

pf3 (Phrase trigram Fields) y ps3 (Phrase trigram slop)

Estos dos parámetros en conjunto definen el boosting a aplicar cuando se encuentre cada uno de los trigramas de los términos de búsqueda con una proximidad determinada. Estos parámetros tampoco afectan al matching, solo al score. Así si buscamos:

q=+pollo +patata +caliente +comida +menu&pf3=titulo^60 descripcion^25&ps3=5

Solr incrementará:

    • titulo:(“patata caliente comida”~5)^60 , es decir, con un boosting de 40 (pf3=titulo^60) si encuentra en el campo titulo el trigrama “patata caliente comida” con una proximidad de 5 (ps3=5).
    • titulo:(“caliente comida menu”~5)^60, idem.
    • descripcion:(“patata caliente comida”~5)^25, es decir, con un boostingde 25 (pf3=descripcion^25) si encuentra en el campo descripcion el trigrama “patata caliente comida” con una proximidad de 5 (ps3=5)
    • descripcion:(“caliente comida menu”~5)^25, idem.

Y hasta aquí el intento de explicación, buena suerte con el ajuste del scoring.

Como optimizar procesos de inserción masiva en MongoDB

Mongo DB es un document storage NoSql que tiene una capacidad de escritura muy alta, y realmente la tiene, por mi experiencia mucho más que por ejemplo MySql. Pero cuando empiezas a cargarlo de verdad, con procesos de inserción masiva, del orden de millones de documentos, la cosa ya no va sola, y necesitas empezar a desarrollar de una forma determinada para conseguir que tus procesos sigan yendo a toda pastilla. Aquí te cuento algunos de los que he descubierto a base de leer en algunos casos, y de tortas en otros.

No gestionar las transacciones

Ni se te ocurra gestionar las transacciones en este tipo de procesos. Mongo tiene la virtud de poder “insertar y olvidar”, es decir, de enviar los datos a insertar al servidor, y no esperar ningún tipo de respuesta, ni de confirmación por su parte. Esto lo consigues especificando un WriteConcern.UNACKNOWLEDGED, que quiere decir eso, que no te interesa si la inserción ha ido bien o mal. Eso si, para utilizar esto, ya te tienes que haber preocupado previamente de asegurarte que tus inserciones no van a fallar, sino, mal lo tendrás para poder verificar lo que ha ido bien o mal.

Ejecuta un único save para cada documento, nunca update

El método save es mucho más rápido que el método update, ya que directamente escribe, sin verificar si previamente existe el registro o no. Nunca, repito, nunca desarrolles procesos de inserción masiva que quieras que vayan rápido y que no se degraden al crecer las colecciones en los que hagas uno o varios updates. Nuevamente, en este proceso, tendrás que preocuparte de tener a tu alcance toda la información que quieres guardar para cada documento, júntala durante N procesos e inserta una única vez.

Contra colecciones vacías

El tiempo de inserción en colecciones vacías en Mongo es mas bajo que cuando tiene datos. Plantéate en tu proceso si es posible al inicio del mismo borrar completamente la colección y volver a generarla de nuevo con los datos que vas a insertar. Aunque parezca mentira, en muchos casos puede ser más rápido hacer de nuevo una inserción completa que N parciales.

Contra colecciones sin índices

Si tus colecciones tienen índices, bórralos con dropIndex antes de empezar a insertar, ejecutas, y al finalizar vuelves a crear el indice con ensureIndex. El tiempo de creación del índice global es mucho menor que el tiempo que se tiene que invertir en actualizar el mismo índice ya creado en cada inserción de cada documento.

En colecciones más pequeñas

Trocea tus colecciones, tanto la inserción como la recuperación de datos van a ir más rápidas contra diez colecciones de 2 millones de documentos, que contra una única colección de 20 millones. Además, esto te permitirá paralelizar tus procesos y reducir la duración total.

Utiliza diferentes databases

Es incluso preferible utilizar diferentes databases para datos temporales o que vayas a regenerar por completo en ese ciclo, que diferentes colecciones en la misma base de datos. Esto te permitirá incluso hacer dropDatabase que liberará el espacio en disco que Mongo reserva para la db, cosa que no hace si eliminas la colección.

Y hasta aquí mis pequeños trucos para optimizar la inserción masiva en Mongo. Si tienes algún truco que no haya dicho, no dudes en comentármelo.