Allow user to create Lucene Indexes on data stored in Geode
Allow user to perform text (Lucene) search on Geode data using the Lucene index. Results from the text searches may be stale due to asynchronous index updates.
Provide highly available of indexes using Geode's HA capabilities
Building next/better Solr/Elasticsearch.
Enhancing the current Geode OQL to use Lucene index.
A previous integration of Lucene and GemFire:
Similar efforts done by other data products
Hibernate Search: Hibernate search
Solandra: Solandra embeds Solr in Cassandra.
A single index will not support multiple regions. Join queries between regions are not supported
Now that this feature has been implemented, please refer to the javadocs for details on the Java API.
// Get LuceneService
LuceneService luceneService = LuceneServiceProvider.get(cache);
// Create Index on fields with default analyzer:
luceneService.createIndex(indexName, regionName, "field1", "field2", "field3");
// create index on fields with specified analyzer:
Map<String, Analyzer> analyzerPerField = new HashMap<String, Analyzer>();
analyzerPerfield.put("field1", new StandardAnalyzer());
analyzerPerfield.put("field2", new KeywardAnalyzer());
luceneService.createIndex(indexName, regionName, analyzerPerField);
Region region = cache.createRegionFactory(RegionShutcut.PARTITION).create(regionName);
// Create Query
LuceneQuery query = luceneService.createLuceneQueryFactory().setLimit(200).setPageSize(20)
.create(indexName, regionName, querystring, "field1" /* default field */);
// Search using Query
PageableLuceneQueryResults<K,Object> results = query.findPages();
// Pagination
while (results.hasNext()) {
results.next().stream().forEach(struct -> {
Object value = struct.getValue();
System.out.println("Key is "+struct.getKey()+", value is "+value);
});
} |
// List Index gfsh> list lucene indexes [with-stats] // Create Index gfsh> create lucene index --name=indexName --region=/orders --field=customer,tags // Create Index gfsh> create lucene index --name=indexName --region=/orders --field=customer,tags --analyzer=org.apache.lucene.analysis.standard.StandardAnalyzer,org.apache.lucene.analysis.bg.BulgarianAnalyzer Execute Lucene query gfsh> search lucene --regionName=/orders -queryStrings="John*" --defaultField=field1 --limit=100 |
<cache
xmlns="http://geode.apache.org/schema/cache"
xmlns:lucene="http://geode.apache.org/schema/lucene"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache
http://geode.apache.org/schema/cache/cache-1.0.xsd
http://geode.apache.org/schema/lucene
http://geode.apache.org/schema/lucene/lucene-1.0.xsd"
version="1.0">
<region name="region" refid="PARTITION">
<lucene:index name="index">
<lucene:field name="a" analyzer="org.apache.lucene.analysis.core.KeywordAnalyzer"/>
<lucene:field name="b" analyzer="org.apache.lucene.analysis.core.SimpleAnalyzer"/>
<lucene:field name="c" analyzer="org.apache.lucene.analysis.standard.ClassicAnalyzer"/>
</lucene:index>
</region>
</cache>
|
[LuceneIndex] --> [RegionDirectory]
() "User"
node "Colocated PR or Replicated Region" {
() User --> [User Data Region] : Puts
[User Data Region] --> [Async Queue]
[Async Queue] --> [LuceneIndex] : Batch Writes
[RegionDirectory] --> [Lucene Regions]
}
|
node "LuceneIndex" {
[Reflective fields]
[AEQ listener]
[RegionDirectory array (one per bucket)]
[Query objects]
}
|
() User -down-> [User Data Region] : PUTs
[User Data Region] ..> [Bucket 1]
[Bucket 1] -down-> [Async Queue Bucket 1]
node LuceneIndex {
[Async Queue Bucket 1] -down-> [AEQ listener processes events into index documents]:Batch Write
[AEQ listener processes events into index documents] -down-> [RegionDirectory1]
[RegionDirectory1] -down-> [file region bucket 1]
[file region bucket 1] -down-> [chunk region bucket 1]
}
[User Data Region] ..> [Bucket 2]
[Bucket 2] -down-> [Async Queue Bucket 2]
node LuceneIndex {
[Async Queue Bucket 2] -down-> [AEQ listener processes events into index documents]:Batch Write
[AEQ listener processes events into index documents] -down-> [RegionDirectory2]
[RegionDirectory2] -down-> [file region bucket 2]
[file region bucket 2] -down-> [chunk region bucket 2]
} |
() User -down-> [LuceneQuery] : fields, Analyzer, query strings, or Query [LuceneQuery] -down-> [User Data Region]: call search() [User Data Region] -down-> [Function Execution] [Function Execution] -down-> [Bucket 1] [Bucket 1] -down-> [RegionDirectory for bucket 1] [RegionDirectory for bucket 1] ..> [Bucket 1] : TopDocs, ScoreDocs [Bucket 1] ..> [Function Execution] : score, key [Function Execution] -down-> [Bucket 2] [Bucket 2] -down-> [RegionDirectory for bucket 2] [RegionDirectory for bucket 2] ..> [Bucket 2] : TopDocs, ScoreDocs [Bucket 2] ..> [Function Execution] : score, key |
PersistentRegions
LuceneIndex can be created and destroy. We don't support creating index on a region with data for now.
The index region and async event queue will be restored with its colocated data region's buckets. So during failover the new primary should be able to read/write index as usual.
In the case of partitioned regions, the query must be sent out to all the primaries. The results will then need to be aggregated back together. Lucene search will use FunctionService to distribute query to primaries.
Input to primaries
Output from primaries
participant LuceneQuery participant FunctionService participant FunctionCollector participant CollectorManager participant M1_LuceneFunction participant M1_CollectorManager participant Index_1 participant Index_2 LuceneQuery -> FunctionService: Query activate FunctionService FunctionService --> M1_LuceneFunction : LuceneContext activate M1_LuceneFunction FunctionService --> M2_LuceneFunction: LuceneContext activate M2_LuceneFunction M1_LuceneFunction -> Index_1 : search(Collector_1) Index_1 -> M1_LuceneFunction : loaded Collector_1 M1_LuceneFunction -> Index_2 : search(Collector_2) Index_2 -> M1_LuceneFunction : loaded Collector_2 M1_LuceneFunction -> M1_CollectorManager : merge Collectors activate M1_CollectorManager M1_CollectorManager -> M1_LuceneFunction : merged Collector deactivate M1_CollectorManager activate FunctionCollector M1_LuceneFunction -> FunctionCollector:Collector_M1 deactivate M1_LuceneFunction M2_LuceneFunction -> FunctionCollector:Collector_M2 deactivate M2_LuceneFunction FunctionCollector -> CollectorManager : merge Collectors activate CollectorManager CollectorManager -> FunctionCollector : Final Collector deactivate CollectorManager FunctionCollector -> FunctionService : Final Collector deactivate FunctionCollector FunctionService -> LuceneQuery : QueryResults deactivate FunctionService |
We are still investigating options for how to aggregate the data, see Text Search Aggregation Options.
In case of replicated regions, query will be sent to one of the members and get the results there. Aggregation will be handled in that member before returned to the caller.
A Lucene Service MBean is available and accessed through an ObjectName like:
GemFire:service=CacheService,name=LuceneService,type=Member,member=192.168.2.13(59583)<ec><v5>-1026
This MBean provides operations these operations:
/**
* Returns an array of {@link LuceneIndexMetrics} for the {@link com.gemstone.gemfire.cache.lucene.LuceneIndex}
* instances defined in this member
*
* @return an array of LuceneIndexMetrics for the LuceneIndexes defined in this member
*/
public LuceneIndexMetrics[] listIndexMetrics();
/**
* Returns an array of {@link LuceneIndexMetrics} for the {@link com.gemstone.gemfire.cache.lucene.LuceneIndex}
* instances defined on the input region in this member
*
* @param regionPath The full path of the region to retrieve
*
* @return an array of LuceneIndexMetrics for the LuceneIndex instances defined on the input region
* in this member
*/
public LuceneIndexMetrics[] listIndexMetrics(String regionPath);
/**
* Returns a {@link LuceneIndexMetrics} for the {@link com.gemstone.gemfire.cache.lucene.LuceneIndex}
* with the input index name defined on the input region in this member.
*
* @param regionPath The full path of the region to retrieve
* @param indexName The name of the index to retrieve
*
* @return a LuceneIndexMetrics for the LuceneIndex with the input index name defined on the input region
* in this member.
*/
public LuceneIndexMetrics listIndexMetrics(String regionPath, String indexName); |
A LuceneIndexMetrics data bean includes raw stat values like:
Region=/data2; index=full_index commitTime->107608255573 commits->5999 commitsInProgress->0 documents->498 queryExecutionTime->0 queryExecutionTotalHits->0 queryExecutions->0 queryExecutionsInProgress->0 updateTime->7313618780 updates->6419 updatesInProgress->0 |
Limitations include: