Optimizando búsquedas con Apache Solr

19/04/2011

Tengo que confesar dos cosas: la primera es que no soy ningun experto en Solr como para escribir sobre él. La segunda, es que la primera vez que ohí hablar de Solr, pensé que era algo reservado para alguien que pretendiese crear el próximo Google, o como mínimo, reservado para portales con un montón de contenido hetereogéneo a indexar.

Pero la verdad es que Solr es un producto útil para cualquier mortal a la hora de realizar búsquedas de contenido, y es que, admitámoslo, las búsquedas que podamos hacer en SQL son muy limitadas: a parte del operador LIKE, poca cosa más queda. Oh sí, en MySQL tenemos el full text search, pero el problema de éste es que sólo funciona con tablas MyISAM, pero MyISAM no soporta integridad referencial, lo cual significa que usar MyISAM es como conducir sin llevar el cinturón de seguridad: no debería pasar nada, pero sabes que si algún día pasa algo, lo lamentarás.

Así pues, qué es Solr y cómo puede ayudarte? Solr es un motor de búsqueda escrito en Java (de hecho, el corazón de Solr es Lucene) que mantiene un índice (el término exacto seria un inverted index) de palabras respecto a los documentos que coinciden con la búsqueda de esa palabra. Solr se ejecuta como un proceso autónomo, ya sea bajo Jetty, Tomcat, u otro servidor de aplicaciones, y funciona bajo HTTP, lo cual lo hace independiente del lenguaje desde el que accedas, entre otras cosas. La funcionalidad que trae es impresionante, aunque para este post nos centraremos en lo más esencial: mejorar las búsquedas que podamos hacer mediante SQL.

Para hacer una instalación básica, deberemos desplegar el war que se puede encontrar en la web de Solr. Hay una serie de ficheros que forman su configuración (solr/conf). Al iniciar Solr, hay que indicarle dónde encontrar esos ficheros. Una manera es mediante catalina.sh:

JAVA_OPTS="-Dsolr.solr.home=/path/solr"

De todos los ficheros de configuración, cabe destacar solrconfig.xml y schema.xml. Solrconfig.xml és la configuración general del servidor, donde caben parámetros de gestión de la caché, memoria, buffers, etc..
El segundo, schema.xml, es quizá, el más importante: define la estructura de los documentos a gestionar: los campos que componen a un documento, y el formato o tipo de cada uno.

Quizá estarás pensando.. ¿Y cómo enlazo los documentos que Solr guarda con mi base de datos relacional? Para ello, hay módulos que permiten importar/volcar de una base de datos. Se pueden hacer imports completos o incrementales, como si fueran backups, vamos. Para activar este módulo, hay que añadir un handler en solrconfig.xml:

   <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
        <str name="config">db-data-config.xml</str>
    </lst>
  </requestHandler>

DataImportHandler gestionará las peticiones de la url /dataimport, usando la configuración definida en solr/config/db-data-config.xml
Ahora solo nos falta definir el esquema (schema.xml), y configurar el proceso de importación (db-data-config.xml).

Veamos un ejemplo de db-data-config.xml:

<dataConfig>
  <dataSource type="JdbcDataSource"
              driver="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost/ejemplo"
              user="usuario1"
              password="pass"/>
  <document>
    <entity name="usuario"
            query="select nombre, telefono, direccion from usuario">
        <entity name="grupos"
		query="SELECT nombre as grupo FROM grupo WHERE id={usuario.id}">
        </entity>
    </entity>
  </document>
</dataConfig>

Como se puede ver, el documento puede tener varias entidades, y de cada una permite, entre otras cosas, definir relaciones many-to-one. No voy a entrar en más detalle con DataImport, ya que la documentación que hay en la web de Solr es el mejor recurso disponible.
Ahora que Solr ya sabe cómo importar la información de nuestra base de datos, deberemos indicarle qué formato tiene esa información y cómo tratar con ella, y para ello tenemos schema.xml. La idea general de este fichero de configuración es, por una parte, definir tipos de datos (types), y de cada tipo, indicarle cómo tratarlo, y por otro definir qué campos hay en un documento (fields).

Veamos un ejemplo primero de definición del documento:

<fields>
   <field name="id" type="string" indexed="true" stored="true" required="true" />
   <field name="nombre" type="text" indexed="true" stored="true"/>
   <field name="grupo" type="text" indexed="true" stored="true"/>
   <field name="all" type="text" indexed="true" stored="true" multiValued="true" termVectors="true" />
</fields>
  <uniqueKey>id</uniqueKey>
  <defaultSearchField>all</defaultSearchField>
  <copyField source="name" dest="all"/>
  <copyField source="description" dest="all"/>

Veamos un trozo de schema.xml donde se define el tipo “text”:

    <fieldType name="text" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
        <filter class="solr.ASCIIFoldingFilterFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
        <filter class="solr.ASCIIFoldingFilterFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
      </analyzer>
    </fieldType>
 
 
 Como se puede ver, para cada tipo de datos, podemos definir cómo tratarlo, mediante una serie de filtros que se aplican para cada valor. Hay muchos filtros disponibles, y también se pueden crear de nuevos.
 Una vez configurado, podemos invocar un proceso de importación total:
 

http://localhost:8080/solr/dataimport?command=full-import

Es conveniente comprobar los logs de tomcat para saber como va el proceso. Una vez importados los documentos, ya podemos hacer consultas. Si no se quiere usar HTTP directamente, hay bindings para los lenguajes más populares. Solr también trae un frontend que permite realizar consultas mediante un formulario web, localizado en http://localhost:8080/solr/admin. Hay que tener en cuenta también que Solr usa UTF8 por defecto, y las consultas se hacen por GET, de manera que hay que activar las URLs en utf-8 bajo tomcat:

     <Connector port="8080" address="127.0.0.1" protocol="HTTP/1.1"  URIEncoding="UTF-8"
               connectionTimeout="20000"
               redirectPort="8443" />

En este ejemplo, también se ha especificado address=”127.0.0.1″ para que tomcat sólo atienda a peticiones provenientes de localhost, así evitamos que cualquiera se pueda conectar a solr desde internet
.
En definitiva, Solr es una herramienta muy potente a considerar cuando necesitemos hacer búsquedas flexibles y rápidas de información, independientemente del formato en el que se encuentre en orígen.