MarkLogic 中的并发事务搜索请求超时

Search requests timing out with concurrent transactions in MarkLogic

在此为这个未简化的用例提前致歉。

在我的一个数据加载过程中,并发请求事务用于填充 MarkLogic。

每个并发线程在较高级别执行以下操作:

1) Create a transaction via the transactions API

2) Search if a certain document exists passing in transaction ID from step 1. (could possibly search for a document being updated and locked in another concurrent transaction with another transaction ID. It is with this query that is timing out)

3) If document does not exist, create new document with transaction
ID from step 1, if it does exist, upsert new document with transaction ID from step 1.

4) Commit transaction from step 1.

这个过程发生了死锁,我们在错误日志中看到了这一点,我们对此很好,如果有任何请求陷入死锁,我们会将它们推到队列底部重试。我们 运行 遇到的一个问题是我们有 cts:search() 查询超时,导致我们的并发事务进程呈指数增长,而不是立即抛出死锁可重试错误,这正是我们所期望的。

下面是对服务器超时的示例请求:

这是一个 POST 到 /v1/search 端点,它只做一个基本的未过滤的路径范围查询,我们有一个索引,以及一个 json- 属性-值查询。

cts:search(fn:collection(), cts:and-query((cts:collection-query(\"my_collection\"), cts:path-range-query(\"/structure/id\", \"=\", \"28820425\", (\"collation=http://marklogic.com/collation/\"), 1), cts:json-property-value-query(\"@class\", \"myClass\", (\"lang=en\"), 1)), ()), (\"unfiltered\", cts:score-order(\"descending\")), xs:double(\"0\"), ())

并且我们得到以下超时错误:

{"errorResponse":{"statusCode":500, "status":"Internal Server Error", "messageCode":"INTERNAL ERROR", "message":"SVC-EXTIME:

调试时,我们确实看到 运行 此事务并发时,cts:search 中返回的文档被锁定以在其他事务中进行更新。

为什么我们的cts:search在这里返回超时错误,请求挂起是因为某些文档被锁定了吗?当在我们的测试中明确锁定文档然后搜索它们时,所有文档仍然会返回有效的搜索响应,没有错误或超时。

我们的数据集不大(3k 个文档),我们的文档更小(10-15 JSON 个字段),所以性能不是问题。

我们可以在此处设置任何选项来帮助应用服务器本身吗?或者以不同的方式构建我们的查询?任何解释在这里都会有很大帮助。再次抱歉无法提供可测试的案例,但很好奇是否有人 运行 有类似的东西。

When debugging, we do see concurrently when this transaction is being run, documents returned in the cts:search are locked for updates in other transactions. We are well aware of this possibility and are okay with it.

您可能认为您可以接受它,但您 运行 遇到了可能由它引起的性能问题,并且希望避免超时 - 所以你可能不喜欢它

当您在更新事务中执行搜索时,所有片段都将获得 read-lock。您可以让多个事务都在同一个 URI 上毫无问题地获得读锁。但是,如果其中一个事务随后决定要更新其中一个文档,则需要将其共享 read-lock 提升为独占 write-lock。发生这种情况时,所有在该 URI 上具有 read-lock 的其他事务都将重新启动。如果他们需要访问具有独占 write-lock 的 URI,那么他们将不得不等到具有 write-lock 的事务完成并放手。

因此,如果您有很多竞争事务都使用相同的条件执行搜索并试图从搜索结果中获取第一项(或第一组项),它们可能会导致彼此不断重新启动 and/or等待,这需要时间。添加更多线程以尝试执行更多操作会使情况变得更糟。

您可以使用多种策略来避免这种锁争用。

而不是 cts:search() 来搜索和检索文档,您可以使用 cts:uris(),然后在使用 fn:doc() 阅读文档之前(这将首先获得 read-lock) 在尝试 UPSERT(这会将 read-lock 提升为 write-lock)之前,您可以在 URI 上使用 xdmp:lock-for-update() 以获得独占的 write-lock 然后阅读文档fn:doc().

如果您尝试执行某种批处理,请使用 CoRB 等工具首先查询要在 [=67= 中处理 (lock-free) 的 URI 集] 事务,然后触发大量工作事务以单独处理每个 URI,其中 reads/locks 文档没有任何争用。

您还可以使用 xdmp:invoke-function()xdmp:spawn-function() 将搜索和更新工作分开,以便执行搜索 lock-free 并隔离更新工作。

一些描述锁和由锁争用引起的性能问题的资源:

如果您想完全了解问题所在,请遵循以下线索:

When debugging, we do see concurrently when this transaction is being run, documents returned in the cts:search are locked for updates in other transactions.

找出哪些交易对应于哪些文档,以及在您的代码中在哪里创建此交易(隐式或显式)。

目标是永远不会有死锁 -- 是的,它们有时是可以恢复的,但应该被视为一个需要修复的错误,而不是作为正常方法陷入困境。

通常,要避免这些问题,您必须:

不保持打开(显式或隐式)进行搜索或循环并具有可能也进行更新的任何路径的事务。找到一种方法来验证您是否应该 update/insert 通过文档 URI 而不是通过搜索或循环。

避免使用事务 REST 端点——而是实现单个 'queries'(即席或存储 xquery/js 模块)对单个文档执行单个原子更新,甚至不查看其他文档。 REST 事务的问题是服务器不知道它应该持续多长时间,因此不得不求助于服务器超时来解决 'detect' 客户端错误。例如。如果您的客户端抛出异常,则服务器不知道要中止事务,因此会长时间保持打开状态。如果服务器 (xquery/js) 中的代码是 运行,则事务 以及所有使用它执行的代码 都会中止。

将 search/update 拆分成独立的任务,它们自己的事务不会通过使用生成 API 之一(通过 REST 或其他方式)继承它们的父事务 异步调用这些,这样它们就不会通过阻塞调用者事务而导致死锁。