Скорость запросов CosmosDb велика с помощью insertMany

У меня есть следующий класс репозитория, вставляющий данные в базу данных CosmosDb из пакета:

public bool InsertZonierData(List<Zonier> zonierList)
{
    if (zonierList == null || !zonierList.Any())
    {
        throw new ZonierListNullOrEmptyException();
    }
    else
    {
        try
        {
            _collection.InsertMany(zonierList);
            return true;
        }
        catch (MongoBulkWriteException ex)
        {
            throw new DataBaseWritingException(ex.Message, ExceptionCodeConstants.DataBaseWritingExceptionCode);
        }
    }
}

К сожалению, имея более 30000 элементов в zonierList, для CosmosDb выдается следующее исключение:

Unhandled Exception: MongoDB.Driver.MongoCommandException: Command insert failed: Message: {"Errors":["Request rate is large"]}

Судя по документации, это вопрос, связанный с РУ/сек на Космосе. Конечно, простым способом было бы увеличить его, но это не то, что я хочу делать.

Есть ли простой и понятный способ рефакторить метод, позволяющий вставлять данные, не нарушая 400 RU/сек от CosmosDb.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
1 752
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Mongo SDK совершенно не знает о существовании CosmosDB. Это означает, что у него нет никакой логики повторных попыток для регулируемых запросов. Это означает, что если вы хотите сохранить количество единиц запросов на уровне 400, вам придется группировать свой список и вызывать метод insertmany, используя механизм регулирования на стороне клиента.

Вы можете рассчитать это, получив размер каждого документа, умножив его на 10, что является платой за вставку за 1 КБ документа, а затем напишите фрагмент кода, который группирует документы в зависимости от размера и выполняется один раз в секунду.

Я согласен, что на данный момент это, вероятно, единственный способ справиться с этим с помощью интерфейса MongoDB. Microsoft представила своего рода библиотеку массовых операций, чтобы абстрагироваться от такой логики, но не поддерживает этот интерфейс. Проблема в том, что любая операция БД может привести к исключению скорости в зависимости от того, что еще происходит в это время. Повторная попытка вставки, получения или записи достаточно проста, но для InsertMany неприятность заключается в создании логики и дополнительных вызовов БД, чтобы понять, ЧТО записи на самом деле СДЕЛАЛИ ЭТО и какие записи необходимо повторить. Использование IsOrdered = true может немного помочь.

Dan 06.06.2019 17:45

Я решил это, используя логику повторных попыток для космоса bs, используя mongo api. Вы можете применить задержку в соответствии с вашими требованиями.

public void Insert(List<BsonDocument> list)
    {
        try
        {
            var collection = this.db.GetCollection<BsonDocument>(COLLECTION_NAME);
            collection.InsertMany(list);
        } catch (MongoBulkWriteException ex)
        {
            int index = ex.WriteErrors[0].Index;
            Insert(list.GetRange(index, list.Count - index));
        }

    }

Драйвер Mongo сообщает вам, какие записи получили ошибки, а какие вообще не были обработаны. Если все ошибки (обычно одна) имеют код 16500, то ваша проблема заключается в регулировании и повторных попытках ошибок, а оставшиеся записи в безопасности. В противном случае ваши ошибки вызваны чем-то другим, и вам следует провести анализ и решить, продолжать ли повторные попытки.

Драйвер Mongo не возвращает HTTP-заголовок, когда Cosmos DB предлагает задержку перед повторной попыткой, но это не имеет большого значения. В любом случае задержка не гарантирует успеха, поскольку другие запросы, попадающие в ту же базу данных, могут израсходовать ЕЗ. Вам лучше поэкспериментировать и определить свои собственные правила повтора. Ниже приведено простое рекурсивное решение, которое продолжает повторять попытки до тех пор, пока все не будет хорошо или не будет достигнут предел повторных попыток.

    private async Task InsertManyWithRetry(IMongoCollection<BsonDocument> collection, 
        IEnumerable<BsonDocument> batch, int retries = 10, int delay = 300)
    {
        var batchArray = batch.ToArray();

        try
        {
            await collection.InsertManyAsync(batchArray);
        }
        catch (MongoBulkWriteException<BsonDocument> e)
        {
            if (retries <= 0)
                throw;

            //Check if there were any errors other than throttling.
            var realErrors = e.WriteErrors.Where(we => we.Code != 16500).ToArray();
            //Re-throw original exception for now.
            //TODO: We can make it more sophisticated by continuing with unprocessed records and collecting all errors from all retries.
            if (realErrors.Any())
                throw;

            //Take all records that had errors.
            var errors = e.WriteErrors.Select(we => batchArray[we.Index]);
            //Take all unprocessed records.
            var unprocessed = e.UnprocessedRequests
                .Where(ur => ur.ModelType == WriteModelType.InsertOne)
                .OfType<InsertOneModel<BsonDocument>>() 
                .Select(ur => ur.Document);

            var retryBatchArray = errors.Union(unprocessed).ToArray();

            _logger($"Retry {retryBatchArray.Length} records after {delay} ms");

            await Task.Delay(delay);

            await InsertManyWithRetry(collection, retryBatchArray, retries - 1, delay);
        }
    }

Другие вопросы по теме