Es gibt ein paar Dinge, die Sie tun können, um die Suche zu beschleunigen. Erstens, wenn Sie nicht Scoring verwenden, sollten Sie Normen deaktivieren, dies wird den Index kleiner machen. Da Sie nur StringField
und LongField
verwenden (im Gegensatz zu beispielsweise TextField
mit einem Schlüsselwort Tokenizer), sind Normen für diese Felder deaktiviert, so dass Sie diese bereits haben.
Zweitens sollten Sie strukturieren und umbrechen Sie Ihre Abfrage, so dass Sie die Berechnung der tatsächlichen Ergebnisse minimieren. Das heißt, wenn Sie verwenden, verwenden Sie Occur.FILTER
anstelle von Occur.MUST
. Beide haben die gleiche Einschlusslogik, aber der Filter punktet nicht. Für andere Abfragen sollten Sie sie in eine ConstantScoreQuery
einschließen. Dies ist jedoch möglicherweise nicht notwendig (Erklärung folgt).
Drittens verwenden Sie eine benutzerdefinierte Collector
. Die Standardsuchmethode ist für kleine, sortierte oder sortierte Ergebnismengen gedacht, aber Ihr Anwendungsfall passt nicht zu diesem Muster. Hier ist eine Beispiel-Implementierung:
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;
}
}
Sie würden es so verwenden.
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();
}
Der Kollektor verwendet eine Liste anstelle eines Sets. Document
implementiert nicht equals
oder hashCode
, so dass Sie nicht von einem Set profitieren und nur für zusätzliche Gleichheitsüberprüfungen bezahlen. Die endgültige Reihenfolge ist die sogenannte Indexreihenfolge.Das erste Dokument wird dasjenige sein, das zuerst im Index erscheint (ungefähr Insertionsreihenfolge, wenn Sie keine benutzerdefinierten Zusammenführungsstrategien haben, aber letztlich ist es eine willkürliche Reihenfolge, die nicht garantiert stabil oder zuverlässig ist). Außerdem signalisiert der Collector, dass keine Scores benötigt werden, was Ihnen die gleichen Vorteile bietet wie die Option 2 von oben, so dass Sie sich ein paar Probleme ersparen und Ihre Anfrage einfach so lassen können, wie sie gerade ist.
Je nachdem, was Sie für die Document
s benötigen, können Sie eine noch größere Beschleunigung erzielen, indem Sie DocValues anstelle von gespeicherten Feldern verwenden. Dies gilt nur, wenn Sie nur ein oder zwei Ihrer Felder benötigen, nicht alle. Die Faustregel ist, für einige Dokumente, aber viele Felder, gespeicherte Felder zu verwenden; Für viele Dokumente, aber wenige Felder, verwenden Sie DocValues. Auf jeden Fall sollten Sie experimentieren - 8 Felder sind nicht so viel und Sie könnten das Ereignis für alle Felder nutzen. Hier ist, wie Sie DocValues in Ihrem Index Prozess verwenden würde:
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));
Der Feldname kann die gleiche sein, und Sie können wählen, nicht speichern Sie Ihre anderen Bereichen, wenn Sie sich ganz auf DocValues verlassen können. Die der Kollektor muss geändert werden, beispielhaft für ein Feld:
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;
}
}
Sie getNumericDocValues
für die lange Felder verwenden würden, respectively. Sie müssen dies (natürlich im selben Collector) für all Ihre Felder wiederholen, die Sie laden müssen, und am wichtigsten: messen, wann es besser ist, vollständige Dokumente aus den gespeicherten Feldern zu laden, anstatt DocValues zu verwenden.
Eine letzte Anmerkung:
ich auf der Anwendungsebene Sperren tue, so Lucene sich nicht um gleichzeitig kümmern liest und schreibt.
Der IndexSearcher und IndexWriter selbst sind bereits Thread-sicher. Wenn Sie ausschließlich für Lucene sperren, können Sie diese Sperren entfernen und sie nur für alle Ihre Threads freigeben. Und erwägen Sie, oal.search.SearcherManager
für die Wiederverwendung des IndexReader/Searcher zu verwenden.
Wow, das ist eine umfassende Antwort genau dort! Vielen Dank, ich schätze es sehr! Ich werde diese Vorschläge definitiv versuchen. Ich brauche die Locking-Sachen für die Anwendung selbst, also habe ich es nicht speziell für Lucene implementiert, aber es ist gut zu wissen, dass diese Klassen selbst Thread-sicher sind. Eine letzte Frage: Wenn Sie DocValuesFields anstelle von gespeicherten Feldern verwenden, müssen Sie das Format der persistenten Datei ändern, richtig? Weil ich meinen Code bereits an einigen Standorten bereitgestellt habe und bestimmte Leute unglücklich darüber wären, Änderungen zu brechen. – Alan47
Ja, die Verwendung von DocValues würde eine Änderung des Indexes erfordern und somit eine vollständige Neuindizierung bewirken. DocValues könnte jedoch einfach zum Index hinzugefügt werden (nichts müsste * gelöscht werden), daher könnte ich mir ein Migrationsskript vorstellen, das das ohne Ausfallzeiten tun kann. Alles in allem ein Grund mehr, dies gründlich zu testen und die Vorteile von Leistung und Wartung abzuwägen. – knutwalker