Lucene:改善不受欢迎的搜索性能?

时间:2016-04-09 14:26:22

标签: java performance search lucene

我正在使用Lucene 5.5.0进行索引。以下标准描述了我的环境:

  1. 索引文档每个包含8个字段。它们对于语料库中的所有文档都是相同的(所有文档都具有相同的“模式”)。
  2. 所有字段都是StringLong字段(因此不需要进行文本分析)。所有这些都由lucene存储。字符串的最大长度为255个字符。
  3. 索引被视为“主要读取”,所有请求的90%是(并发)读取。我正在锁定应用程序级别,因此Lucene不必担心并发读取和写入。
  4. 在搜索语料库时,我不要求对结果进行任何排名。检索到的文档结果的顺序可以完全是任意的。
  5. 查询通常是布尔,正则表达式和数值范围查询的组合。
  6. 搜索语料库时,检索与该查询匹配的所有文档为最高优先级
  7. 我实现的当前search方法包含了Lucene的API,如下所示:

    public Set<Document> performLuceneSearch(Query query) {
            Set<Document> documents = Sets.newHashSet();
            // the reader instance is reused as often as possible, and exchanged
            // when a write occurs using DirectoryReader.openIfChanged(...).
            if (this.reader.numDocs() > 0) {
                // note that there cannot be a limiting number on the result set.
                // I absolutely need to retrieve ALL matching documents, so I have to
                // make use of 'reader.numDocs()' here.
                TopDocs topDocs = this.searcher.search(query, this.reader.numDocs());
                ScoreDoc[] scoreDocs = topDocs.scoreDocs;
                for (ScoreDoc scoreDoc : scoreDocs) {
                    int documentId = scoreDoc.doc;
                    Document document = this.reader.document(documentId);
                    documents.add(document);
                }
            }
            return Collections.unmodifiableSet(documents);
    }
    

    考虑到我上面概述的环境,有没有办法更快/更好地做到这一点?特别是考虑到我不需要任何排名或排序(但结果的完整性),我觉得应该有一些角落来削减和加快速度。

1 个答案:

答案 0 :(得分:4)

您可以采取一些措施来加快搜索速度。 首先,如果你不使用评分,你应该禁用规范,这将使索引更小。 由于您只使用StringFieldLongField(而不是TextField使用关键字标记符),因此对这些字段禁用规范,因此您已经获得了之一。

其次,您应该构建并包装查询,以便最小化实际分数的计算。也就是说,如果您使用BooleanQuery,请使用Occur.FILTER代替Occur.MUST。两者都具有相同的包含逻辑,但过滤器没有得分。对于其他查询,请考虑将它们包装在ConstantScoreQuery中。但是,这可能根本不需要(解释如下)。

第三,使用自定义Collector。默认搜索方法适用于小型,排序或排序的结果集,但您的用例并不适合该模式。以下是一个示例实现:

import org.apache.lucene.document.Document;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.SimpleCollector;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


final class AllDocumentsCollector extends SimpleCollector {

  private final List<Document> documents;
  private LeafReader currentReader;

  public AllDocumentsCollector(final int numDocs) {
    this.documents = new ArrayList<>(numDocs);
  }

  public List<Document> getDocuments() {
    return Collections.unmodifiableList(documents);
  }

  @Override
  protected void doSetNextReader(final LeafReaderContext context) {
    currentReader = context.reader();
  }

  @Override
  public void collect(final int doc) throws IOException {
    documents.add(currentReader.document(doc));
  }

  @Override
  public boolean needsScores() {
    return false;
  }
}

你可以这样使用它。

public List<Document> performLuceneSearch(final Query query) throws IOException {
  // the reader instance is reused as often as possible, and exchanged
  // when a write occurs using DirectoryReader.openIfChanged(...).
  final AllDocumentsCollector collector = new AllDocumentsCollector(this.reader.numDocs());
  this.searcher.search(query, collector);
  return collector.getDocuments();
}

收集器使用列表而不是集合。 Document未实施equalshashCode,因此您不会从套装中获利,只需支付额外的平等支票。最后的顺序是所谓的索引顺序。第一个文档将是索引中的第一个文档(粗略的插入顺序,如果您没有自定义合并策略,但最终它是一个不保证稳定的任意顺序或可靠)。此外,收集器表示不需要分数,这使您获得与使用上面的选项2相同的好处,因此您可以省去一些麻烦,只需保留现在的查询。

根据您需要Document的内容,您可以使用DocValues而不是存储字段来获得更高的加速。仅当您只需要一个或两个字段时才会这样,而不是全部字段。经验法则是,对于少数文件但是很多字段,使用存储的字段;对于许多文档但很少使用字段,请使用DocValues。无论如何,你应该尝试 - 8个字段不是那么多,你可能会为所有领域获利。以下是在索引过程中使用DocValues的方法:

import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.util.BytesRef;

document.add(new StringField(fieldName, stringContent, Field.Store.NO));
document.add(new SortedDocValuesField(fieldName, new BytesRef(stringContent)));
// OR
document.add(new LongField(fieldName, longValue, Field.Store.NO));
document.add(new NumericDocValuesField(fieldName, longValue));

字段名称可以相同,如果您可以完全依赖DocValues,则可以选择不存储其他字段。 必须更改收集器,这是一个字段的示例:

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.search.SimpleCollector;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


final class AllDocumentsCollector extends SimpleCollector {

  private final List<String> documents;
  private final String fieldName;
  private SortedDocValues docValues;

  public AllDocumentsCollector(final String fieldName, final int numDocs) {
    this.fieldName = fieldName;
    this.documents = new ArrayList<>(numDocs);
  }

  public List<String> getDocuments() {
    return Collections.unmodifiableList(documents);
  }

  @Override
  protected void doSetNextReader(final LeafReaderContext context) throws IOException {
    docValues = context.reader().getSortedDocValues(fieldName);
  }

  @Override
  public void collect(final int doc) throws IOException {
    documents.add(docValues.get(doc).utf8ToString());
  }

  @Override
  public boolean needsScores() {
    return false;
  }
}

分别对长字段使用getNumericDocValues。您必须为您必须加载的所有字段重复此操作(当然在同一个收集器中)并且最重要的是:测量何时更好地从存储的字段加载完整文档而不是使用DocValues。

最后一点说明:

  

我正在锁定应用程序级别,因此Lucene不必担心并发读写。

IndexSearcher和IndexWriter本身已经是线程安全的。如果您仅为Lucene锁定,则可以删除这些锁并在所有线程中共享它们。并考虑使用oal.search.SearcherManager重用IndexReader / Searcher。