[英]Lucene Index Query does not find document if too many documents/similar documents present
如果我这样创建文档:
{
Document document = new Document();
document.add(new TextField("id", "10384-10735", Field.Store.YES));
submitDocument(document);
}
{
Document document = new Document();
document.add(new TextField("id", "10735", Field.Store.YES));
submitDocument(document);
}
for (int i = 20000; i < 80000; i += 123) {
Document otherDoc1 = new Document();
otherDoc1.add(new TextField("id", String.valueOf(i), Field.Store.YES));
submitDocument(otherDoc1);
Document otherDoc2 = new Document();
otherDoc2.add(new TextField("id", i + "-" + (i + 67), Field.Store.YES));
submitDocument(otherDoc2);
}
意义:
10384-10735
10735
的一个(这是上一个文档 ID 的最后一部分)然后使用以下方法编写它们:
final IndexWriterConfig luceneWriterConfig = new IndexWriterConfig(new StandardAnalyzer());
luceneWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
final IndexWriter luceneDocumentWriter = new IndexWriter(luceneDirectory, luceneWriterConfig);
for (Map.Entry<String, Document> indexDocument : indexDocuments.entrySet()) {
final Term term = new Term(Index.UNIQUE_LUCENE_DOCUMENT_ID, indexDocument.getKey());
indexDocument.getValue().add(new TextField(Index.UNIQUE_LUCENE_DOCUMENT_ID, indexDocument.getKey(), Field.Store.YES));
luceneDocumentWriter.updateDocument(term, indexDocument.getValue());
}
luceneDocumentWriter.close();
现在索引已经写好了,我想执行一个查询,搜索 ID 10384-10735
的文档。
我将通过两种方式执行此操作,使用 TermQuery 和带有 StandardAnalyzer 的 QueryParser:
System.out.println("term query: " + index.findDocuments(new TermQuery(new Term("id", "10384-10735"))));
final QueryParser parser = new QueryParser(Index.UNIQUE_LUCENE_DOCUMENT_ID, new StandardAnalyzer());
System.out.println("query parser: " + index.findDocuments(parser.parse("id:\"10384 10735\"")));
在这两种情况下,我都希望文档出现。 但是,如果我运行查询,这就是结果:
term query: []
query parser: []
这似乎很奇怪。 我进一步试验了一下,发现如果我减少文档数量或删除条目10735
,查询解析器查询现在可以成功找到文档:
term query: []
query parser: [Document<stored,indexed,tokenized<id:10384-10735> stored,indexed,tokenized<uldid:10384-10735>>]
这意味着这有效:
{
Document document = new Document();
document.add(new TextField("id", "10384-10735", Field.Store.YES));
submitDocument(document);
}
for (int i = 20000; i < 80000; i += 123) {
Document otherDoc1 = new Document();
otherDoc1.add(new TextField("id", String.valueOf(i), Field.Store.YES));
submitDocument(otherDoc1);
Document otherDoc2 = new Document();
otherDoc2.add(new TextField("id", i + "-" + (i + 67), Field.Store.YES));
submitDocument(otherDoc2);
}
这有效(490 份文件)
{
Document document = new Document();
document.add(new TextField("id", "10384-10735", Field.Store.YES));
submitDocument(document);
}
{
Document document = new Document();
document.add(new TextField("id", "10735", Field.Store.YES));
submitDocument(document);
}
for (int i = 20000; i < 50000; i += 123) {
Document otherDoc1 = new Document();
otherDoc1.add(new TextField("id", String.valueOf(i), Field.Store.YES));
submitDocument(otherDoc1);
Document otherDoc2 = new Document();
otherDoc2.add(new TextField("id", i + "-" + (i + 67), Field.Store.YES));
submitDocument(otherDoc2);
}
有人知道这是什么原因吗? 我真的需要索引来始终如一地找到文档。 我可以使用 QueryParser 而不是 TermQuery。
我使用 9.3.0 lucene-core 和 lucene-queryparser。
提前谢谢你的帮助。
编辑 1 :这是 findDocuments() 中的代码:
final TopDocs topDocs = getIndexSearcher().search(query, Integer.MAX_VALUE);
final List<Document> documents = new ArrayList<>((int) topDocs.totalHits.value);
for (int i = 0; i < topDocs.totalHits.value; i++) {
documents.add(getIndexSearcher().doc(topDocs.scoreDocs[i].doc));
}
return documents;
编辑2 :这是一个工作示例: https://pastebin.com/Ft0r8pN5
出于某种原因,文档过多的问题并没有发生在这个问题中,我将对此进行研究。 我仍然将其留作示例。 这是我的 output:
[similar id: true, many documents: true]
Indexing [3092] documents
term query: []
query parser: []
[similar id: true, many documents: false]
Indexing [654] documents
term query: []
query parser: []
[similar id: false, many documents: true]
Indexing [3091] documents
term query: []
query parser: [Document<stored,indexed,tokenized<id:10384-10735> stored,indexed,tokenized<uldid:10384-10735>>]
[similar id: false, many documents: false]
Indexing [653] documents
term query: []
query parser: [Document<stored,indexed,tokenized<id:10384-10735> stored,indexed,tokenized<uldid:10384-10735>>]
如您所见,如果将 ID 为10735
的文档添加到文档中,则无法再找到该文档。
乍一看,一个可能的解决方案是:
带有作为第一个参数传递的术语的updateDocument()
方法当前用于构建索引。 当将null
作为术语传递或使用addDocument()
方法时,查询成功返回了正确的值。 解决方案必须与Term
相关。
luceneDocumentWriter.addDocument(indexDocument.getFields());
// or
luceneDocumentWriter.updateDocument(null, indexDocument);
再玩一点:存储相关文档的术语的键不能再次用作文档内的字段键,否则文档将变得不可搜索:
final Term term = new Term("uldid", indexDocument.get("id"));
// would work, different key from term...
indexDocument.add(new TextField("uldid2", indexDocument.get("id"), Field.Store.YES));
// would not work...
indexDocument.add(new TextField("uldid", indexDocument.get("id"), Field.Store.YES));
// ...when adding to index using term
luceneDocumentWriter.updateDocument(term, indexDocument);
避免这种情况的另一种方法是使用文档中相同字段的不同值(在本例中为uldid
),这也与在索引中搜索的 ID 不同:
final Term term = new Term("uldid", indexDocument.get("id").hashCode() + "");
// or
indexDocument.add(new TextField("uldid", indexDocument.get("id").hashCode() + "", Field.Store.YES));
这似乎很奇怪。 我真的没有最终的解决方案或原因,但从现在开始我将使用第二个选项,使用 hash 的任何键,我想将文档存储为Term
。
概括
问题是由 (a) 处理您的文件的顺序引起的; (b) updateDocument
首先删除然后在索引中插入数据的事实。
当您使用writer.updateDocument(term, document)
时,Lucene 执行原子删除然后添加:
通过首先删除包含术语的文档然后添加新文档来更新文档。
在您的情况下,处理文档的顺序取决于如何从您的 Java Map
检索它们 - 这取决于 Z1D78DC8ED51214E518ZB5114FE24490AE 如何对条目进行哈希处理。
正如您在回答中指出的那样,您已经有办法通过使用 Java object 哈希作为updateDocument
术语来避免这种情况。 (只要您没有遇到任何 hash 碰撞。)
该答案试图解释您所看到的结果背后的“原因”。
基本演示
这是您的代码的高度简化版本。
考虑以下两个 Lucene 文档:
final Document documentA = new Document();
documentA.add(new TextField(FIELD_NAME, "10735", Field.Store.YES));
final Term termA = new Term(FIELD_NAME, "10735");
writer.updateDocument(termA, documentA);
final Document documentB = new Document();
documentB.add(new TextField(FIELD_NAME, "10384-10735", Field.Store.YES));
final Term termB = new Term(FIELD_NAME, "10384-10735");
writer.updateDocument(termB, documentB);
文件A然后是文件B:
Lucene 在添加documentA
时没有什么可删除的。 添加文档后,索引包含以下内容:
field id
term 10735
doc 0
freq 1
pos 0
所以,我们只有一个令牌10735
。
对于documentB
,索引中没有包含术语10384-10735
的文档 - 因此在将documentB
添加到索引之前不会删除任何内容。
我们最终得到以下最终索引数据:
field id
term 10384
doc 1
freq 1
pos 0
term 10735
doc 0
freq 1
pos 0
doc 1
freq 1
pos 1
当我们搜索10384
时,正如预期的那样,我们得到了一个结果。
文件B 然后是文件A:
如果我们交换 2 个文档的处理顺序,我们会在documentB
被索引后看到以下内容:
field id
term 10384
doc 0
freq 1
pos 0
term 10735
doc 0
freq 1
pos 1
当documentA
被索引时,Lucene 发现索引中的doc 0
(上图)确实包含documentA
使用的术语10735
。 因此,在添加documentA
之前,会从索引中删除所有doc 0
条目。
我们最终得到以下索引数据(基本上是一个新的doc 0
,在原始doc 0
被删除之后):
field id
term 10735
doc 0
freq 1
pos 0
现在,当我们搜索10384
时,我们得到零命中——这不是我们所期望的。
更复杂的演示
通过使用 Java Map
来收集要索引的文档,在您的问题场景中事情变得更加复杂。 由于 map 执行散列,这会导致 Lucene 文档的索引顺序与创建它们的顺序不同。
这是您的代码的另一个简化版本,但这次它使用 map:
public class MyIndexBuilder {
private static final String INDEX_PATH = "index";
private static final String FIELD_NAME = "id";
private static final Map<String, Document> indexDocuments = new HashMap<>();
public static void buildIndex() throws IOException, FileNotFoundException, ParseException {
final Directory dir = FSDirectory.open(Paths.get(INDEX_PATH));
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
iwc.setOpenMode(OpenMode.CREATE);
//iwc.setCodec(new SimpleTextCodec());
try ( IndexWriter writer = new IndexWriter(dir, iwc)) {
String suffix = "10429";
Document document1 = new Document();
document1.add(new TextField("id", "10001-" + suffix, Field.Store.YES));
indexDocuments.put("10001-" + suffix, document1);
Document document2 = new Document();
document2.add(new TextField("id", suffix, Field.Store.YES));
indexDocuments.put(suffix, document2);
//int max = 10193; // OK
int max = 10192; // not OK
for (int i = 10003; i <= max; i += 1) {
Document otherDoc1 = new Document();
otherDoc1.add(new TextField(FIELD_NAME, String.valueOf(i), Field.Store.YES));
indexDocuments.put(String.valueOf(i), otherDoc1);
}
System.out.println("Total docs: " + indexDocuments.size());
for (Map.Entry<String, Document> indexDocument : indexDocuments.entrySet()) {
if (indexDocument.getKey().contains(suffix)) {
// show the order in which the document1 and document2 are indexed:
System.out.println(indexDocument.getKey());
}
final Term term = new Term(FIELD_NAME, indexDocument.getKey());
writer.updateDocument(term, indexDocument.getValue());
}
}
}
}
除了我们感兴趣的两个文档之外,我还在索引中添加了 191 个额外的(完全不相关的)文档。
当我处理 map 时,我看到以下 output:
Total docs: 193
10429
10001-10429
因此, document2
在document1
之前被索引 - 我们对10001
的搜索会找到一个命中。
但是,如果我处理更少的这些“额外”文档(190 个而不是 191 个):
int max = 10192; // not OK
...然后我得到这个 output:
Total docs: 192
10001-10429
10429
您可以看到document1
和document2
的处理顺序已经颠倒了 - 现在对10001
的相同搜索发现零命中。
一个看似不相关的更改(处理少一个文档)导致从 map 的检索顺序发生更改,从而导致索引数据不同。
(当我注意到索引数据显然相同时,我在问题中的一个评论中是不正确的。它不一样。当我第一次查看索引数据时,我错过了。)
推荐
考虑在您的 Lucene 文档中添加一个新字段,用于存储每个文档的唯一标识符。
您可以将其doc_id
并将其创建为StringField
,而不是TextField
。
这将确保该字段的内容永远不会被标准分析器处理并作为单个(可能是唯一的)令牌存储在索引中。 StringField
已编入索引但未标记化。
然后,您可以在构建要在updateDocument()
方法中使用的术语时使用此字段。 您可以使用现有的id
字段进行搜索。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.