viernes, 13 de febrero de 2009

Evitar superar el maxClauseCount en Lucene

Uno de los errores típicos en Lucene es el siguiente:

org.apache.lucene.search.BooleanQuery$TooManyClauses: maxClauseCount is set to 1024

Esto normalmente viene provocado por hacer una consulta con carácteres comodines (* y ?). Cuando hacemos una consulta de este tipo, WildcardQuery, lo que hace internamente Lucene es sustituir nuestra consulta con comodines por una consulta sin ellos. ¿Cómo? Pues mira los campos que cumplan el patrón de la consulta y hace un OR con cada uno de ellos.


Mejor veamos un ejemplo:

Supongamos que tenemos indexado documentos y queremos encontrar todos aquellos cuyo campo título empiece por "cas". Para ello haremos la siguiente consulta:

titulo: cas*

Al tratarse de una WildcardQuery, Lucene miraría en todos los campos "titulo" del índice y tomaría aquellos que empiezan por "cas", por ejemplo supongamos que existen 3: "casa", "casita" y "caserio". Lucene sustituiría la consulta por:

titulo: casa or titulo: casita or titulo: caserio


En este ejemplo, vemos que una simple consulta que para mí tiene únicamente una única clausula, realmente se convierte en una consulta con 3 clausulas. Si en lugar de existir sólo 3 campos "titulo" que empiecen por "cas" existieran más de 1024, nos daría el error que he comentado al principio.

Conclusión: no usar comodines. Por desgracia, esto no es siempre posible, pero si se piensa un poco a la hora de indexar, sí que podemos minimizar los casos de uso de las WildcardQuerys.

Indexar una estructura de carpetas o en árbol

Es muy corriente el uso de Lucene para indexar estructuras en árbol como un sistema de ficheros en un gestor de contenidos. Cuando se tiene una estructura en árbol se usa mucho la consulta por ruta o path. Supongamos que tenemos una carpeta A, que dentro tiene otra, B y ésta a su vez tiene otra, C. A su vez cada carpeta tiene ficheros dentro. La ruta hacia C sería /A/B/C y la de cualquier fichero en C /A/B/C/nombre_fichero.

Es lógico indexar en cada Document de Lucene un campo llamado path que contenga la ruta. De esta forma, podemos buscar un contenido por ruta:

path: /A/B/C/nombre_fichero

o buscar los hijos de C (todos los que su ruta empiece por la de C)

path: /A/B/C/*

Dependiendo de los hijos que tenga C, podremos superar el maxClauseCount. Y lo más seguro es que tarde o temprano lo hagamos.

Para evitar este problema, lo que podemos hacer es:
  1. Seguimos indexando el campo path para hacer consultas por ruta pero sin *.
  2. Indexamos la ruta de la carpeta padre, por ejemplo en el campo parent_path. Esto nos servirá para consultar los hijos de una carpeta.
  3. Indexamos como campo múltiple la ruta de todas las carpetas padres. Por ejemplo, llamemosle ancestor_path. Esto nos sirve para buscar los hijos, nietos, bisnietos,.... de una carpeta.

Dicho esto, para el fichero /A/B/C/nombre_fichero, indexaríamos lo siguiente:
  • path: /A/B/C/nombre_fichero
  • parent_path: /A/B/C
  • ancestor_path: /A
  • ancestor_path: /A/B
  • ancestor_path: /A/B/C
Tened en cuenta que ancestor_path es un campo múltiple.

Si queremos obtener un contenido a partir de su ruta, la consulta seguiría siendo la misma:

path: /A/B/C/nombre_fichero

pero si queremos obtener los hijos de C ya no tenemos que usar comodines:

parent_path: /A/B/C

y si quisieramos obtener todos los hijos de A, incluido nietos tampoco:

ancestor_path: /A

De esta forma, estamos evitando el error del maxClauseCount en este tipo de consultas, que por otro lado suelen ser muy habituales si se trabaja sobre una estructura en árbol.

Si vuestro caso es otro, quizás pensando un poco podáis encontrar una solución alternativa a los comodines. Y si no, no tendréis más remedio que aumentar el maxClauseCount, pero como se suele decir, "eso es pan para hoy, pero hambre para mañana".

Por último, decir que en Lucene no van bien las consultas con "/", por lo que tendréis que transformar el path antes de indexar y antes de consultar, de forma que sustituyáis las "/" por otro/s caráctere/s. Podéis usar las solución que hace Alfresco, que consiste en transformar las "/" por "_x" o algo así. Es una ISO, pero no recuerdo el número. En cuanto lo encuentre lo actualizaré.

Espero que os sea de utilidad.

13 comentarios:

Emilio Escobar dijo...

ISO 9075-14:2003, y es de jackrabbit
http://jackrabbit.apache.org/api/1.4/org/apache/jackrabbit/util/ISO9075.html

JESUS JOAQUIN dijo...

kiyooooooooo, no entiendo un c............. de tu pagina pero supongo que para quien este enfoacada esta de lujo, porque todo lo que haces esta bien, asi que esto no será menos, jeje. Bueno solo era saludarte aunque sea por esta via. Un abrazo.

Xela dijo...

jaja. Qué tal Jesulete? Me ha hecho mucha ilusión tu comentario. Muchas gracias por tus ánimos. La idea es que a alguien le sirva, pero no es tu caso.

Lo único que tengo que te pueda interesar, es decir, cosas que no son de mi curro, es el post de Rumore, rumore, donde hablo de una página sobre rumores y leyendas urbanas. Hay cosas bastante curiosas.

Mi idea es ir mezclando post profesionales con post de entretenimiento y ocio . No todo va a ser trabajar. Pero de momento del último grupo sólo hay uno frente a unos 20 del primero.

Pronto subiré algunos vídeos míos tocando la guitarra. No es que sea Jimi Hendrix, pero puede interesar, sobretodo a los que están empezando como yo. ;-)

Nos vemos.

Domingo dijo...

Hola!! Buen post. Pozi es una forma de evitar ese error que alguna vez nos ha dado algun dolor de cabeza :P. Pero tambie, se deben tener en cuenta los contras. En este caso supongo que será el tamaño del indice generado, sobre todo por el campo multiple. Como todo en informatica la información es bienvenida pero habra que saber utilizarla segun el caso, es decir, si nos conviene usar mas memoria o tener indices de mayor tamaño. De todas formas, por lo menos, el campo simple, "antecesor" es una buena opcion, ya que muchas veces se realiza busquedas de hijos directos. resumiendo, muy buen artículo, como todos los anteriores. Un saludo!!

Xela dijo...

Muchas gracias, Domingo. Me alegra escucharte, aunque sea por aquí. A ver si un día cuando bajes nos haces una visita. ;-)

Como bien dices todo es cuestión de valorar los pros y los contras. De todas formas, no creo que el campo múltiple sea una gran carga para el índice. Más que tener en cuenta lo que indexar y lo que no, la pregunta que debemos hacernos es si queremos almacenar en el índice los campos o no (stored o no stored, esa es la cuestión). El poner los campos como no stored sí que ahorra bastante espacio en el índice. El contra es que del índice no podrías recuperar esos campos. Tendrías que ir a base de datos a por la información.

Sin embargo, esa es la solución que mejor nos está funcionando. Es decir, sólo almacenamos el campo id y el resto de cosas van como no stored. Al hacer las búsquedas lo único que recuperas de los documents son sus ids. Tienes que ir a la base de datos y leer el resto a partir del id. Sin embargo, como el id suele ser clave primaria, esto último no es muy costoso y te has ahorrado la búsqueda gorda de base de datos.

Espero que te vaya bien por Los Madriles. Por cierto, ya mismo comienza la temporada de Formula 1. ¡¡¡Pit-lane va a echar humo!!!

Domingo dijo...

Oye!! Pues es buena idea eso de usar el índice solo para las búsquedas!! Me lo apunto, jeje. Si la temporada está cerca, estoy haciendo algunos cambios, ya te contare. Un saludo!!

Anónimo dijo...

Si el título fuera "La casita de chocolate" ¿Como debríamos hacer la query para que por ejemplo buscando "casita" y "chocolate" nos devolviera este titulo?

Hice pruebas con casita+chocolate, casita||chocolate, casita&&chocolate y "casita chocolate" pero no sale con eso.

Muy interesante el post y muy buena la página de la F1 que ya es inminente su llegada.

Saludos cordiales

Xela dijo...

Como estás buscando por dos palabras no consecutivas, la consulta quedaría:
titulo:casita and titulo:chocolate

Saludos
;-)

Todo Ruby dijo...

El blog esta bueno, me han interesado los temas.

Buena suerte

Aprende Ruby

Xela dijo...

Muchas gracias, Todo Ruby. Tu blog también parece muy interesante. ;-)

Dejo el enlace por si alguien está interesado en Ruby.

Anónimo dijo...

"Conclusión: no usar comodines"
Si todo lo solucionas así...

Xela dijo...

Anónimo, no sé si es que no te has leído el post al completo o que no lo has entendido muy bien. En este post estoy dando algunas alternativas al uso de comodines en determinados casos. Lo que recomiendo es usar alternativas mientras sea posible. Esta claro que no siempre lo será y por tanto no quedará más remedio que usarlos.

polle dijo...

Buen dia a todos,
Soy nuevo en este trabajo con Lucene y me pregunto si existe alguna manera de realizar consultas donde intervengan varias frases y donde no se tenga la necesidad de colocar "".
Pregunto esto porque seria bueno que los usuarios finales no se enredaran con los operadores,ya que ellos no tienen por que conocer la existencia de estos operadores