Busca textual com solr e sunspot no rails

2,475 views

Published on

Aprenda a fazer buscas textuais com mais eficiência usando o Solr integrado a sua aplicação Rails através do Sunspot.

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,475
On SlideShare
0
From Embeds
0
Number of Embeds
12
Actions
Shares
0
Downloads
16
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Busca textual com solr e sunspot no rails

  1. 1. Busca  textual  com  Rais,  Solr  e  Sunspot   @mauriciojr  –  h-p://techbot.me/  
  2. 2. Who?  •  Maurício  Linhares   •  @mauriciojr   •  h-p://techbot.me/   •  Developer  da  OfficeDrop.com   •  Professor  na  Faculdade  iDez   •  JUGleader  do  PBJUG  
  3. 3. “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;  
  4. 4. 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  |  +-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+      
  5. 5. 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;  
  6. 6. 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  
  7. 7. 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;  
  8. 8. 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;  
  9. 9. 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    
  10. 10. 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!
  11. 11. 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  
  12. 12. 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  
  13. 13. 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  
  14. 14. 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]  
  15. 15. 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.  
  16. 16. 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/  
  17. 17. 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>  
  18. 18. 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>  
  19. 19. 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;  
  20. 20. 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>  
  21. 21. 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;  
  22. 22. 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  
  23. 23. 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  )  )  
  24. 24. Outras  ferramentas  de  busca  textual  •  Sphinx  -­‐  h-p://sphinxsearch.com/  •  ElasPcSearch  -­‐  h-p://www.elasPcsearch.org/  •  Ferret  -­‐  h-ps://github.com/dbalmain/ferret    
  25. 25. DÚVIDAS?  

×