Cosmos CreateDocumentQuery linq 在 where 条件下的性能

Cosmos CreateDocumentQuery linq perfromance on where condition

我有一个包含大约 28000 个文档的 cosmos 集合,我在 DocumentClient 上使用 CreateDocumentQuery 并在 'T' 类型的属性上使用 where 条件。通过下面提到的不同类型的使用,我在获得结果时的时间延迟差异很大。

案例一:

    var docs2 = 
    _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(collectionUri).Where(x =>
           x.SubjectDeviceInformation.StudyId == "TestStudy"
           && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
           && x.SubjectDeviceInformation.SubjectId == "Subject3"
           && x.SubjectDeviceInformation.DeviceId == "Device1"
           && x.DaySplit == "20181112").AsEnumerable().FirstOrDefault(); 

案例 2: 这是相同的代码和条件,但这次,我使用函数变量来对 where 条件进行 decalre。

Func<HeartRateDayRecordIdentifierData, bool> searchOptions = x =>
        x.SubjectDeviceInformation.StudyId == "TestStudy"
        && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
        && x.SubjectDeviceInformation.SubjectId == "Subject3"
        && x.SubjectDeviceInformation.DeviceId == "Device1"
        && x.DaySplit == "20181112";

var docs1 = _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(collectionUri)
                        .Where(searchOptions).AsEnumerable().FirstOrDefault();

案例 1 具有内联条件,其中条件在不到一秒的时间跨度内返回结果,而在 案例 2 中结果大约需要 20-30 秒,这看起来有点奇怪。我不明白内联 where 条件和将 where 条件作为变量传递之间有什么区别。

如果有人对示例 cosmos 文档感兴趣:

{
    "id": "TestStudy_Site_._Street_21_Subject1_Device1_20181217",
    "AssemblyVersion": "1.2.3.0",
    "DataItemId": "20181217/TestStudy_Site_._Street_21_Subject1_Device1_20181217",
    "MessageType": "HeartRateDayDocumentIdentifier",
    "TimeStamp": "2018-12-14T00:00:00",
    "DaySplit": "20181217",
    "SubjectDeviceInformation": {
        "SubjectId": "Subject1",
        "DeviceId": "Device1",
        "StudyId": "TestStudy",
        "SiteId": "Site_._Street_21"
    }   
}

这是用于反序列化文档的模型: 内部 class HeartRateDayRecordIdentifierData { public 字符串 ID { 得到;放; }

    public string AssemblyVersion { get; set; }

    public string DataItemId { get; set; }

    public string MessageType { get; set; }

    public DateTime TimeStamp { get; set; }

    public string DaySplit { get; set; }

    public SubjectDeviceInformation SubjectDeviceInformation { get; set; }
}

internal class SubjectDeviceInformation
{
    public string SubjectId { get; set; }

    public string DeviceId { get; set; }

    public string StudyId { get; set; }

    public string SiteId { get; set; }
}

对我在这里做错的任何建议。

在这两种情况下,您都以非最佳方式执行此操作。

如果没有匹配项,您只需要 first 或 null。

但是您正在通过调用 AsEnumerable().FirstOrDefault().

进行同步跨分区查询调用

此外,您的 where 子句应该是 Expression<Func<HeartRateDayRecordIdentifierData, bool>> 而不是 Func

在这两种情况下发生的情况是,首先您 return CosmosDB 中的所有数据,然后 LINQ 进行内存过滤以返回数据。

您应该改为使用 while(query.HasMoreResults)query.ExecuteNextAsync() 方法来 return 您的数据。

您的查询应该是这样的:

public async Task<HeartRateDayRecordIdentifierData> GetSomethingAsync()
{
    var query = 
        _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(collectionUri).Where(x =>
               x.SubjectDeviceInformation.StudyId == "TestStudy"
               && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
               && x.SubjectDeviceInformation.SubjectId == "Subject3"
               && x.SubjectDeviceInformation.DeviceId == "Device1"
               && x.DaySplit == "20181112").AsDocumentQuery();

    while(query.HasMoreResults)
    {
        var results = await query.ExecuteNextAsync();
        if(results.Any())
            return results.First();     
    }          

    return null;
}

这样,SDK 会执行所需的最少调用量来匹配数据,并且不会查询所有可能的文档。

如果您需要任何进一步的解释,请告诉我,因为这很棘手,而且样本对这个问题没有真正帮助。

您也可以抽象所有这些,如果您使用 Cosmonaut,则只使用您的对象和 .FirstOrDefaultAsync 方法。这样你的整个代码就可以变成这样:

public async Task<HeartRateDayRecordIdentifierData> GetSomethingAsync()
{
    return await cosmosStore.Query().Where(x =>
                   x.SubjectDeviceInformation.StudyId == "TestStudy"
                   && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
                   && x.SubjectDeviceInformation.SubjectId == "Subject3"
                   && x.SubjectDeviceInformation.DeviceId == "Device1"
                   && x.DaySplit == "20181112").FirstOrDefaultAsync();
}

您可以自行选择适合您的方式。 免责声明,我是宇航员的创造者。