1. Busca
textual
com
Rais,
Solr
e
Sunspot
@mauriciojr
–
h-p://techbot.me/
2. Who?
• Maurício
Linhares
• @mauriciojr
• h-p://techbot.me/
• Developer
da
OfficeDrop.com
• Professor
na
Faculdade
iDez
• JUGleader
do
PBJUG
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. 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. 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. 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. 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. 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;
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. 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. 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. 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
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. 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/
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;
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. 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. 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. Outras
ferramentas
de
busca
textual
• Sphinx
-‐
h-p://sphinxsearch.com/
• ElasPcSearch
-‐
h-p://www.elasPcsearch.org/
• Ferret
-‐
h-ps://github.com/dbalmain/ferret