HardТеория3 min

Cosmos DB для разработчиков

Partition keys, уровни согласованности, Change Feed, SDK для C#

Cosmos DB -- глобально распределенная NoSQL-база данных Azure с гарантией задержки менее 10 мс. На AZ-204 особое внимание уделяется partition keys, consistency levels и работе с SDK.

Иерархия ресурсов

Account -> Database -> Container -> Items
  • Account -- верхний уровень, определяет API, регионы, consistency по умолчанию
  • Database -- логическая группировка контейнеров, shared throughput
  • Container -- аналог таблицы, определяет partition key
  • Items -- документы JSON (до 2 MB каждый)

Выбор Partition Key

Правильный выбор partition key -- самое важное архитектурное решение при работе с Cosmos DB. Он определяет, как данные распределяются между физическими разделами.

Хороший partition key:

  • Имеет много уникальных значений (высокая кардинальность)
  • Равномерно распределяет данные и запросы
  • Часто используется в WHERE-условиях запросов

Примеры для разных сценариев:

  • Заказы: /customerId -- запросы обычно фильтруются по клиенту
  • Логи: /date или /tenantId -- равномерное распределение по дням или арендаторам
  • IoT данные: /deviceId -- каждое устройство генерирует свои данные

Плохой partition key:

  • Значение с низкой кардинальностью (/country -- всего ~200 значений)
  • Монотонно растущие значения (/timestamp -- все записи идут в один раздел)
  • Значение, которое не используется в запросах

Уровни согласованности (Consistency Levels)

Cosmos DB предлагает 5 уровней, от строгого к слабому:

Уровень Описание Задержка Доступность RU стоимость
Strong Линеаризуемое чтение Высокая Ниже Высокая
Bounded Staleness Чтение отстает не более чем на K версий или T секунд Средняя Средняя Средняя
Session Согласованность в рамках клиентской сессии Низкая Высокая Низкая
Consistent Prefix Чтения никогда не видят неупорядоченные записи Низкая Высокая Низкая
Eventual Нет гарантий порядка, максимальная производительность Минимальная Максимальная Минимальная

Session -- рекомендуемый уровень по умолчанию. Он гарантирует: ваша сессия видит свои записи (read-your-own-writes), но другие сессии могут видеть данные с небольшой задержкой.

Работа с Cosmos DB SDK в C#

Подключение

using Microsoft.Azure.Cosmos;
using Azure.Identity;

// Рекомендуемый способ -- через Managed Identity
var client = new CosmosClient(
    "https://myaccount.documents.azure.com:443/",
    new DefaultAzureCredential(),
    new CosmosClientOptions
    {
        SerializerOptions = new CosmosSerializationOptions
        {
            PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
        },
        ConnectionMode = ConnectionMode.Direct,
        MaxRetryAttemptsOnRateLimitedRequests = 5
    });

var database = client.GetDatabase("OrderDB");
var container = database.GetContainer("Orders");

CRUD-операции

// Create
var order = new Order
{
    Id = Guid.NewGuid().ToString(),
    CustomerId = "cust-001",
    Total = 150.00m,
    Status = "Created"
};
await container.CreateItemAsync(order, new PartitionKey(order.CustomerId));

// Read (Point Read -- самая быстрая операция, 1 RU)
var response = await container.ReadItemAsync<Order>(
    order.Id,
    new PartitionKey(order.CustomerId));
var readOrder = response.Resource;
// response.RequestCharge -- стоимость операции в RU

// Replace
readOrder.Status = "Confirmed";
await container.ReplaceItemAsync(readOrder, readOrder.Id,
    new PartitionKey(readOrder.CustomerId));

// Delete
await container.DeleteItemAsync<Order>(order.Id,
    new PartitionKey(order.CustomerId));

// Upsert (создать или обновить)
await container.UpsertItemAsync(order, new PartitionKey(order.CustomerId));

Запросы

// Параметризованный запрос
var query = new QueryDefinition(
    "SELECT * FROM c WHERE c.customerId = @cid AND c.status = @status")
    .WithParameter("@cid", "cust-001")
    .WithParameter("@status", "Created");

var iterator = container.GetItemQueryIterator<Order>(query);
while (iterator.HasMoreResults)
{
    var results = await iterator.ReadNextAsync();
    Console.WriteLine($"RU charge: {results.RequestCharge}");
    foreach (var item in results)
    {
        Console.WriteLine($"Order: {item.Id}, Total: {item.Total}");
    }
}

Cross-partition vs Single-partition запросы

Если запрос включает partition key в WHERE -- это single-partition запрос (дешевый, быстрый). Если нет -- cross-partition запрос (сканирует все разделы, дорогой).

// Single-partition (быстрый): partition key в запросе
var query = new QueryDefinition("SELECT * FROM c WHERE c.customerId = @cid")
    .WithParameter("@cid", "cust-001");

// Cross-partition (дорогой): нет partition key
var query = new QueryDefinition("SELECT * FROM c WHERE c.total > 100");

Change Feed

Change Feed -- поток изменений в контейнере Cosmos DB. Позволяет реагировать на каждое создание или обновление документа.

Сценарии использования:

  • Синхронизация данных между базами (CQRS read model)
  • Event-driven архитектура
  • Real-time аналитика
  • Инвалидация кеша
// Change Feed Processor
var changeFeedProcessor = container
    .GetChangeFeedProcessorBuilder<Order>("orderProcessor", HandleChanges)
    .WithInstanceName("instance-1")
    .WithLeaseContainer(leaseContainer) // Separate container for tracking
    .Build();

await changeFeedProcessor.StartAsync();

// Handler
static async Task HandleChanges(
    IReadOnlyCollection<Order> changes,
    CancellationToken ct)
{
    foreach (var order in changes)
    {
        Console.WriteLine($"Changed order: {order.Id}, Status: {order.Status}");
        // Update read model, invalidate cache, send notification...
    }
}

Change Feed также работает как триггер Azure Functions:

[Function("SyncOrderViews")]
public async Task Run(
    [CosmosDBTrigger(
        databaseName: "OrderDB",
        containerName: "Orders",
        Connection = "CosmosDB",
        LeaseContainerName = "leases",
        CreateLeaseContainerIfNotExists = true)]
    IReadOnlyList<Order> changes)
{
    foreach (var doc in changes)
    {
        _logger.LogInformation("Processing change: {Id}", doc.Id);
    }
}

Request Units (RU) и оптимизация

1 RU = стоимость чтения одного документа 1 KB по ID и partition key (Point Read).

Советы по оптимизации RU:

  • Используйте Point Read вместо запросов, когда возможно
  • Всегда указывайте partition key в запросах
  • Проектируйте данные для чтения (денормализация)
  • Мониторьте response.RequestCharge для каждой операции

Проверь себя

Change Feed в Cosmos DB фиксирует:

Самая дешевая операция чтения в Cosmos DB (1 RU для документа 1 KB) -- это:

Cross-partition запрос в Cosmos DB:

Какая характеристика partition key является самой важной?

Какой уровень согласованности Cosmos DB рекомендуется по умолчанию?