Azure 上的 DocumentDb - 我无法在单个 运行 中获得完整的记录集?

DocumentDb on Azure - I can't get the complete recordset in a single run?

对于一个应用程序,我在 AzureDB 上创建了一个 javascript 到 运行 的存储过程,这样我可以获得一组综合汇总,然后我可以在我的屏幕上显示。

然而计数总是不对,好像我从来没有得到完整的记录集。

所以我做了一个测试。我创建了以下存储过程:

function trialRun(startDateTime, endDateTime)
{
    var context = getContext();
    var collection = context.getCollection();
    var response = context.getResponse();

    var sqlString = "select p.id, p.ClientId, p.ActionType, a.Action, a.TimeStamp "
                  + "from History p"
                  + "     join a in p.Actions "
                  + "where a.TimeStamp >= '" + startDateTime + "' "
                  + "  and a.TimeStamp <= '" + endDateTime + "' ";

    // Query documents
    var isAccepted = collection.queryDocuments(collection.getSelfLink(), sqlString,
                                               function (err, feed, options)
                                               {
                                                    if (!err)
                                                    {
                                                        response.setBody(JSON.stringify(feed));
                                                    }
                                                    else
                                                    {
                                                        throw err;
                                                    }
                                               });

    if (!isAccepted) { throw new Error("The query was not accepted by the server."); }
}

事实上,输出遗漏了 10 条记录。

我阅读了有关分页的信息等等...但到目前为止我还没有发现任何与此问题相关的内容。

这里有谁能给我指明正确的方向吗?也许是关于如何从下一个“页面”获取数据的示例代码?

先来个推荐。如果您所做的只是查询,最好使用客户端 SDK and/or documentdb-utils(它只是 Azure 提供的 SDK 的包装器)。 sproc 内的查询将不会使用辅助并限制您的整体吞吐量。来自客户端 SDK 的查询将使用辅助对象并且性能更高。另一方面,如果您将一堆行聚合成一个结果,那么存储过程可能是最好的,具体取决于。

就是说,即使您将聚合移动到存储过程中,您也会遇到同样的问题。因此,要解决此问题,您可能需要查看 documentdb-utils 中的示例之一。从 countDocuments sproc. Also, you might want to learn the pattern for writing restartable sprocs found here.

开始

我怀疑您遗漏的关键概念是,在 NoSQL 世界中,尤其是 JavaScript,您需要异步思考并编写代码,以便它可以被中断和重新启动。

假设您可以将聚合需求移动到存储过程中,那么您所写的需要进行的更改包括:

  1. 当系统停止接受请求时不要抛出错误。相反,保存当前的执行状态并包含一个指示存储过程需要重新启动的指示符。我链接到的 countDocuments 示例使用 stillQueueing。如果您使用 documentdb-utils,它会告诉它存储过程尚未完成,需要再次调用。

  2. requestOptionsqueryDocuments() 调用添加第三个参数并将其中的 pageSize 设置为 1000。1000 足够大,您不会有到数据库的大量往返,但足够小以至于您可以在系统 return 为 false 后处理它,表明它不再排队请求但在 sproc 被强制超时之前。

  3. 当页面进入时进行聚合,将结果累积到正文中,您将 return。

  4. 您不需要JSON.stringify()回复。他们的系统将负责编组普通 JavaScript 对象。

  5. 无论如何,不​​要将整个提要粘贴到您的回复中。只有你的聚合。

  6. 捕获在回调的第三个 options 参数中找到的继续标记,并将其与您的响应一起传回。

如果您真的想使用存储过程和 return 所有行而不是客户端 SDK 或仅具有聚合功能的存储过程,那么我需要警告您不要使用 documentdb-utils。如果您将整个提要粘贴到响应中,它会尝试在每次往返中来回发送它,并很快超过 sproc 调用的最大负载或 return。不过,您可以使用原始 SDK,只需确保在返回另一个调用之前删除提要内容即可。

好吧,我结合了你的答案和我在 GitHub

上找到的示例存储过程

使用这 2 个作为参考,我重新设计了我的“试用存储过程”:

function trialRun(startDateTime, endDateTime, continuationToken)
{
    var context = getContext();
    var collection = context.getCollection();
    var maxResult = 1000;   // Value from sample = 25

    var sqlString = "select p.id, p.ClientId, p.ActionType, a.Action, a.TimeStamp "
                   + "from History p"
                   + "     join a in p.Actions "
                   + "where a.TimeStamp >= '" + startDateTime + "' "
                   + "  and a.TimeStamp <= '" + endDateTime + "' ";
    var result = null;

    tryQuery(continuationToken, sqlString);

    // Helper method to check for max result and call query.
    function tryQuery(nextContinuationToken)
    {
        var responseOptions = { continuation: nextContinuationToken, pageSize : maxResult };

        // In case the server is running this script for long time/near timeout, it would return false,
        // in this case we set the response to current continuation token, 
        // and the client will run this script again starting from this continuation.
        // When the client calls this script 1st time, is passes empty continuation token.
        if (result >= maxResult || !query(responseOptions)) { setBody(nextContinuationToken); }
    }

    function query(responseOptions)
    {
        var resultSet = collection.queryDocuments(collection.getSelfLink(), sqlString, responseOptions, onReadDocuments);

        return resultSet;
    }

    function onReadDocuments(err, docFeed, responseOptions)
    {
        if (err)
        {
            throw 'Error while reading document: ' + err;
        }

        // Increament the number of documents counted so far.
        result += JSON.Stringify(docFeed);

        // If there is continuation, call query again with it, 
        // otherwise we are done, in which case set continuation to null.
        if (responseOptions.continuation)
        {
            tryQuery(responseOptions.continuation);
        }
        else
        {
            setBody(null);
        }
    }

    // Set response body: use an object the client is expecting (2 properties: result and continuationToken).
    function setBody(continuationToken)
    {
        var body = { trialRun: result, continuationToken: continuationToken };
        getContext().getResponse().setBody(body);
    }
}

现在我收到的结果集包含缺少的 10 条记录。我将以同样的方式修改我的实际程序。