Full text search com Sphinx

Artigo que apresenta o Sphinx, um banco de dados open-source para buscas com full text.

Introdução
logo do sphinx search

Full text é um recurso conhecido pelos entusiastas de bancos de dados. Ele permite a realização de queries de busca de registros em campos de texto com mais flexibilidade que uma busca envolvendo os operadores convencionais como o =, <>, LIKE, NOT LIKE, etc. Vários bancos de dados relacionais dão suporte a este recurso, por exemplo, o MySQL, PostgreSQL, Oracle, até o SQLite possui módulos que suportam Full Text.

Porém, também existem bancos de dados especialistas em buscas Full Text. Neste artigo, vou mostrar o Sphinx, que é um banco de dados Open Source, gratuito, muito prático e que tem evoluído rapidamente. Veremos as vantagens/desvantagens e quando é útil utilizar este tipo de banco.

O que Full Text é capaz?

Antes de mostrar os recursos do Sphinx, vamos ver rapidamente algumas limitações das buscas de texto convencionais.

Uma delas é quanto a buscas envolvendo várias palavras. Se você quer buscar "ação civil", pode achar que uma query contendo a condição campo_texto LIKE '%ação%civil%' seja a melhor opção do mundo. Mas já parou pra pensar que esta query pode retornar resultados contendo "anulação ... civilização"? Ou seja, a consulta não leva em conta as palavras exatas, mas as sequencias de texto. Outra limitação é que não virão resultados que contenham as palavras em ordem inversa, como "civil ação".

Com Full Text, o texto é tratado de forma mais inteligente para tentar devolver os resultados mais relevantes para determinada busca. É possível usar alguns operadores para exigir que determinadas palavras estejam no resultado, que determinadas palavras não estejam no resultado, que determinadas palavras estejam em uma ordem específica, etc.

Você pode achar que pode usar condições envolvendo expressões regulares, como o operador REGEXP do MySQL, mas aí também mora outro perigo. A performance do LIKE e de REGEXP normalmente não é muito boa pois os registros são comparados de forma textual, um por um.

Soluções envolvendo Full Text são especialmente otimizadas para devolver resultados de forma rápida. Ao invés do texto ser comparado em todo conteúdo, ele é buscado em um índice fulltext. Este índice é como um dicionário, contendo apenas palavras que apareceram no campo.

Portanto, Full Text é a solução ideal para campos de busca de propósito geral para grandes portais, cuja quantidade de texto é extremamente alta e onde o propósito principal é devolver os resultados mais relevantes com o menor tempo.


Introdução ao Sphinx

Já sabemos que o Sphinx é um banco de dados especializado em buscas Full Text e quais os benefícios deste tipo de solução. Porém, o Sphinx tem algumas características interessantes para se saber. Vou listar algumas características que acho interessantes:

  • Possibilidade de utilizá-lo para armazenamento de dados (texto, int, float, etc).
  • Possibilidade de armazenar os dados em campos JSON, com sub-campos (cada registro pode ter um conjunto de campos próprio).
  • Não é necessário declarar diferentes índices Full Text com combinações diferentes de campos de texto, basta declarar quais campos serão indexados pelo Full Text e, no momento da consulta, você especifica quais campos quer envolver na busca Full Text.
  • Sintaxe facilitada para realizar diferentes operações relacionadas aos campos Full Text.
  • Possibilidade de criação de índices estáticos ou dinâmicos. O índice estático é aquele que você consulta um banco relacional e preenche todo o índice Full Text no Sphinx de uma vez (periodicamente), de forma otimizada. Já o índice dinâmico permite que sejam realizadas inserções, remoções e atualizações em tempo real.
  • Possibilidade de criação de índices distribuídos, que propicia a escalabilidade, mantendo-se boa performance.
  • Facilidade para se conectar com o banco a partir de scripts PHP: pode-se usar PDO ou as funções do MySQL. A diferença é que você se conecta na porta em que subiu o serviço do Sphinx e precisa utilizar a sintaxe do Sphinx.
  • Possibilidade de acessar os índices através de uma API própria, o que torna a consulta mais "programável", ao invés de precisar montar queries e ter a preocupação com injections.

Para mais detalhes, segue o link do site oficial do Sphinx


Passo a passo para usar o Sphinx

Instalação do Sphinx

Primeiro é preciso instalar o Sphinx. Você pode optar por fazer uma instalação via repositório (por exemplo, no Fedora Linux, executar yum install sphinx) ou então baixar o fonte no site sphinxsearch.com, compilar e instalar.

Criação dos índices e configurações do Sphinx

O Sphinx, assim como vários outros bancos de dados, provê um serviço que responde a partir de uma porta do computador. Uma diferença importante para ressaltar é que o Sphinx não trabalha com "tabelas", mas sim com "índices". E estes índices não são criados através de queries SQL, mas sim a partir de um arquivo de configurações. Portanto, antes de subir o serviço, é preciso editar o arquivo de configurações (sphinx.conf) com os índices que você precisa. Vou deixar um exemplo de arquivo de configurações com índice do tipo RT (real-time), que é um tipo de índice que permite inserções, remoções e atualizações em tempo real:

# Indice de usuarios
index usuarios
{
    type = rt
    path = /var/lib/sphinx/data/usuarios

    # Campos de dados
    rt_attr_bigint = id_usuario
    rt_attr_string = nome
    rt_attr_string = login
    rt_attr_string = endereco

    # Campos fulltext
    rt_field = nome
    rt_field = endereco
}

# Configuracoes do servico
searchd
{
    listen              = 127.0.0.1:9306:mysql41
    log                 = /var/log/sphinx/searchd.log
    client_timeout      = 3600
    read_timeout        = 5
    max_children        = 0
    pid_file            = /var/run/sphinx/searchd.pid
    max_matches         = 1000
    preopen_indexes     = 1
    unlink_old          = 1
    attr_flush_period   = 3600
    mva_updates_pool    = 16M
    read_buffer         = 256K
    workers             = threads
    binlog_path         = /var/lib/sphinx/data/binlog
    ondisk_dict_default = 1
    dist_threads        = 3
    binlog_max_log_size = 16M
    rt_flush_period     = 3600
    listen_backlog      = 50
    thread_stack        = 64k
    watchdog            = 1
}

Esta configuração especifica: (1) o índice "usuarios", que contém 4 campos, sendo dois deles Full Text e (2) as configurações do serviço "searchd", que é o serviço responsável por responder pelas requisições ao banco do Sphinx. Note que a porta padrão para resposta é a 9306. Para que esta configuração funcione, é preciso que os diretórios especificados já estejam criados.

Iniciando/Parando o serviço do Sphinx

Agora que temos um arquivo de configurações pronto, podemos subir o serviço com o comando:

$ searchd

Este comando irá buscar o arquivo de configurações padrão. Se você o colocou em outro lugar ou se você quer instanciar vários serviços do sphinx em paralelo (cada um em uma porta diferente), então pode iniciar o serviço passando o caminho do arquivo de configurações pelo argumento --config, da seguinte forma:

$ searchd --config /etc/sphinx/sphinx.conf

Se você quiser parar o serviço por algum motivo, execute o mesmo comando que usou para iniciar o serviço, mas adicionando o argumento --stop, como neste exemplo:

$ searchd --config /etc/sphinx/sphinx.conf --stop

Atenção: não modifique o arquivo de configurações sem interromper o serviço, caso contrário, ele poderá não ser encerrado adequadamente. Se pretende fazer uma alteração, faça num arquivo a parte, depois pare o serviço do sphinx, substitua o arquivo de configurações, depois suba o serviço novamente.

Uma vez iniciado o serviço, você verá uma mensagem de sucesso como esta:

Sphinx 2.0.8-id64-release (r3831)
Copyright (c) 2001-2012, Andrew Aksyonoff
Copyright (c) 2008-2012, Sphinx Technologies Inc (http://sphinxsearch.com)

listening on 127.0.0.1:9306
precaching index 'usuarios'
precached 1 indexes in 0.000 sec

Se apareceu algum erro, resolva-o e tente subir o serviço novamente.

Assim que o serviço é iniciado pela primeira vez, ele verifica se os índices especificados no arquivo de configurações existem e, caso não existam ainda, eles são criados.

Acessando os índices do Sphinx manualmente

Com o serviço do Sphinx ativo, você pode acessá-lo com o mesmo cliente de acesso ao banco MySQL. A diferença é que você precisa passar o host/porta especificados no arquivo de configurações. Além disso, o Sphinx não possui controle de acessos por usuários, portanto, não precisa informá-los. Para acessar o banco que criamos anteriormente, pode-se utilizar o seguinte comando:

$ mysql -h 127.0.0.1 -P 9306

Observação: o host precisa ser passado da mesma forma como está no arquivo de configurações. Se tentar -h localhost, provavelmente irá falhar.

Ao abrir o client do MySQL, você irá notar que é informada a versão do Sphinx que você acessou e é mostrado um terminal de comandos SQL. Mas lembre-se: você não está em um banco de dados MySQL, só está utilizando o mesmo programa que permite o acesso ao banco MySQL, mas para acessar o banco Sphinx, que possui outra sintaxe SQL. Você pode consultar a documentação de todos comandos disponíveis no Sphinx procurando na documentação por "SphinxQL". Mas para ajudar, vou deixar uma lista com os principais comandos:

  • SHOW TABLES;
    Para listar os índices do seu banco.
  • DESC [índice];
    Para descrever os campos de um índice.
  • QUIT
    Para sair do programa cliente.

Os comandos de SELECT, INSERT, UPDATE e DELETE são relativamente parecidos com a sintaxe do MySQL, mas possuem algumas diferenças importantes. Portanto, antes de usá-los, recomendo que dê uma estudada em SphinxQL.

Acessando os índices do Sphinx pelo PHP

Para aqueles que utilizam PHP, vou deixar um exemplo de como se conectar no banco do Sphinx utilizando PDO.

$dsn     = 'mysql:host=127.0.0.1;port=9306;charset=utf8';
$usuario = null;
$senha   = null;

$pdo = new PDO($dsn, $usuario, $senha);

// Realizando um insert
$stmt = $pdo->prepare('INSERT INTO usuarios (id, id_usuario, nome, endereco, login) VALUES (:id, :id_usuario, :nome, :endereco, :login)');
$stmt->bindValue(':id', 1, PDO::PARAM_INT);
$stmt->bindValue(':id_usuario', 1, PDO::PARAM_INT);
$stmt->bindValue(':nome', 'Rubens Takiguti Ribeiro', PDO::PARAM_STR);
$stmt->bindValue(':endereco', 'Rua la la la', PDO::PARAM_STR);
$stmt->bindValue(':login', 'rubs', PDO::PARAM_STR);
$stmt->execute();

// Realizando uma busca envolvendo o campo nome
$stmt = $pdo->prepare('SELECT id_usuario, nome, WEIGHT() AS peso FROM usuarios WHERE MATCH(:match)');
$stmt->bindValue(':match', '@(nome)(takiguti)', PDO::PARAM_STR);
$stmt->execute();
while ($obj = $stmt->fetchObject()) {
    var_dump($obj);
}

Observações:
No Sphinx, todo índice possui um campo chamado id, que deve ser único por registro. Ele existe mesmo sem você especificá-lo no arquivo de configurações.
Durante a consulta, você também pode obter o campo WEIGHT(), que representa a relevância do campo de acordo com a sua busca.
As instruções SELECT sempre utilizam algum LIMIT. Se você não passa nenhum valor para LIMIT, é utilizado um valor padrão (20).

Conclusão

Bom, não quero deixar o artigo muito longo, por isso vou parando por aqui. Espero que este artigo ajude a dar uma introdução sobre o assunto e os passos iniciais para começar a usar o Sphinx, mas certamente há muito mais para discutir futuramente.

0 comentários