Upload
mauricio-linhares
View
2.163
Download
0
Embed Size (px)
DESCRIPTION
Aprenda a fazer buscas textuais com mais eficiência usando o Solr integrado a sua aplicação Rails através do Sunspot.
Citation preview
Busca textual com Rais, Solr e Sunspot
@mauriciojr – h-p://techbot.me/
Who? • Maurício Linhares • @mauriciojr • h-p://techbot.me/ • Developer da OfficeDrop.com • Professor na Faculdade iDez • JUGleader do PBJUG
“LIKE” considered evil • Consultas que usam LIKE só são eficientes se a coluna esPver indexada e for uma busca de prefixo: • “josé%” • “maria%”
• Alguns bancos tem um limite de caracteres que podem ser indexado em campos textuais;
• Bancos de dados relacionais normalmente não são capazes de fazer análise para tornar os dados buscáveis mais fáceis de serem encontrados;
EXPLAINing mysql> select * from products where name like "%galacPca%"; +-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+ | id | name | price | descripPon | category_id | +-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+ | 2 | Ba-lestar GalacPca: The Complete Series | 39.90 | All four seasons in a single pack | 2 | | 3 | Ba-lestar GalacPca: The Boardgame | 59.90 | A game of strife, space fights and intrige | 3 | +-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+ 2 rows in set (0.00 sec)
mysql> explain select * from products where name like "%galacPca%"; +-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+ | 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 12 | Using where | +-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐+-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐+
Entram as ferramentas de busca textual • Bancos de dados não servem, surgem as ferramentas de busca puramente textual;
• Lucene, escrita em Java, torna-‐se a ferramenta open source mais comum pra solucionar esse Ppo de problema;
• Surge o Solr, um servidor web com interface semi-‐REST para que outras linguagens possam também usar o Lucene pra busca textual;
Diferenças? • Stemming – redução das palavras para o seu radical: • Cat – catlike, ca-y, catwoman, caright
• Remoção de palavras comuns: • e, ou, de, aqui, ali, se, a, o,
• Subdivisão de palavras: • PowerShot DX3100 – power, shot, dx, 3100
• Sinônimos • Casa – lar, apartamento, domicílio, residência
Lucene, Solr e Rails • Vários plugins disponíveis, mas só o Sunspot (h-p://outowime.github.com/sunspot/ ) é realmente manPdo;
• Existe um port do Lucene para Ruby, o Ferret, mas está sem desenvolvimento já a muito tempo e é instável quando várias aplicações usam o mesmo índice;
• É possível usar Lucene diretamente se você esPver usando JRuby;
sunspot e sunspot_rails • Gems para integrar as buscas com Solr na sua aplicação, contém uma instalação do Solr como servidor web pronto pra ser uPlizado;
• Integram-‐se em objetos AcPveRecord, mas também é possível usar modelos não AcPveRecord;
• Projeto em movimento constante e já com vários plugins pra se integrar com outros bancos de dados, como MongoDB;
Setup – conIig/sunspot.yml development: solr: hostname: localhost port: 8980 log_level: DEBUG auto_commit_awer_request: true test: solr: hostname: localhost port: 8981 log_level: OFF producPon: solr: hostname: localhost port: 8982 log_level: WARNING auto_commit_awer_request: true
Setup – Parte 2 • Pegue o código fonte do Sunspot no GitHub -‐ h-ps://github.com/outowime/sunspot
• Copie a pasta “sunspot/solr-‐1.3/solr” pra dentro do seu projeto Rails
• Adicione o Sunspot no seu Gemfile:
!gem 'sunspot', '1.2.1'!!gem 'sunspot_rails', '1.2.1'!
Integrando o sunspot em um model class Product < AcPveRecord::Base belongs_to :category validates_presence_of :name, :descripPon, :category_id, :price validates_uniqueness_of :name, :allow_blank => true searchable :auto_index => true, :auto_remove => true do text :name, :boost => 2.0 text :descripPon float :price integer :category_id, :references => ::Category end def to_s self.name end end
E no controller class ProductsController < ApplicaPonController def index @products = if params[:q].blank? Product.all :order => 'name ASC' else Product.solr_search do |s| s.keywords params[:q] end end end end
Mas antes de continuar, um pequeno monkey-‐patch ::Sunspot::Search::StandardSearch.class_eval do include Enumerable delegate( :current_page, :per_page, :total_entries, :total_pages, :offset, :previous_page, :next_page, :out_of_bounds?, :each, :in_groups_of, :blank?, :[], :to => :results) end
Na sua view -‐ 1 %h1 Products %p= link_to 'New product', new_product_path %h2 Search products -‐ form_tag products_path, :method => :get do |t| %p = text_field_tag :q, params[:q] = submit_tag 'Go!' = hidden_field_tag :category_id, params[:category_id]
Na sua view -‐ 2 = will_paginate @products %table %thead %tr %th Name %th Category %th Price %tbody -‐ for product in @products %tr %td= product %td= product.category %td= product.price %td= link_to 'Edit', edit_product_path( product ) = will_paginate @products -‐ else %p There are no products available.
Análise de dados
• Inicie o Solr no seu projeto: • rake sunspot:solr:run
• Faça a indexação de alguns dos seus dados: • Product.solr_reindex
• Abra a administração do Solr: • h-p://localhost:8980/solr/admin/
Snippet -‐ solr/conf/schema.xml <fieldtype class="solr.TextField" posiPonIncrementGap="100" name="text"> <analyzer> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.StandardFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldtype>
Adicionando mais Iiltros <fieldtype class="solr.TextField" posiPonIncrementGap="100" name="text"> <analyzer> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.StandardFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> <filter class="solr.ISOLaPn1AccentFilterFactory"/> <filter class="solr.TrimFilterFactory" /> </analyzer> </fieldtype>
Buscas com match parcial • O Lucene normalmente só retorna um match em uma palavra se ela for um match total em um token, ele não faz matches parciais diretamente;
• Há um operador pra permiPr o match parcial de palavras, “*”, mas esse operador só é indicado para buscas simples em índices pequenos;
• Se você tem um índice grande e precisa de performance nas suas buscas, precisa usar um filtro que gere pedaços da palavra como tokens para serem buscados;
Adicionando o ngram Iilter <fieldtype class="solr.TextField" posiPonIncrementGap="100" name="text"> <analyzer type="index"> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.StandardFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> <filter class="solr.ISOLaPn1AccentFilterFactory"/> <filter class="solr.TrimFilterFactory" /> <filter class="solr.EdgeNGramFilterFactory" minGramSize="3" maxGramSize="30"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.StandardFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> <filter class="solr.ISOLaPn1AccentFilterFactory"/> <filter class="solr.TrimFilterFactory" /> </analyzer> </fieldtype>
Facets • Facets são uma forma de agrupar os resultados com base em um dos campos do seu objeto indexado;
• Você poderia retornar os produtos do resultado da busca e mostrar para o usuário quantos produtos em cada categoria foram retornados, assim o usuário poderia filtrar também por categoria;
Adicionando facets na busca result = Product.solr_search do |s| s.keywords params[:q] unless params[:category_id].blank? s.with( :category_id ).equal_to( params[:category_id].to_i ) else s.facet :category_id end s.paginate :per_page => 3, :page => @page end if result.facet( :category_id ) @facet_rows = result.facet(:category_id).rows end
Na sua view -‐ unless @facet_rows.blank? %h3 Filters -‐ %ul -‐ @facet_rows.each do |facet| %li= link_to( "#{facet.instance} (#{facet.count})", products_path( :q => params[:q], :category_id => facet.instance ) )
Outras ferramentas de busca textual • Sphinx -‐ h-p://sphinxsearch.com/
• ElasPcSearch -‐ h-p://www.elasPcsearch.org/
• Ferret -‐ h-ps://github.com/dbalmain/ferret
DÚVIDAS?