This short presentation at @herokujp on 20 Apr 2012 takes a look at the basics of full-text search. Why is a full-text search index so much faster than searching in SQL?
Bonus: we take a deeper look at the challenges of indexing and searching Japanese, and the new Kuromoji morphological analyzer in Lucene 3.6.0.
6. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
7. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
8. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
9. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
10. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
11. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
12. id title LIKE "hello"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
13. SQL LIKE
O(N) = SLOW ☹
SQL like is SLOW
- You are scanning
through your entire
database table and
checking each record.
14. hello Search
• hello, world!
however, you do get
results, and so that's as
• hello, 東京! far as some go.
but there is another
reason why SQL LIKE is a
bad idea…
15. hello world Search
Let's pretend we are the
customer or a user, and
start entering more
queries. This should
work, right?
16. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した here's the same table
again
6 成田国際空港から出発した
17. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した uh-oh…
That didn't work, because
SQL like is basically just
6 成田国際空港から出発した testing the exact
equality of bytes here.
That comma breaks our
query.
18. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した And it gets worse: we're
not done with our search
yet. We still have to
check the rest of the
table!
6 成田国際空港から出発した
Again, we're back to being
slow.
19. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
20. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
21. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
22. id title LIKE "hello world"; …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した
6 成田国際空港から出発した
23. hello world Search
No results found to match your query.
We just scanned through
our entire table of data
に一致する情報は見つかりませんでした。 without getting any
useful results, and the
user has no idea why.
And it gets worse.
24. hello 東京 Search
No results found to match your query.
It's easy to make up
queries here that seem
に一致する情報は見つかりませんでした。 like they should match
something, but they
don't.
25. 際空こんにちは Search
No results found to match your query.
Ultimately, if you want
flexible searches, you
に一致する情報は見つかりませんでした。 need to parse your
queries and combine the
results of multiple
searches.
But that is slow!
26. QUERY PARSING
Flexible queries combine the results of many separate
queries.
Required and optional terms
Flexible order of terms
Users expect flexible
queries, but making
queries flexible will make
But many slow searches is even slower! a slow search much
slower.
27. STEP ONE:
MAKE IT FAST
So let's revisit the slow
search problem and try
to make it faster
28. id title …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した This is our original data.
It's stored sensibly
enough for SQL, but it's
not really optimized for
searching.
6 成田国際空港から出発した Let's improve it.
29. id term
1 hello
1 world
2 hello
This is one step in a
better direction.
Separate each term, and
maintain its association
2 東京 with the original record
that it appears in.
30. id term
1 hello
2 hello
1 world This means we can do something
clever, like sort by term instead,
which will let us run a faster
binary search.
This hypothetical index looks a
bit like a normal database index.
2 東京
31. PROBLEM:
But we have a problem.
How do you decide what
makes a "term"?
This is easy in English,
WHAT IS A TERM?
where words are separated
by whitespace and
punctuation.
But languages like
Japanese don't use
whitespace, and have
relatively little
punctuation.
32. N-GRAM
One approach is to split
the text into "n-grams"
We can take the original
text and break it into
t wo-character "pairs"
33. シニアプロジェクトマネージャー
シニ ニア アプ プロ …
関西国際空港
関西 西国 国際 際空 空港
The results would look
something like this.
It's not a very good
technique, but it's better
than nothing.
34. N-GRAM
Generates too many “terms”
Terms don't preserve meaning
The problem:
Bad for index size and relevancy
1. it generates a lot of terms
2. many of these "terms" don't have
meaning
We can do better! 3. bad for index size and performance
4. bad for relevancy
we can do better!
35. MORPHOLOGICAL
ANALYSIS
Morphological analysis
uses a dictionary and
statistical modeling of
the language to identify
terms
36. KUROMOJI
NEW IN LUCENE 3.6.0
Happily, Lucene 3.6.0 was
released one week ago
with an EXCELLENT
Japanese morphological
analyzer package called
Kuromoji
37. シニアプロジェクトマネージャー
シニア プロジェクト マネージャ
関西国際空港
関西 国際 空港
We can see right away—if you read
Japanese—that we get much better
terms from this kind of analysis.
Since we are confident that we can
tokenize Japanese, let's continue
building our hypothetical index
38. term id
hello 1
hello 2
world 1
東京 2
東京 3
こんにちは 3 Our table now includes a
few of the tokenized
こんにちは 4 Japanese terms
シニア 4
プロジェクト 4
マネージャ 4
39. term id term id
hello 1 プロジェクト 4
hello 2 マネージャ 4
world 1 出発 5
から 5 出発 6
から 6 国際 5
こんにちは 3 国際 6
こんにちは 4 成田 6
し 5 東京 2
し 6 東京 3
When we finish
tokenizing all the text,
シニア 4 空港 5
we end up with a table
that looks something like
this.
た 5 空港 6
This is progress—but we
た 6 関西 5
can do better.
40. term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
マネージャ 4
出発 5, 6
国際 5, 6 This table here maintains
one entry per term,
成田 6 associated with a set of
IDs for the records that
東京 2, 3 are included.
空港 5, 6
関西 5
41. LUCENE
“INVERSE INDEX”
We have been building a
structure that is similar to the
"inverse index" built by Lucene.
Lucene is a library that
specializes in creating and
maintaining efficient data
structures for your index.
42. 空港 Search
Let's try a few more
searches against this
new data structure to
compare it to our earlier
slow searches
43. term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
マネージャ 4 Because we now have a
sorted list of each term,
出発 5, 6 we can perform a binary
search.
国際 5, 6
成田 6
東京 2, 3
空港 5, 6
関西 5
44. term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
マネージャ 4
出発 5, 6 We check the middle
国際 5, 6
成田 6
東京 2, 3
空港 5, 6
関西 5
45. term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
マネージャ 4 Check the middle again
出発 5, 6
国際 5, 6
成田 6
東京 2, 3
空港 5, 6
関西 5
46. term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
And check the middle
マネージャ 4 again.
出発 5, 6 Three operations to find
our matching records,
国際 5, 6 from a list of 15 terms!
成田 6
東京 2, 3
空港 5, 6
関西 5
47. id title …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した Now that we have the
matching IDs, it is a
simple matter for SQL to
fetch the matching rows
6 成田国際空港から出発した
48. 空港 Search
• 関西国際空港から出発した
And we have our search
results, in much less time
• 成田国際空港から出発した
49. FAST!
O(LOG N)
So the bottom line is
that an inverse index is
very fast!
We can take advantage
of this speed for better
queries
50. 空港のマネージャ Search
A good search engine
processes your query
into tokens, the same as
it does your data, and
runs a separate "query"
for each term
51. 空港 term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
マネージャ 4
出発 5, 6
国際 5, 6
Let's find documents
成田 6 matching our first term
東京 2, 3
空港 5, 6
関西 5
64. マネージャ term id
hello 1, 2
world 1
から 5, 6
こんにちは 3, 4
し 5, 6
シニア 4
た 5, 6
プロジェクト 4
マネージャ 4
出発 5, 6
国際 5, 6
We can combine the
成田 6 matched documents in
many different ways
東京 2, 3 using set theory
空港 5, 6
関西 5
65. id title …
1 hello, world!
2 hello, 東京!
3 東京こんにちは!
4 こんにちはシニアプロジェクトマネージャー
5 関西国際空港から出発した in this case, let's just
fetch all the documents
that match any of the
terms
6 成田国際空港から出発した
66. 空港のマネージャ Search
• こんにちはシニアプロジェクトマネージャー
• 関西国際空港から出発した and we have our results!
• 成田国際空港から出発した
67. REVIEW
Using an index is FASTER
Using an index is more FLEXIBLE
Lucene creates and manages efficient index structures
69. SOLR, ELASTICSEARCH
HTTP interface to Lucene.
Scale separately from your application.
Use with any language or framework.
Abstract away low-level Lucene implementation details.
70. Solr ElasticSearch
Created in 2004. Created in 2010.
Well-established and widely Growing quickly with early-
adopted. adopters.
Pre-RESTful API design. Modern RESTful JSON.
Minimal JSON/YAML
XML configuration files.
configuration.
Distribution & real-time
Distributed & real-time by design.
a work in progress
More features. More minimalist.
Many developers. Just one “benevolent dictator.”
71. GIVE IT A TRY!
https://devcenter.heroku.com/articles/websolr
https://devcenter.heroku.com/articles/bonsai