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





Mongo SDK совершенно не знает о существовании CosmosDB. Это означает, что у него нет никакой логики повторных попыток для регулируемых запросов. Это означает, что если вы хотите сохранить количество единиц запросов на уровне 400, вам придется группировать свой список и вызывать метод insertmany, используя механизм регулирования на стороне клиента.
Вы можете рассчитать это, получив размер каждого документа, умножив его на 10, что является платой за вставку за 1 КБ документа, а затем напишите фрагмент кода, который группирует документы в зависимости от размера и выполняется один раз в секунду.
Я решил это, используя логику повторных попыток для космоса 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);
}
}
Я согласен, что на данный момент это, вероятно, единственный способ справиться с этим с помощью интерфейса MongoDB. Microsoft представила своего рода библиотеку массовых операций, чтобы абстрагироваться от такой логики, но не поддерживает этот интерфейс. Проблема в том, что любая операция БД может привести к исключению скорости в зависимости от того, что еще происходит в это время. Повторная попытка вставки, получения или записи достаточно проста, но для InsertMany неприятность заключается в создании логики и дополнительных вызовов БД, чтобы понять, ЧТО записи на самом деле СДЕЛАЛИ ЭТО и какие записи необходимо повторить. Использование IsOrdered = true может немного помочь.