Lucene para ASP.NET (Parte II). Indexación.

04 jul. 2008
Tras hablar de buscadores avanzados para Webs e introducir Lucene para ASP.NET, a lo largo de este artículo hablaremos de cómo hacer la indexación con Lucene.

Distinguimos tres pasos básicos cuando hacemos la indexación:
  1. Abrir el índice.
  2. Crear y añadir el Document (del que ya hablamos en el anterior artículo).
  3. Optimizar el índice.

Abrir el índice

Lo primero un ejemplo de cómo se abre un índice:

IndexWriter writer = new IndexWriter(IndexPath, new SpanishAnalyzer(), !Directory.Exists(IndexPath)); 

El constructor de "IndexWriter" consta de tres parámetros:
  • IndexPath: donde indicaremos el Path del directorio que hemos creado como índice. Este directorio es muy importante. Evitaremos tocar nada de lo que veamos ahí dentro ;)
  • Analyzer: una de las características de Lucene es que tiene en cuenta las características del lenguaje a la hora de indexar (así como de buscar). Por ejemplo, el SpanishAnalyzer indica que hay palabras con poca importancia (como las preposiciones), que no tenga demasiado en cuenta los acentos, etc.
  • Crear índice: por tonto que parezca (a mí me lo parece) hay que indicarle si quieres crear o no el índice... ya podría hacer la comprobación solito, pero bueno, sobreviviremos indicando que si existe el directorio es que existe el índice.

Crear y añadir el Document
Sin duda la fase más importante. Ya sabemos que el Document, en una analogía con una base de datos relacional, sería algo así como la estructura de una tabla.

Debemos definir muy bien qué queremos indexar y cómo. Tenemos diferentes opciones de indexación para cada campo del Document:
  • Guardar el valor y no separar las palabras.
  • Guardar el valor, no separar las palabras y no indexar.
  • No guardar el valor y no separar las palabras.
  • Separar las palabras y no guarda.
Tener que indicar la opción de "Guardar" puede resultar difícil de entender para los que trabajamos con bases de datos relacionales. Hay que tener en cuenta que Lucene está pensada a lo grande, y todo lo que sea optimizar es poco.

Siempre que podamos debemos indexar y no guardar, sobretodo en lo referente a textos. Por ejemplo, si quisiéramos hacer un buscador para "El Quijote" nos interesaría indexarlo, pero no guardar todo su contenido, dado que no es lo que le vamos a mostrar en los resultados de buscador al usuario. Una técnica habitual cuando se quieren indexar textos largos es crear un campo de "Indexar y no guardar" para el texto completo, y otro de "Guardar y no indexar" para un resumen de ese texto largo.

El concepto de "Separar palabras" también es digno de mención. cuando lo indicamos (usando Field.Index.TOKENIZED) le estamos diciendo a Lucene que aplique los filtros del Analyzer. Sin embargo hay ocasiones en que queremos mantener la información tal cual, sin filtros, para lo que indicaremos Field.Index.UN_TOKENIZED.

Vayamos al grano y creemos el Document de ejemplo:


Document document = new Document();

// Guardar el valor y no separar las palabras
document.Add(new Field("Identificador", ID, Field.Store.YES, Field.Index.UN_TOKENIZED));
document.Add(new Field("Fecha", MyDate, Field.Store.YES, Field.Index.UN_TOKENIZED));

// Guardar el valor, no separar las palabras y no indexar
document.Add(new Field("ResumenDelTexto", Summary, Field.Store.YES, Field.Index.NO));

// Separar la palabras y guardar
document.Add(new Field("Title", Title, Field.Store.YES, Field.Index.TOKENIZED));

// Separar las palabras y no guardar
document.Add(new Field("Texto", Text, Field.Store.NO, Field.Index.TOKENIZED));

writer.AddDocument(document);

 


Vemos que el modo de trabajar es muy intuitivo. Como Document entendemos a "La estructura" y como Field entendemos "El campo" con sus características.

Llegados aquí, vuelvo a repetir que lo más importante a la hora de hacer un buscador es saber indexarlo bien. Conocidas las opciones posibles para crear un Field del Document, hay que pararse a pensar en qué y cómo queremos indexar.


Optimizar el índice
Lucene provee de la opción de optimizar el índice. Como siempre sucede en estos casos, el proceso de optimizar es muy pesado, pero tiene como consecuencia que las búsquedas serán más rápidas.

De modo que tenemos que encontrar un equilibrio entre el coste computacional de indexar y el coste computacional de buscar. Tendremos desde Webs que no indexen apenas nada y se pasen el día haciendo búsquedas, o la Web que indexa mucho pero que hace pocas búsquedas.

Y como en el centro está la virtud, para Todoexpertos he optado la siguiente opción "artesanal":


if ((DateTime.Now.Millisecond % 500) == 0) writer.Optimize();


Estructura final
Juntando un poquito las piezas, he aquí la estructura básica de la indexación con Lucene que utilizamos en Todoexpertos:

public static void Index(QuestionInfo questionInfo)
{
    IndexWriter writer = null;
    lock (LuceneIndexing)
    {
        try
        {
            // Abrimos el índice
            writer = new IndexWriter(IndexPath, new SpanishAnalyzer(), !Directory.Exists(IndexPath));

            // Accede a un método que crea el Document... aquí es donde hay que pensar :D
            Document document = GetDocument(questionInfo);

            writer.AddDocument(document);

            if ((DateTime.Now.Millisecond % 500) == 0)
                writer.Optimize();
        }
        finally
        {
            // Poniéndolo en el finally nos aseguramos que el writer queda cerrado. Muy importante: SIEMPRE debe quedar cerrado.
            if (writer != null)
                writer.Close();
        }
    }
}



En el próximo artículo veremos cómo podemos buscar con Lucene.

comments powered by Disqus
subir