Cosmos CreateDocumentQuery производительность linq при условии

У меня есть коллекция космоса, содержащая около 28000 документов, и я использую CreateDocumentQuery на DocumentClient с условием where для свойств типа «T». С различными типами использования, упомянутыми ниже, я получаю очень резкую разницу во времени задержки при получении результатов.

Случай 1:

    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.

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 секунд, что кажется немного странно. Я не понимаю, в чем разница между наличием inline where condition и передачей where condition как varaible.

Если кого-то интересует образец документа космоса:

{
    "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"
    }   
}

и вот модель, используемая для десериализации документа: внутренний класс HeartRateDayRecordIdentifierData {public string id {get; установленный; }

    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; }
}

Любые предложения по поводу того, что я делаю здесь неправильно.


person Kailash Ravuri    schedule 18.12.2018    source источник


Ответы (1)


В обоих случаях вы делаете это неоптимальным образом.

Вам нужен только first или null, если совпадения нет.

Однако вы выполняете синхронный вызов запроса между разделами, вызывая AsEnumerable().FirstOrDefault().

Также ваше предложение where должно быть Expression<Func<HeartRateDayRecordIdentifierData, bool>> вместо Func.

В обоих случаях происходит следующее: сначала вы возвращаете все данные в CosmosDB, а затем LINQ выполняет фильтрацию в памяти, чтобы вернуть вам данные.

Вместо этого вам следует использовать методы while(query.HasMoreResults) и query.ExecuteNextAsync() для возврата данных.

Вот каким должен быть ваш запрос:

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, который выполняет минимально необходимое количество вызовов для сопоставления данных, не будет запрашивать все возможные документы.

Дайте мне знать, если вам понадобятся какие-либо дополнительные объяснения, потому что это довольно сложно, и образцы действительно не помогают в этом.

Вы также можете абстрагироваться от всего этого и просто использовать свои объекты и метод .FirstOrDefaultAsync, если используете Cosmonaut. Таким образом, весь ваш код может измениться на это:

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();
}

Вы можете сами выбрать, какой путь вам лучше всего подходит. Заявление об отказе от ответственности, я создатель Cosmonaut.

person Nick Chapsas    schedule 18.12.2018
comment
Спасибо за подробный анализ. Пара вещей из вашего анализа: 1. Я не думаю, что использование .AsEnumerable().FirstOrDefault() с условием where перед ним не извлекает все данные, потому что, когда я попробовал ToString (), как показано ниже _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(_uri).Where(x => x.SubjectDeviceInformation.StudyId == "TestStudy" && x.SubjectDeviceInformation.SiteId == "Site_._Street_23" && x.SubjectDeviceInformation.SubjectId == "Subject3" && x.SubjectDeviceInformation.DeviceId == "Device1" && x.DaySplit == "20181112").ToString() - person Kailash Ravuri; 21.12.2018
comment
он возвращает запрос sql, равный {"query":"SELECT * FROM root WHERE (((((root[\"SubjectDeviceInformation\"][\"StudyId\"] = \"TestStudy\") AND (root[\"SubjectDeviceInformation\"][\"SiteId\"] = \"Site_._Street_23\")) AND (root[\"SubjectDeviceInformation\"][\"SubjectId\"] = \"Subject3\")) AND (root[\"SubjectDeviceInformation\"][\"DeviceId\"] = \"Device1\")) AND (root[\"DaySplit\"] = \"20181112\")) "}, что идеально подходит для того, чтобы сделать - person Kailash Ravuri; 21.12.2018
comment
_documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(_uri).Where(x => x.SubjectDeviceInformation.StudyId == "NOR580" && x.SubjectDeviceInformation.SiteId == "Site_._Street_23" && x.SubjectDeviceInformation.SubjectId == "Subject3" && x.SubjectDeviceInformation.DeviceId == "Device1" && x.DaySplit == "20181112").ToList() Это именно то, что ему нужно. Таким образом, он не возвращает все документы из космоса и не выполняет фильтрацию из локального, я даже проверил полезную нагрузку с помощью fiddler. - person Kailash Ravuri; 21.12.2018
comment
Однако глупая вещь, которую я сделал, - это объявление переменной как Func<HeartRateDayRecordIdentifierData, bool> вместо Expression<Func<HeartRateDayRecordIdentifierData, bool>>, что так глупо с моей стороны: (. Где в этом случае Func<HeartRateDayRecordIdentifierData, bool> запрос пуст и просматривает весь список документов и фильтрует локально. Большое спасибо за указание на это. После изменения типа переменной все работает нормально (проверена полезная нагрузка для обоих сценариев с помощью скрипта). - person Kailash Ravuri; 21.12.2018