Clone this repo:
  1. 10bb3d1 Minor fixes by Mike Miller · 4 months ago master
  2. 134b78d Upgrade to lucene 7.1.0 by Mike Miller · 4 months ago
  3. c06e9d3 Remove lucene-core dep by Mike Miller · 4 months ago
  4. db5eb75 Update lucene by Christopher Tubbs · 4 months ago
  5. 7667f22 Build against Accumulo 2.0.0-alpha-2 by Christopher Tubbs · 4 months ago

Apache Accumulo Wikisearch

Wikisearch is an example Accumulo application that provides a flexible, scalable search over Wikipedia articles.

Installation

Follow the install instructions to run the example.

Design

The example uses an indexing technique helpful for doing multiple logical tests against content. In this case, we can perform a word search on Wikipedia articles. The sample application takes advantage of 3 unique capabilities of Accumulo:

  1. Extensible iterators that operate within the distributed tablet servers of the key-value store
  2. Custom aggregators which can efficiently condense information during the various life-cycles of the log-structured merge tree
  3. Custom load balancing, which ensures that a table is evenly distributed on all tablet servers

In the example, Accumulo tracks the cardinality of all terms as elements are ingested. If the cardinality is small enough, it will track the set of documents by term directly. For example:

Row (word)Value (count)Value (document list)
Octopus2[Document 57, Document 220]
Other172,849[]
Ostrich1[Document 901]

Searches can be optimized to focus on low-cardinality terms. To create these counts, the example installs “aggregators” which are used to combine inserted values. The ingester just writes simple “(Octopus, 1, Document 57)” tuples. The tablet servers then used the installed aggregators to merge the cells as the data is re-written, or queried. This reduces the in-memory locking required to update high-cardinality terms, and defers aggregation to a later time, where it can be done more efficiently.

The example also creates a reverse word index to map each word to the document in which it appears. But it does this by choosing an arbitrary partition for the document. The article, and the word index for the article are grouped together into the same partition. For example:

Row (partition)Column FamilyColumn QualifierValue
1DDocument 57“smart Octopus”
1Word, OctopusDocument 57
1Word, smartDocument 57
...
2DDocument 220“big Octopus”
2Word, bigDocument 220
2Word, OctopusDocument 220

Of course, there would be large numbers of documents in each partition, and the elements of those documents would be interlaced according to their sort order.

By dividing the index space into partitions, the multi-word searches can be performed in parallel across all the nodes. Also, by grouping the document together with its index, a document can be retrieved without a second request from the client. The query “octopus” and “big” will be performed on all the servers, but only those partitions for which the low-cardinality term “octopus” can be found by using the aggregated reverse index information. The query for a document is performed by extensions provided in the example. These extensions become part of the tablet server's iterator stack. By cloning the underlying iterators, the query extensions can seek to specific words within the index, and when it finds a matching document, it can then seek to the document location and retrieve the contents.

Performance

The Wikisearch examples was run a on a cluster of 10 servers, each with 12 cores, and 32G RAM, 6 500G drives. Accumulo tablet servers were allowed a maximum of 3G of working memory, of which 2G was dedicated to caching file data.

Following the instructions in the example, the Wikipedia XML data for articles was loaded for English, Spanish and German languages into 10 partitions. The data is not partitioned by language: multiple languages were used to get a larger set of test data. The data load took around 8 hours, and has not been optimized for scale. Once the data was loaded, the content was compacted which took about 35 minutes.

The example uses the language-specific tokenizers available from the Apache Lucene project for Wikipedia data.

Original files:

ArticlesCompressed sizeFilename
1.3M2.5Gdewiki-20111120-pages-articles.xml.bz2
3.8M7.9Genwiki-20111115-pages-articles.xml.bz2
0.8M1.4Geswiki-20111112-pages-articles.xml.bz2

The resulting tables:

> du -p wiki.*
      47,325,680,634 [wiki]
       5,125,169,305 [wikiIndex]
                 413 [wikiMetadata]
       5,521,690,682 [wikiReverseIndex]

Roughly a 6:1 increase in size.

We performed the following queries, and repeated the set 5 times. The query language is much more expressive than what is shown below. The actual query specified that these words were to be found in the body of the article. Regular expressions, searches within titles, negative tests, etc are available.

QuerySample 1 (seconds)Sample 2 (seconds)Sample 3 (seconds)Sample 4 (seconds)Sample 5 (seconds)MatchesResult Size
“old” and “man” and “sea”4.073.793.653.853.6722,9563,830,102
“paris” and “in” and “the” and “spring”3.063.062.783.022.9210,7551,757,293
“rubber” and “ducky” and “ernie”0.080.080.10.110.16808
“fast” and ( “furious” or “furriest”)1.341.331.31.311.312,973493,800
“slashdot” and “grok”0.060.060.060.060.06142,371
“three” and “little” and “pigs”0.920.910.91.080.882,742481,531

Because the terms are tested together within the tablet server, even fairly high-cardinality terms such as “old,” “man,” and “sea” can be tested efficiently, without needing to return to the client, or make distributed calls between servers to perform the intersection between terms.

For reference, here are the cardinalities for all the terms in the query (remember, this is across all languages loaded):

TermCardinality
ducky795
ernie13,433
fast166,813
furious10,535
furriest45
grok1,168
in1,884,638
little320,748
man548,238
old720,795
paris232,464
pigs8,356
rubber17,235
sea247,231
slashdot2,343
spring125,605
the3,509,498
three718,810

Accumulo supports caching index information, which is turned on by default, and for the non-index blocks of a file, which is not. After turning on data block caching for the wiki table:

QuerySample 1 (seconds)Sample 2 (seconds)Sample 3 (seconds)Sample 4 (seconds)Sample 5 (seconds)
“old” and “man” and “sea”2.472.482.512.482.49
“paris” and “in” and “the” and “spring”1.331.421.61.611.47
“rubber” and “ducky” and “ernie”0.070.080.070.070.07
“fast” and ( “furious” or “furriest”)1.280.780.770.790.78
“slashdot” and “grok”0.040.040.040.040.04
“three” and “little” and “pigs”0.550.320.320.310.27

For comparison, these are the cold start lookup times (restart Accumulo, and drop the operating system disk cache):

QuerySample
“old” and “man” and “sea”13.92
“paris” and “in” and “the” and “spring”8.46
“rubber” and “ducky” and “ernie”2.96
“fast” and ( “furious” or “furriest”)6.77
“slashdot” and “grok”4.06
“three” and “little” and “pigs”8.13

Random Query Load

Random queries were generated using common english words. A uniform random sample of 3 to 5 words taken from the 10000 most common words in the Project Gutenberg's online text collection were joined with “and”. Words containing anything other than letters (such as contractions) were not used. A client was started simultaneously on each of the 10 servers and each ran 100 random queries (1000 queries total).

TimeCount
41.97440,743
41.61320,522
42.11347,969
38.32275,655

Query Load During Ingest

The English wikipedia data was re-ingested on top of the existing, compacted data. The following query samples were taken in 5 minute intervals while ingesting 132 articles/second:

QuerySample 1 (seconds)Sample 2 (seconds)Sample 3 (seconds)Sample 4 (seconds)Sample 5 (seconds)
“old” and “man” and “sea”4.913.9211.589.8610.21
“paris” and “in” and “the” and “spring”5.033.3712.223.299.46
“rubber” and “ducky” and “ernie”4.212.048.571.541.68
“fast” and ( “furious” or “furriest”)5.842.832.563.123.09
“slashdot” and “grok”5.682.622.22.782.8
“three” and “little” and “pigs”7.823.422.793.293.3