2016-04-09 5 views
0

Ich verwende Lucene 5.5.0 für die Indizierung. Die folgenden Kriterien beschreiben meine Umgebung:Lucene: Verbesserte Leistung bei nicht abgesteckten Suchen?

  1. Die indizierten Dokumente bestehen aus jeweils 8 Feldern. Sie sind für alle Dokumente im Korpus gleich (alle Dokumente haben das gleiche "Schema").
  2. Alle Felder sind entweder String oder Felder (daher ist keine Textanalyse erforderlich). Alle werden von Lucene gelagert. Die Zeichenfolgen haben eine maximale Länge von 255 Zeichen.
  3. Der Index wird als "hauptsächlich gelesen" behandelt, wobei 90% aller Anfragen (gleichzeitige) Lesevorgänge sind. Ich schließe die Anwendungsebene ab, so dass Lucene sich nicht um gleichzeitige Lese- und Schreibvorgänge kümmern muss.
  4. Bei der Suche im Korpus, ich nicht eine Rangfolge der Ergebnisse. Die Reihenfolge der abgerufenen Dokumente kann völlig willkürlich sein.
  5. Die Abfragen sind in der Regel eine Kombination aus Booleschen, regulären und numerischen Bereichsabfragen.
  6. Alle Dokumente, die der Abfrage entsprechen, sind beim Suchen des Korpus mit der höchsten Priorität.

Die aktuelle search Methode, die ich umgesetzt habe, Lucene API Einwickeln, sieht wie folgt aus:

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); 
} 

Gibt es eine Möglichkeit, dies schneller zu tun/besser, über meine Umgebung skizziert in Erwägung? Vor allem, da ich kein Ranking oder Sortierung benötige (sondern Vollständigkeit des Ergebnisses), denke ich, dass es einige Ecken geben sollte, um etwas zu schneiden und Dinge schneller zu machen.

Antwort

4

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.

+0

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

+0

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