Tag Archives: Solr

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.

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.

 

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.