sistemas de busca para texto (full text search)

Há algum tempo criei e venho mantendo uma aplicação web que faz busca em um grande volume de informações. Hoje, a base de dados possui cerca de 120 milhões de registros, e preciso importar mais 90 milhões.

No início, procurei me informar sobre sistemas de busca, e descobri que há diversas alternativas de ótima qualidade. O problema da minha base é que os dados não são relacionados a data, os registros não são relacionados a um período (estilo log). Porém, são imutáveis, nunca sofrem update – só select.

Comecei pelo full text search do MySQL 5.1, que era o banco onde os dados estavam armazenados – em uma única tabela, de uns 40GB. Os resultados não foram muito bons – no próprio manual há comentários de outros usuários relatando fatos de lentidão com grandes volumes.

Pelos testes que fiz, o mysql se comportou muito bem com pequeno volume de dados, entre 1 e 2 milhões de registros. A máquina é um quad-core 3.2 com 4GB de RAM e dois discos SATA de 160GB em Raid1.

Procurando mais, encontrei o Lucene, da Jakarta. Como o backend do site é inteiro escrito em python, optei por não usar o lucene em java e acabei encontrando o PyLucene – binding do lucene pra python, usando jcc. Na época, havia ainda uma versão que podia ser compilada com gcj, que era o padrão.

Depois de uma batalha pra comparar os dois, e escolher a melhor opção, acabei ficando com a versão do jcc – ainda, publiquei um artigo no Ubuntu Forums sobre a instalação e uso dos dois, pouco antes de entrar na lista de discussão e me tornar ativo no pylucene.

Consegui diversos benefícios usando o lucene:

  • Armazenar ou apenas indexar documentos (linhas), compostos por campos (estilo csv)
  • Realizar a busca em um ou mais campos, considerando relevância
  • Linguagem de busca com muitos recursos específicos pra texto

Em determinado período, eu excluí completamente o mysql do sistema. Coloquei todos os dados em um index do lucene e quando executava uma busca, ele já me fornecia os campos dos documentos retornados. Com cerca de 80 buscas simultâneas, na base com uns 80 milhões de registros, os resultados surgiam em menos de 0.5 segundos.

Quando dobrei a quantidade de clientes usando o sistema, começaram os problemas de memória do java.lang.OutOfMemoryError: Java heap space. Aí, descobri um problema no design do sistema.

Pra cada nova busca eu abria o index, executava a query, pegava os documentos do resultado e fechava o index – o index do lucene é um diretório no disco, e as funções da API gerenciam o conteúdo dele. Encontrei em algum documento do manual que isso não era apropriado, e poderia causar lentidão – mas meu problema não era lentidão, e sim falta de memória. Se eu aumentasse o heap de 512 pra 1024, a máquina engolia o swap com essa quantidade de clientes simulâneos.

Então, decidi mudar o design e acabei criando um daemon que sozinho abria o index, com um heap de 4096, atendendo requisições por um unix socket. Resolveu parcialmente, pois aquelas buscas de 0.5 passaram pra 0.8, 0.9 segundos. As vezes, chegava a 1.5 segundos. Depois de uma semana, voltavam os problemas de memória do java, e não havia mais como aumentar o heap.

Outro problema do lucene, nesse caso, era gerenciar o conteúdo da base: ele não possui nenhum mecanismo de update. Há apenas insert, search e delete. Pra atualizar um documento é necessário removê-lo e depois criá-lo novamente, e após criar novos documentos é recomendado otimizar a base toda – processo que demorava mais de 8 horas.

Numa tentativa de melhorar o desempenho, fiz o seguinte: voltei os dados pro mysql, extraí apenas os campos texto que precisavam de busca, e criei o index do lucene sem armazenar os dados. Em cada documento, havia um único campo que era armazenado, que era o ID daquele registro no mysql. Todos os outros eram apenas tokenized.

Com isso, o sistema melhorou muito. As buscas pelo daemon voltaram a ser extremamente rápidas, mas depois de cada uma ainda era necessário fazer um select no mysql pra extrair os dados, pois a busca no lucene retornava apenas um monte de IDs dos registros do mysql.

Então, voltei a procurar outras alternativas. O Gleicon já fez um ou mais sistemas com o Ferret, mas sou alérgico a Ruby e por isso já descartei. Pouco depois encontrei o Sphinx, que parece muito interessante pelo fato de já extrair e indexar automaticamente os dados de RDBMs como MySQL e PostgreSQL. Ele possui um daemon próprio e permite queries via socket, mas depois que vi um dos clientes em python, achei que poderia ser lento só pelo processo de conexão e manuseio do protocolo, e decidi nem tentar.

Por fim, o que estava evidente e nunca cheguei nem a cogitar, foi o que mais me impressionou. Durante muitos anos fui à favor do PostgreSQL. Vale lembrar que o MySQL até as versões 4.x era pelado. Mas quando saiu a versão 5, fui migrando gradativamente e passei a usar MySQL em tudo.

O full text search do PostgreSQL 8.3 é beeeeeem interessante. Como sempre, o PG é muito mais lento pra criar os índices e importar os dados, mas no meu caso, se comportou muito melhor que todas outras alternativas com grande volume de dados e diversas conexões simultâneas. Um pouco disso é empolgação, pois essa versão do meu sistema ainda não está em produção – mas pelos testes que fiz, já houve resultado muito satisfatório.

Uma das coisas necessárias foi particionar a tabela, pra gerar índices menores e mais rápidos. Porém, ele tem um esquema que permite gerar o vetor da busca e armazenar em um campo da tabela junto com os dados, e então gerar o índice à partir daquele campo. A configuração inicial de tudo isso tem quase a mesma complexidade de escrever os programas que gerenciam o index do lucene, mas por se tratar de SQL acaba sendo mais prático pra dar manutenção, importar e principalmente atualizar os dados quando necessário.

Usando esse esquema, ele permite um fast full text search, que apresenta um desempenho tão bom quanto do lucene, sem que haja o maldito problema de falta de memória do java.

Se um dia tiver saco de fazer um benchmark de tudo isso, publico aqui pra todos verem.


11 Comentários on “sistemas de busca para texto (full text search)”

  1. gbel disse:

    Caraco estava investigando umas paradas dessa hoje pra um projeto que to fazendo na IBM.

    Muito bom seu artigo Alê.

    Pra quem se interessar tem um “tutorial”:

    http://blog.decapole.com.br/?p=30

    Eu tinha me aventurado no Lucene um bom tempo atras em um projeto da faculdade mas não foi pra frente.

    Agora vou usar o pylucene pra parsear logs acho que vai ficar legal.

    Abraço

  2. Tkm disse:

    Pô, mais um post muito bom cara! Já encaminhei pros amigos e inclusive comentei que é difícil achar este tipo de artigo, direto e baseado em fatos e dados. Mais do que isso, um aritgo imparcial, sem querer vender a tecnologia XYZ nem outros interesses .

    parabens de novo.

    []s!

    Tkm

  3. alef disse:

    Boa! Valeu Bruno! ;)

  4. […] memcache em tudo! 7 06 2008 Não sei como passei tanto tempo sem usar memcache. Numa breve conversa com o Gleicon, ele mencionou isso e em 15 minutos implementei no meu sistema de busca. […]

  5. Fabiano disse:

    Olá amigo,
    Achei interessante seu post sobre full text search e grande volumes de dados. Estou fazendou um buscador web para páginas em domínio .com.br, e agora pretendo utilizar os links para medir a importância de uma página (utilizo php/mysql). Se seu buscador também for web e quiser trocar experiências/problemas já tem meu email para entrar em contato.
    Grato,
    Fabiano
    http://www.radarbit.com

    • Marcos disse:

      Olá, estou no último ano de facul, curso ciência da computação, e tenho que fazer um mecanismo de busca na internet como projeto final. Gostaria de saber se podem me dar uma força. Grato.

  6. Alexandre,

    Gostei muito do seu post, estou com um problema em um cliente, temos uma base mysql e estamos usando o mysql com a engine Myisam para poder fazer a busca fulltext. Porém preciso importar 5.000.000 de registros e no meio da importação a base naum aguentou e tive que voltar atrás.

    Pesquisei bastante sobre o lucene para utilizar com php, porém resolvi partir para uma muança no banco de dados.

    Instalei o postgre no servidor e agora quero saber mais informações sobre o fast full text search. Fiquei curioso para saber como você resolveu o seu problema.

    Grande abraço e parabéns pelo blog.

  7. alef disse:

    opa, valeu ai galera.
    coloquei um exemplo completo em outro artigo: https://fiorix.wordpress.com/2009/03/12/postgresql-fast-full-text-search/

    espero que ajude!

  8. Lz disse:

    Alexandre, esse artigo é muito interessante porque relata resultado de experimentação de ferramentas e gerenciadores de base de dados numa situação muito específica, mas o legal é exatamente isso, você vai fundo no que cada uma das ferramentas é bom nessa especificidade, é ótimo saber do “power” delas.

    Fiquei tranquilo porque se o MySQL gerencia bem rápido até 2 milhões de registros, vai dar conta muito bem de alguns ambientes que implantei e do que planejo fazer.

    Então escrevo para te agradecer e perguntar algo que me deixou curioso, você diz que é alérgico à Ruby. Tem algum motivo específico para isso ou tem a ver somente com projetos para essa sua necessidade relatada? Tenho ouvido falar muito bem do RoR e pensei em experimentá-lo.

    Obrigado mais uma vez.

    Cordialmente

    Lz

    • alef disse:

      po, não sei nem como explicar essa coisa do ruby sem parecer arrogante
      prefiro apenas reforçar que, pra mim, hoje, python é mais adequado para os projetos em que tenho trabalhado
      sobre o RoR, é bem prático e normalmente supre as necessidades
      valeu pelo elogio ao artigo

  9. Barba Negra disse:

    Olá Amigo, como anda isso hoje? Vi que o post é antigo, mas continua no PG? Gostaria muito de saber poi tenho um buscador com MySQL e gostaria de estudar outras possibilidades.
    http://www.zeen.com.br


Deixar mensagem para memcache em tudo! « coisas lunix Cancelar resposta