索引将 eXist-db 版本 2 升级到 4.5 时出现问题

Issue in index upgrading eXist-db version 2 to 4.5

我正在升级和测试大型安装,遇到了一个我无法理解的问题。我有大量文档,其中我的索引创建如下:

<collection xmlns="http://exist-db.org/collection-config/1.0">
    <index xmlns:mods="http://www.loc.gov/mods/v3" xmlns:xlink="http://www.w3.org/1999/xlink">
        <fulltext default="none" attributes="false"/>
        <lucene>
            <analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer">
                <param name="stopwords" type="org.apache.lucene.analysis.util.CharArraySet"/>
            </analyzer>
            <analyzer id="ws" class="org.apache.lucene.analysis.WhitespaceAnalyzer"/>
            <text qname="p"/>
            <text qname="li"/>
            <text qname="h1"/>
            <text qname="h2"/>
            <text qname="h3"/>
        </lucene>
    </index>
</collection>

在我的第 2 版安装中,这完美无缺。查询 returns 列表中的唯一元素 (p, li, h1, h2, h3)。它还 returns 那些元素中带有文本的元素(如预期的那样)。搜索功能为:

declare function ls:ls($collection as xs:string, $phrase as xs:string) as element()* {
    for $hit in collection(xmldb:encode-uri($collection))//*[ft:query(.,
        <query>
            <phrase>{$phrase}</phrase>
        </query>
        )]
        order by $hit/ancestor::div[@class='content']/@doc/string()
        return 
            <tr>
                <td>
                    {$hit/ancestor::div[@class='content']/@doc/string()}
                </td>
                <td>
                    {$hit/ancestor::div[@class='content']/@title/string()}
                </td>
                <td>
                    {local-name($hit)}
                </td>
                <td class="hit_text">
                    {normalize-space($hit)}
                </td>
            </tr>
};

只是为了查看结果,这里是网页结果的快照:

当然这并没有显示所有结果,但请相信我......它只是 return 命名元素,并且只有那些包含 "heart" 的元素。

在新版本 4 安装 export/import 之后,大多数其他一切都运行良好。然而,即使在重新索引内容后,完全相同的 xQuery returns 不需要的更高级别元素(如 div)以及 returns 不包含搜索短语的元素。

例如,这个完全相同的查询显示了这个结果:

现在,奇怪的是,如果我更改函数以删除通配符并仅在 "h1"(或任何其他命名元素)之后进行,它会起作用:

for $hit in collection(xmldb:encode-uri($collection))//h1[ft:query(.,

产量:

您可以看到,与前面的示例不同,没有 "heart" 的 h1 不是 returned.

我在升级中遗漏了什么?我错过或不明白的 Lucene 有什么变化吗?

更新

作为 hack(恕我直言),这有效:

let $targets := collection(xmldb:encode-uri($collection))//*[local-name(.) = 'p' or local-name(.) = 'h1' or local-name(.) = 'h2' or local-name(.) = 'h3' or local-name(.) = 'li']
    for $hit in $targets[ft:query(.,
        <query>
            <phrase>{$phrase}</phrase>
        </query>
        )]

但是,如果我删除创建节点集 $targets 并将 collection() 放在 "for" 中,那么它将不起作用。

更新二

肯定有问题(如全文未启用或运行ning或?)因为运行ning中的类似查询都采用方式 在新的、更新的服务器中更长。

那么我在升级中错过了什么?我 conf.xml 在两者中调用了 Lucene。任何有关寻找内容的提示都会很棒。

更新三

也许日志中的这个有问题?我怀疑它搜索 2.x 版本的日志显示相同的错误。

2018-12-19 19:27:05,570 [qtp14962548-143] ERROR (AnalyzerConfig.java [configureAnalyzer]:173) - Lucene index: analyzer class org.apache.lucene.analysis.WhitespaceAnalyzer not found. (org.apache.lucene.analysis.WhitespaceAnalyzer) 
2018-12-19 19:27:38,852 [qtp14962548-43] INFO  (NativeBroker.java [reindexCollection]:1844) - Start indexing collection /db/EIDO/data/Core 
2018-12-19 19:27:54,837 [qtp14962548-43] INFO  (NativeBroker.java [reindexCollection]:1854) - Finished indexing collection /db/EIDO/data/Core in 15985 ms. 

更新四

我按照建议将 collection.xconf 更改为删除停用词并删除 WhitespaceAnalyzer:

<collection xmlns="http://exist-db.org/collection-config/1.0">
    <index xmlns:mods="http://www.loc.gov/mods/v3" xmlns:xlink="http://www.w3.org/1999/xlink">
        <fulltext default="none" attributes="false"/>
        <lucene>
            <analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
            <text qname="p"/>
            <text qname="li"/>
            <text qname="h1"/>
            <text qname="h2"/>
            <text qname="h3"/>
        </lucene>
    </index>
</collection>

我重新索引了集合。来自日志:

2018-12-20 02:14:56,803 [qtp31631875-34] INFO  (NativeBroker.java [reindexCollection]:1844) - Start indexing collection /db/EIDO/data/Core 
2018-12-20 02:15:16,553 [qtp31631875-34] INFO  (NativeBroker.java [reindexCollection]:1854) - Finished indexing collection /db/EIDO/data/Core in 19750 ms. 

我得到了完全相同的结果。

更新 V

我想我是在踢球。本周末将再次 运行 整个过程,删除所有内容并重试,但这没有任何意义,也不起作用。

更新 VI

我不喜欢下注!现在,在查看结果时,基本上是在当前安装中搜索:

 for $hit in collection(xmldb:encode-uri($collection))//*[ft:query(.,
        <query>
            <phrase>{$phrase}</phrase>
        </query>
        )]

Returns 数据库中的每个元素,无论它们是否有 $phrase。它 return 是 div,然后是子 p,然后可能是子 span。他们全部。该词是否实际存在于文本中并不重要。

如果我将通配符“*”更改为 "h1",它 return 只是其中实际包含该文本的 h1。所以有些东西不对或坏了还是?我当然可以将提供给 ft:query 的元素列表更改为有问题的确切元素(p、h1、h2、h3、li),但是该查询在 4.5 中永远需要,在 2.[=37= 中需要几秒钟]

可能最后更新

我放弃并重新安装了一切,包括 Monex。我重新导出现有的数据库并导入它。尽管我通常会进行其他更改,但我只将端口更改为 80。

现在,即使尝试 运行 仪表板(导入后)也会产生:

javax.servlet.ServletException: javax.servlet.ServletException: An error occurred while processing request to /exist/apps/dashboard/: err:XPST0081 error found while loading module restxq: Error while loading module modules/restxq.xql: Invalid qname text:groups
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)
    at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:724)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
    at org.eclipse.jetty.server.Server.handle(Server.java:531)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:352)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
    at org.eclipse.jetty.io.ChannelEndPoint.run(ChannelEndPoint.java:118)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:760)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:678)

这向我表明,如果您安装了不同的应用程序,则导出数据库然后重新导入将永远行不通。

不幸的是,我不得不下注并寻找替代解决方案。我可以尝试重建数据或其他东西,但该应用程序有 10,000 个用户。无法重新创建。

在这个时候,我只能说它还没有准备好迎接黄金时段,只会坐在运行良好且已经运行多年的旧数据库上。

请注意...在安装全新、干净的数据库且没有任何更改后,我可以访问 Monex 或仪表板。如果我从我的备份中导入(根据需要,因为它不兼容二进制文件),一切都会中断。

对于开发人员来说,这对我来说是一个明显的问题。

再次更新

我做了一个完全干净的安装。之后我可以毫无问题地访问 Monex。然后我恢复我的数据库。注意:完成时有一个问题询问我是否要升级应用程序。不确定正确答案,也许这是一个问题,我回答错误(我回答否)。

全部重新安装后,我可以正常访问数据库和我的整个应用程序。但是当尝试 运行 Monex 时,我现在得到:

<exception>
    <path>/db/apps/monex/modules/view.xql</path>
    <message>err:XPST0081 error found while loading module indexes: Error while loading module indexes.xqm: Invalid qname text:index-terms</message> 
</exception>

升级应用程序的正确答案是肯定的吗?我假设这意味着我通过纯安装安装的 Monex 被我的版本 2 备份覆盖,这导致了错误。

我破解了导致问题的 monex 索引部分,并将 Monex 设置为 运行。所以它正在使用 Lucene:

所以,一个观察是关于为什么 Monex 运行 很好但恢复我的(旧)数据库会杀死它的问题。它不应该 AFAIK。

也许有人可以向我解释这个结果,我不明白第二项但我怀疑它是 returning everything:

好的,工作中

所以。首先,我发现恢复我的 /db 会破坏全新安装中的所有 /apps(如 monex)。对我来说似乎很奇怪,或者对我或其他人来说是糟糕的计划。所以为了解决这个问题,我有一个全新的安装备份。

安装新版本的 eXist 后,我​​恢复了旧数据库,然后立即再次恢复全新安装。这会用从我的备份安装的最新版本覆盖所有 /apps(如 monex),但不会破坏我的。对不起,荒谬。

现在我可以测试并查看正在使用的 Lucene 索引。但这就是它告诉我的全部,没有别的(正如我所怀疑的)。

很明显,Lucene 集成中的行为发生了变化。在我的旧版本中,我会发送每个元素,它只会 return 命中。在这个新版本中,您不能这样做。如果您发送类似于上面代码中的内容,即使有 none,它仍将 return 作为 "hit"。因此,$collection//* 将整个结构发送到查询,它 return 包含所有内容,无论是否有命中。它以前没有这种行为。

所以解决方案是(这是一个我什至讨厌说出来的黑客),你可以只将项目发送到你想要搜索的查询,看看是否有内容是命中的。哇。再次,我很抱歉,但如果我错了,请告诉我,但这完全是黑客攻击。如果我创建所有 p 的索引,我只希望 p 在我进行一般搜索时返回 p,发送 p、h1 等。现在发生的是它发送回所有内容,无论命中与否,除非你要求完全相同您索引的元素的名称。

这似乎是一个 late/early 绑定的东西。在旧的 eXist 中,我将发送 $coll/[ft:query... 并且它 returned 了我在索引中作为已识别元素的内容。现在它不能那样工作所以你不能在 $coll/[ft:query... =].恕我直言,这是错误的。

所以为了解决,我这样做了,基本上是先执行搜索,然后遍历结果。

declare function ls:ls($collection as xs:string, $phrase as xs:string) as element()* {
    let $coll := collection(xmldb:encode-uri($collection))
    let $hits := ($coll//p | $coll//li | $coll//h1 | $coll//h2 | $coll//h3)[ft:query(.,
        <query>
            <phrase>{$phrase}</phrase>
        </query>
        )]
    for $hit in $hits
        order by $hit/ancestor::div[@class='content']/@doc/string()
        return 
            <tr>
                <td>
                    {$hit/ancestor::div[@class='content']/@doc/string()}
                </td>
                <td>
                    {$hit/ancestor::div[@class='content']/@title/string()}
                </td>
                <td>
                    {local-name($hit)}
                </td>
                <td class="hit_text">
                    {normalize-space($hit)}
                </td>
            </tr>
}

;

现在我更新了测试,这也有效:

let $hits := (collection(xmldb:encode-uri($collection))//*)[ft:query(.,
    <query>
        <phrase>{$phrase}</phrase>
    </query>
    )]
for $hit in $hits ...

所以现在这与我以前的非常接近,我不需要去追求正确的显式元素。问题是现在他们不能在 for 循环中。

关键在这里:

(collection(xmldb:encode-uri($collection))//*)

对比:

collection(xmldb:encode-uri($collection))//*

所以...所有这些...解决方案是 for 循环需要:

for $hit in (collection(xmldb:encode-uri($collection))//*)[ft:query(.,
    <query>
        <phrase>{$phrase}</phrase>
    </query>
    )]

既然现在已经解决了,也许有人想解释为什么旧代码没有在 individual 元素周围使用 () 但确实有效不在最新的 eXist 中。

准确地说,我打开了两个系统进行测试。

版本 2x:

for $hit in collection(xmldb:encode-uri($collection))//*[ft:query(.,

一秒钟,答对。

for $hit in (collection(xmldb:encode-uri($collection))//*)[ft:query(.,

17秒,答对。

版本 4.5:

for $hit in collection(xmldb:encode-uri($collection))//*[ft:query(.,

10 秒,完全错误的答案(div 和未命中 returned)

for $hit in (collection(xmldb:encode-uri($collection))//*)[ft:query(.,

一秒答对

在我看来,在旧的 eXist 中,一个查询 return 什么都没做,而在这个新的 eXist 中,似乎 return 发送的每个元素的结果,如果不存在索引,它仍然return就这样了。

最后一次更新

在查看全新安装 conf.xml 时,我在 enable-query-rewriting 的 xquery 条目中找到了一条注释。此评论表明它是实验性的,设置为 "yes" 可能会导致不正确的结果。

请注意,我不相信我碰过它,默认安装将此值设置为 "yes"。我从干净安装中保存了 conf.xml,因为我改变了很多东西(当然),在查看干净安装时我看到了这个:

<xquery enable-java-binding="no" disable-deprecated-functions="no" 
        enable-query-rewriting="yes" backwardCompatible="no" 
        enforce-index-use="always"
        raise-error-on-failed-retrieval="no">

我改为"no"并重新启动了exist-db。现在一切都像以前一样工作,我现在在搜索中没有问题,它 return 正是我所期望的,查询的编写与版本 2x 中的完全一样。

所以...我相信我学到了什么

我实施了新的范围索引并根据下面的评论重新索引了集合并重新启用了查询重写。检查 monex 我看到了索引,但我的查询没有使用它们,它报告索引为遗留 "range" 和优化为 "No Index".

我发现我不能这样做(我假设通配符会这样做):

($collection//foo | $collection//bar)[包含(.,$phrase)]

或这个

($collection//foo , $collection//bar)[包含(.,$phrase)]

或这个

$测试节点 := $collection//foo | $collection//栏

然后

$testnodes[包含(.,$phrase)]

虽然有效,但它不使用新范围索引。这些将始终报告未使用索引。

但这确实使用了完全优化的新范围索引:

$collection//foo[包含(.,$phrase)] | $collection//bar[包含(.,$phrase)]

我们应该先清除错误...

  1. 空白分析器的 class 应该是 org.apache.lucene.analysis.core.WhitespaceAnalyzer

尽管看起来您没有通过 'id' 引用空白分析器,所以您可以删除它。

  1. 你使用 StandardAnalyzer 的配置在我看来是错误的。您指定了 stopwords 参数,但是:
    1. 它的class是错误的,应该是org.apache.lucene.analysis.util. CharArraySet,而
    2. 你还没有给它任何值。

如果您只需要默认停用词,则可以完全省略该参数。

完成这些更改后,您应该尝试重建索引并再次监控日志。

之后,您应该使用 eXist 4.5.0 中仪表板中的 Monex 应用程序来检查可用索引,以检查您的数据是否按照您的预期进行了索引。

更新 1

来自@kevin-brown 的评论:

From what I see today, if I do this ($collection//foo | $collection//bar)[fn:contains(.,'string')] no index is used. But if I do this $collection//foo[fn:contains(.,'string')] | $collection//bar[fn:contains(.,'string')],the new-range index is used and optimization is full.

我可以确认,在 XQuery 的某些表述中,eXist-db 没有正确地优化查询以使用范围索引。这肯定是一个错误!

eXist-db 的 Java 管理客户端允许您显示查询的踪迹:

    Kevin 报告的
  1. ($collection//foo | $collection//bar)[fn:contains(., $string)] 没有使用索引,产生跟踪:

    $collection/descendant::{}foo union
        $collection/descendant::{}bar
            [contains(self::node(), $string)]
    
  2. Kevin 报告的
  3. $collection//foo[fn:contains(., $string)] | $collection//bar[fn:contains(., $string)] 确实正确使用了索引,产生了跟踪:

    $collection
    (# exist:optimize-field #)
    (# exist:optimize #) {
        descendant::{}foo[range:contains(self::node(), $string)]
    }
    union $collection
    (# exist:optimize-field #)
    (# exist:optimize #) {
        descendant::{}bar[range:contains(self::node(), $string)]
    }
    

在 (2) 中我们可以清楚地看到优化是由 XQuery pragma 指示的。这些意味着检测到合适的索引并将在评估期间使用。

相比之下,在 (1) 中我们看到 eXist 未能正确检测到可以进行优化的可用索引。

可悲的是,似乎 eXist-db 可能为这些使用了错误的轴,即 descendant 而不是 descendant-or-self。

我已经为 eXist-db 打开了一个 GitHub 问题,它报告了这个问题 - https://github.com/eXist-db/exist/issues/2363

虽然我对 eXist 还是个新手,但在我看来有两个想法被混为一谈。

告诉 Lucene 索引某些东西与在查询 Xpath 上放置谓词不同。 Lucene 索引的 qname 并不(我相信)意味着给定元素不会受到查询。这只是Lucene索引什么以加快搜索速度的问题?您发现使用谓词可以提高速度这一事实表明这是真的。

当我进行搜索时,无论我告诉 Lucene 如何建立索引,我仍然限制要查询的元素。我个人并不认为这是一种 hack - 只是减少了 'search pool'。我不使用 local-name() 作为谓词。相反,我会使用元素本身。我不确定使用 local-name() 与此相比是否有成本:

let $coll := collection(xmldb:encode-uri($collection))

let target := $coll//p | $coll//h1 | $coll//h2 | $coll//h3 | $coll//li

根据您的 XML 层次结构,您可能会发现通过使用 collection(xmldb:encode-uri($collection))//some-element

减少节点池来提高速度

以上可能会使用然后使用Lucene索引更有效?值得一试。

此外,虽然我不知道你的XML是什么层级,但你也可以显式告诉Lucene忽略某些元素(但这通常是对于嵌套在您正在索引的那些元素中的那些元素):

 <ignore qname="div"/>

注意:我使用的是 eXist 4.4

已添加:尝试使用 range index 除了 Lucene。此外,我在 qnames 中没有看到名称-space(另外您有两个名称 space 正在运行,我在 xmlns:xs 中添加了第三个范围索引)。

此示例假定(从上面链接的 eXist 文档复制)名称 space 的 mods 用于演示。但如果 xml 集合中有特定名称 space,则必须将其附加到每个 qname

<collection xmlns="http://exist-db.org/collection-config/1.0">
  <index xmlns:mods="http://www.loc.gov/mods/v3" 
        xmlns:xlink="http://www.w3.org/1999/xlink"  
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <fulltext default="none" attributes="false"/>
    <range>
       <create qname="mods:p" type="xs:string"/>
       <create qname="mods:li" type="xs:string"/>
       <create qname="mods:h1" type="xs:string"/>
       <create qname="mods:h2" type="xs:string"/>
       <create qname="mods:h3" type="xs:string"/>
    </range>
    <lucene>
        <analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
        <text qname="mods:p"/>
        <text qname="mods:li"/>
        <text qname="mods:h1"/>
        <text qname="mods:h2"/>
        <text qname="mods:h3"/>
        <ignore qname="mods:div"/>
    </lucene>
  </index>
</collection>

删除未使用的名称space 声明。

eXist-db 2.2 于 2014 年发布,因此跨两个主要版本的长跳升级往往不是直截了当的。

您的代码似乎仍在使用遗留范围索引,正如 monex 所报告的那样,这可能是导致您不想要的结果的原因。

此索引已标记为已弃用,将使用新的范围索引代替。

如果您无法提供 MWE,则需要找出您的哪些查询调用了旧范围索引并将其更改为新的,或者完全禁用旧范围索引。

我不建议使用例如和旧的 monex 里面有一个新的存在,并在被要求将默认应用程序升级到更新版本时说 yes。您仍然可以 运行 没有任何默认应用程序的生产站点。

无法从您的示例中看出 for $hit in (collection(xmldb:encode-uri($collection))//*)[ft:query(., 如何在您的应用中回避对旧范围索引的调用,但它应该会给您一些线索。我的猜测是,如果您摆脱这些调用,您将看到 for $hit in collection(xmldb:encode-uri($collection))//*[ft:query(., 以相同的方式行动和工作。