Иерархические ключи секций в Azure Cosmos DB

Azure Cosmos DB распределяет данные между логическими и физическими секциями на основе ключей секций для поддержки горизонтального масштабирования. Используя иерархические ключи секций (также называемые подпартитонингом), можно настроить до трехуровневой иерархии для ключей секций для дальнейшего оптимизации распределения данных и повышения уровня масштабирования.

Если вы используете искусственные ключи сегодня, у вас есть сценарии, в которых ключи секций могут превышать 20 ГБ данных, или хотите убедиться, что документ каждого клиента сопоставляется с собственной логической секцией, подсекция может помочь. При использовании этой функции префиксы ключа логического раздела могут превышать 20 ГБ и 10 000 единиц запросов в секунду (ЕЗ/с). Запросы по префиксу эффективно направляются в подмножество секций, в которых хранятся данные.

Выберите иерархические ключи секций

Если у вас есть мультитенантные приложения и в настоящее время арендаторы изолированы по ключу секции, иерархические секции могут быть полезны для вас. Иерархические секции позволяют масштабироваться за пределы ограничения логического ключа раздела в 20 ГБ и являются хорошим решением, если вы хотите обеспечить бесконечное масштабирование документов каждого арендатора. Если ваш текущий ключ раздела или если единственный ключ раздела часто достигает 20 ГБ, иерархические разделы являются отличным выбором для вашей рабочей нагрузки.

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

При выборе каждого уровня иерархического ключа секции важно учитывать следующие общие понятия секционирования и понимать, как каждый из них может повлиять на рабочую нагрузку:

  • Для всех контейнеров каждый уровень полного пути (начиная с первого уровня) ключа иерархического разбиения должен быть:

    • Имеет высокую кардинальность. Первый, второй и третий (если применимо) ключи иерархической секции должны иметь широкий диапазон возможных значений.

      • Низкая кардинальность на первом уровне иерархического ключа разбиения ограничивает все операции записи во время приема данных только одним физическим разбиением, пока он не достигнет 50 ГБ и не разобьётся на два физических разбиения. Например, предположим, что ваш ключ первого уровня находится на TenantId , и у вас есть только пять уникальных арендаторов. Каждая из этих операций клиента ограничена только одной физической секцией, что ограничивает потребление пропускной способности только тем, что находится в этой физической секции. Это связано с тем, что иерархические разделы оптимизируют размещение всех документов с одним и тем же ключом первого уровня на одной физической части, чтобы избежать полного распределения запросов.
      • Хотя это может быть хорошо для рабочих нагрузок, где мы делаем разовую загрузку всех данных наших клиентов, и последующие операции в первую очередь ориентированы на чтение, это может быть неидеальным для рабочих нагрузок, где деловые требования заключаются во встраивании данных в сжатые сроки. Например, если у вас есть строгие бизнес-требования, позволяющие избежать задержек, максимальная пропускная способность вашей рабочей нагрузки при приеме данных теоретически равна количеству физических секций * 10k. Если ключ верхнего уровня имеет низкую кратность, число физических секций, скорее всего, 1, если нет достаточных данных для ключа уровня 1 для его распространения по нескольким секциям после разбиения, что может занять от 4 до 6 часов.
    • Равномерно распределять использование ресурсов и хранение данных по всем логическим разделам. Это распределение обеспечивает равномерное потребление и распределение единиц запроса (RU) по физическим разделам.

      • Если вы выберете ключ первого уровня, который кажется имеющим высокую кардинальность, как UserId, но на практике ваша рабочая нагрузка выполняет операции только для одного конкретного UserId, то, скорее всего, вы столкнётесь с горячим разделом, так как все операции ограничены только одним или несколькими физическими разделами.
  • Нагрузки, требующие большого объема чтения: Мы рекомендуем выбирать иерархические ключи разбиения, которые часто используются в ваших запросах.

    • Например, рабочая нагрузка, которая часто выполняет запросы для фильтрации определенных сеансов пользователей в мультитенантном приложении, может воспользоваться иерархическими ключами секций TenantIdи UserIdSessionIdв этом порядке. Запросы можно эффективно перенаправлять в нужные физические секции, включив ключ секции в предикат фильтра. Дополнительные сведения о выборе секционных ключей для рабочих нагрузок с большим объемом чтения см. в обзоре по секционированию.
  • Рабочие нагрузки, связанные с высокой частотой записи: Рекомендуется использовать высокое кардинальное значение для первого уровня иерархического ключа раздела. Высокая кратность означает, что ключ первого уровня (и последующие уровни) имеет по крайней мере тысячи уникальных значений и более уникальных значений, чем число физических секций.

    • Например, предположим, что у нас есть рабочая нагрузка, которая изолирует арендаторов по разделительному ключу и имеет несколько крупных арендаторов, обладающих более высокой интенсивностью операций записи по сравнению с другими. Сегодня Azure Cosmos DB перестает принимать данные для любого значения ключа раздела, если оно превышает 20 ГБ. В этой рабочей нагрузке корпорация Майкрософт и Contoso являются крупными клиентами, и мы ожидаем, что она растет гораздо быстрее, чем другие клиенты. Чтобы избежать риска невозможности загрузки данных для этих арендаторов, иерархические ключи секций позволяют масштабировать этих арендаторов за пределы лимита в 20 ГБ. Мы можем добавить дополнительные уровни, такие как UserId и SessionId, чтобы обеспечить более высокую масштабируемость среди арендаторов.

    • Чтобы рабочая нагрузка могла поддерживать записи для всех документов с одинаковым ключом первого уровня, рекомендуется использовать идентификатор элемента в качестве второго или третьего уровня ключа.

    • Если у вашего первого уровня нет высокой кардинальности и вы сталкиваетесь с ограничением в 20 ГБ на логический раздел по ключу раздела, мы рекомендуем использовать искусственный ключ раздела вместо иерархического ключа раздела.

Пример варианта использования

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

В реальных условиях некоторые клиенты могут увеличиваться до тысяч пользователей, а многие другие клиенты остаются небольшими и имеют несколько пользователей. Секционирование по /TenantId может привести к превышению ограничения хранилища Azure Cosmos DB 20 ГБ в одной логической секции. Секционирование по /UserId делает все запросы арендатора межсекционными. Оба подхода имеют значительные недостатки.

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

Если у рабочей нагрузки есть тенанты с примерно одинаковыми паттернами рабочей нагрузки, иерархический ключ раздела может помочь. С помощью иерархических ключей секционирования сначала можно разделять по TenantId, а затем по UserId. Если вы ожидаете, что сочетание TenantId и UserId создаст секции, превышающие 20 ГБ, вы можете даже секционировать их дальше до другого уровня, например, на SessionId. Общая глубина не может превышать три уровня. Если физическая секция превышает 50 ГБ хранилища, Azure Cosmos DB автоматически разделяет физическую секцию, чтобы примерно половина данных была на одной физической секции, а половина — в другой. Фактически подсекция означает, что одно TenantId значение может превышать 20 ГБ данных, и данные TenantId могут охватывать несколько физических секций.

Запросы, указывающие либо TenantIdили и TenantIdUserIdто, и другое, эффективно направляются только в подмножество физических секций, содержащих соответствующие данные. Указание полного пути к ключу секции или пути с префиксами для подсекций позволяет эффективно избежать полностью размноженного запроса. Например, если контейнер имел 1000 физических секций, но определенное TenantId значение было только в пяти физических секциях, запрос будет перенаправлен на меньшее количество соответствующих физических секций.

Использование идентификатора элемента в иерархии

При использовании иерархических ключей разбиения можно добавить идентификатор элемента в качестве последнего уровня в иерархии, чтобы гарантировать возможность масштабироваться сверх ограничения размера логического ключа раздела в 20 ГБ. Этот подход особенно полезен для первого уровня или для сочетания ключей первого и второго уровней.

Например, в мультитенантной рабочей нагрузке, секционированной по TenantId и UserId, если одно сочетание этих значений может превышать 20 ГБ, добавьте ключ третьего уровня с высокой кардинальностью. GUID является хорошим выбором, так как сочетание TenantId, UserId и GUID вряд ли превысит 20 ГБ, что позволяет сочетанию TenantId и UserId эффективно масштабироваться за пределы 20 ГБ.

Для получения полного руководства о том, почему идентификатор элемента является эффективным выбором ключа разбиения (высокая кардинальность, равномерное распределение и эффективные операции точечного чтения), см. раздел "Использование идентификатора элемента в качестве ключа разбиения " в обзоре разбиения.

Get started

Important

Работа с контейнерами, используюющими иерархические ключи секций, поддерживается только в следующих версиях пакета SDK. Необходимо использовать поддерживаемый пакет SDK для создания контейнеров с иерархическими ключами секции и выполнения операций создания, чтения, обновления и удаления (CRUD) или запросов к данным. Если вы хотите использовать пакет SDK или соединитель, который в настоящее время не поддерживается, отправьте запрос на наш форум сообщества.

Найдите последнюю предварительную версию каждого поддерживаемого пакета SDK:

SDK Поддерживаемые версии Ссылка на диспетчер пакетов
пакет SDK .NET версии 3 >= 3.33.0 https://www.nuget.org/packages/Microsoft.Azure.Cosmos
пакет SDK Java версии 4 >= 4.42.0 https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/cosmos/azure-cosmos
Пакет SDK версии 4 для JavaScript 4.0.0 https://www.npmjs.com/package/@azure/cosmos/
пакет SDK Python >= 4.6.0 https://pypi.org/project/azure-cosmos

Создание контейнера с помощью иерархических ключей секций

Чтобы приступить к работе, создайте новый контейнер, используя предопределенный список путей для субразделения до трех уровней вложенности.

Вы можете создать новый контейнер с помощью одного из следующих вариантов:

  • Azure portal
  • SDK
  • Шаблон Azure Resource Manager
  • Эмулятор Azure Cosmos DB

Azure portal

Самый простой способ создать контейнер и указать иерархические ключи секции — использовать портал Azure.

  1. Войдите на портал Azure.

  2. Перейдите на существующую страницу учетной записи Azure Cosmos DB для NoSQL.

  3. В меню слева выберите Data Explorer.

    Снимок экрана, на котором показана страница новой учетной записи NoSQL в Azure Cosmos DB с выделенным пунктом меню Data Explorer.

  4. В Data Explorer выберите параметр New Container.

    Скриншот опции нового контейнера в Data Explorer.

  5. Введите в разделе "Новый контейнер" для ключа раздела. В остальных полях введите любое значение, соответствующее вашему сценарию.

    Замечание

    Мы используем /TenantId здесь в качестве примера. При реализации иерархических ключей разделов в своих контейнерах можно указать любой ключ для первого уровня.

  6. Нажмите дважды Добавить иерархический ключ разбиения.

    Снимок экрана: кнопка для добавления нового иерархического ключа секции.

  7. Для второго и третьего уровней подсекционирования введите /UserId и /SessionId соответственно.

    Снимок экрана: список трех иерархических ключей разделов.

  8. Выберите ОК, чтобы создать контейнер.

SDK

При создании контейнера с помощью пакета SDK определите список путей к ключам подпартии до трех уровней глубины. При настройке свойств нового контейнера используйте список ключей подпартии.

// List of partition keys, in hierarchical order. You can have up to three levels of keys.
List<string> subpartitionKeyPaths = new List<string> { 
    "/TenantId",
    "/UserId",
    "/SessionId"
};

// Create a container properties object
ContainerProperties containerProperties = new ContainerProperties(
    id: "<container-name>",
    partitionKeyPaths: subpartitionKeyPaths
);

// Create a container that's subpartitioned by TenantId > UserId > SessionId
Container container = await database.CreateContainerIfNotExistsAsync(containerProperties, throughput: 400);

Шаблоны диспетчера ресурсов Azure

Шаблон Azure Resource Manager для подпартийного контейнера почти идентичен стандартному контейнеру. Единственное ключевое различие заключается в значении пути properties/partitionKey. Дополнительные сведения о создании шаблона Azure Resource Manager для ресурса Azure Cosmos DB см. в справочных материалах по шаблонам Azure Resource Manager для Azure Cosmos DB.

Чтобы создать подразделённый контейнер, настройте объект partitionKey с помощью значений в следующей таблице:

Путь Value
paths Список иерархических ключей разделения (максимум три уровня глубины)
kind MultiHash
version 2

Пример определения ключа секции

Например, предположим, что у вас есть иерархический ключ секции, состоящий из TenantId>UserId>SessionId. Объект partitionKey будет настроен для включения всех трех значений в paths свойство, kind значения MultiHashи version значения 2.

partitionKey: {
  paths: [
    '/TenantId'
    '/UserId'
    '/SessionId'
  ]
  kind: 'MultiHash'
  version: 2
}

Дополнительные сведения об объекте partitionKey см. в спецификации ContainerPartitionKey.

Эмулятор Azure Cosmos DB

Вы можете протестировать возможность субразделения, используя последнюю версию локального эмулятора для Azure Cosmos DB. Чтобы включить подпаривание в эмуляторе, запустите эмулятор из каталога установки с флагом /EnablePreview :

.\CosmosDB.Emulator.exe /EnablePreview

Предупреждение

В настоящее время эмулятор не поддерживает все функции иерархического ключа секции так же, как и портал. Эмулятор в настоящее время не поддерживает:

  • Использование обозревателя данных для создания контейнеров с иерархическими ключами разделов
  • Использование обозревателя данных для перехода к элементам и взаимодействию с ними с помощью иерархических ключей секций

Дополнительные сведения см. в эмуляторе Azure Cosmos DB.

Использование пакетов SDK для работы с контейнерами с иерархическими ключами секций

Если у вас есть контейнер с иерархическими ключами секционирования, используйте ранее указанные версии пакетов SDK для .NET или Java для выполнения операций и выполнения запросов в этом контейнере.

Добавление элемента в контейнер

Существует два варианта добавления нового элемента в контейнер с включенными иерархическими ключами секций:

  • Автоматическое извлечение
  • Вручную укажите путь

Автоматическое извлечение

Если передать объект с заданным значением ключа секции, пакет SDK может автоматически извлечь полный путь к ключу секции.

// Create a new item
UserSession item = new UserSession()
{
    id = "f7da01b0-090b-41d2-8416-dacae09fbb4a",
    TenantId = "Microsoft",
    UserId = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee",
    SessionId = "0000-11-0000-1111"
};

// Pass in the object, and the SDK automatically extracts the full partition key path
ItemResponse<UserSession> createResponse = await container.CreateItemAsync(item);

Вручную укажите путь

Класс PartitionKeyBuilder в пакете SDK может создать значение для ранее определенного пути к иерархическому ключу секции. Используйте этот класс, когда вы добавляете новый элемент в контейнер с включенным субразбиением.

Tip

При масштабировании производительность может улучшиться, если указать полный путь ключа раздела, даже если пакет SDK может извлечь путь из объекта.

// Create a new item object
PaymentEvent item = new PaymentEvent()
{
    id = Guid.NewGuid().ToString(),
    TenantId = "Microsoft",
    UserId = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee",
    SessionId = "0000-11-0000-1111"
};

// Specify the full partition key path when creating the item
PartitionKey partitionKey = new PartitionKeyBuilder()
            .Add(item.TenantId)
            .Add(item.UserId)
            .Add(item.SessionId)
            .Build();

// Create the item in the container
ItemResponse<PaymentEvent> createResponse = await container.CreateItemAsync(item, partitionKey);

Поиск по ключу и значению (точечная операция чтения) элемента

Поиск по ключу и значению (операции чтения точек) выполняется аналогично, как и в контейнере без подпартиций. Например, предположим, что у вас есть иерархический ключ секции, состоящий из TenantId>UserId>SessionId. Уникальный идентификатор элемента — это GUID. Она представлена в виде строки, которая служит уникальным идентификатором транзакции документа. Чтобы выполнить точное чтение одного элемента, передайте id свойство элемента и полное значение ключа раздела, включая все три компонента пути.

// Store the unique identifier
string id = "f7da01b0-090b-41d2-8416-dacae09fbb4a";

// Build the full partition key path
PartitionKey partitionKey = new PartitionKeyBuilder()
    .Add("Microsoft") //TenantId
    .Add("00aa00aa-bb11-cc22-dd33-44ee44ee44ee") //UserId
    .Add("0000-11-0000-1111") //SessionId
    .Build();

// Perform a point read
ItemResponse<UserSession> readResponse = await container.ReadItemAsync<UserSession>(
    id,
    partitionKey
);

Выполнение запроса

Код SDK, используемый для выполнения запроса в подразделенном контейнере, идентичен выполнению запроса в неделённом на подпартии контейнере.

Если запрос задает все значения ключей секций в WHERE фильтре или префиксе иерархии ключей, пакет SDK автоматически направляет запрос на соответствующие физические секции. Запросы, которые предоставляют только середину иерархии, являются межсекционными запросами.

Для запросов, нацеленных на префикс пути иерархического ключа разбиения, следует включить эти ведущие значения ключа разбиения в предложение WHERE, чтобы запросы можно было эффективно маршрутизировать. Предоставление значений только через PartitionKeyBuilder само по себе не гарантирует эффективную маршрутизацию. Запросы, опускающие значения ключа раздела из предложения WHERE, могут привести к значительно более высоким затратам на единицу запросов.

Например, рассмотрим иерархический ключ секции, состоящий из TenantId>UserId>SessionId. Компоненты фильтра запроса определяют, является ли запрос односекционным, целевым межсекционным запросом или запросом с распараллеливанием.

Query Маршрутизация
SELECT * FROM c WHERE c.TenantId = 'Microsoft' AND c.UserId = '00aa00aa-bb11-cc22-dd33-44ee44ee44ee' AND c.SessionId = '0000-11-0000-1111' Перенаправлено на одну логическую и физическую секцию , содержащую данные для указанных значений TenantId, UserIdи SessionId.
SELECT * FROM c WHERE c.TenantId = 'Microsoft' AND c.UserId = '00aa00aa-bb11-cc22-dd33-44ee44ee44ee' Перенаправляется только в целевое подмножество логических и физических секций, содержащих данные для указанных значений TenantId и UserId. Это целевой межразделочный запрос, который возвращает данные для конкретного пользователя в тенанте.
SELECT * FROM c WHERE c.TenantId = 'Microsoft' Перенаправляется только в целевое подмножество логических и физических секций, содержащих данные для указанного значения TenantId. Это целевой запрос между секциями, который возвращает данные для всех пользователей в арендаторе.
SELECT * FROM c WHERE c.UserId = '00aa00aa-bb11-cc22-dd33-44ee44ee44ee' Перенаправляется во все физические секции, что приводит к выполнению размноженного запроса между секциями.
SELECT * FROM c WHERE c.SessionId = '0000-11-0000-1111' Перенаправляется во все физические секции, что приводит к выполнению размноженного запроса между секциями.

Запрос по одному разделу в контейнере с субразделами

Ниже приведен пример выполнения запроса, который включает все уровни подсекреции, эффективно выполняя запрос с одним разделом.

// Define a single-partition query that specifies the full partition key path
QueryDefinition query = new QueryDefinition(
    "SELECT * FROM c WHERE c.TenantId = @tenant-id AND c.UserId = @user-id AND c.SessionId = @session-id")
    .WithParameter("@tenant-id", "Microsoft")
    .WithParameter("@user-id", "00aa00aa-bb11-cc22-dd33-44ee44ee44ee")
    .WithParameter("@session-id", "0000-11-0000-1111");

// Retrieve an iterator for the result set
using FeedIterator<PaymentEvent> results = container.GetItemQueryIterator<PaymentEvent>(query);

while (results.HasMoreResults)
{
    FeedResponse<UserSession> resultsPage = await resultSet.ReadNextAsync();
    foreach(UserSession result in resultsPage)
    {
        // Process result
    }
}

Целевой запрос по нескольким секциям в контейнере с подсекциями

Ниже приведен пример запроса, который содержит подмножество уровней подсекреции, эффективно делая этот запрос целевым многосекционным запросом.

// Define a targeted cross-partition query specifying prefix path[s]
QueryDefinition query = new QueryDefinition(
    "SELECT * FROM c WHERE c.TenantId = @tenant-id")
    .WithParameter("@tenant-id", "Microsoft")

// Retrieve an iterator for the result set
using FeedIterator<PaymentEvent> results = container.GetItemQueryIterator<PaymentEvent>(query);

while (results.HasMoreResults)
{
    FeedResponse<UserSession> resultsPage = await resultSet.ReadNextAsync();
    foreach(UserSession result in resultsPage)
    {
        // Process result
    }
}

Перенос существующих контейнеров на иерархические ключи разделов

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

  1. Создайте контейнер с требуемой конфигурацией иерархического ключа секции.
  2. Копирование данных с помощью заданий копирования контейнеров для автономной миграции или использование канала изменений для динамической миграции с минимальным временем простоя.
  3. Обновите приложение , чтобы указать новый контейнер.
  4. Убедитесь , что запросы правильно используют префиксы иерархического ключа.

Ограничения и известные проблемы

  • Работа с контейнерами, используюющими иерархические ключи секционирования, поддерживается только в пакете SDK .NET версии 3 в пакете SDK Java версии 4, в пакете SDK Python и в предварительной версии пакета SDK javaScript. Для создания контейнеров с иерархическими ключами секций и выполнения операций CRUD или запросов к данным необходимо использовать поддерживаемый пакет SDK. В настоящее время поддержка других пакетов SDK, включая Python, недоступна.
  • Существуют ограничения с различными соединителями Azure Cosmos DB (например, с Фабрика данных Azure).
  • Иерархические ключи секций можно указать только до трех слоев глубины.
  • Иерархические ключи секций в настоящее время можно включить только в новых контейнерах. Необходимо задать пути ключа раздела во время создания контейнера, и их нельзя изменить позже. Чтобы использовать иерархические секции в существующих контейнерах, создайте новый контейнер с набором ключей иерархических разделов и переместите данные с помощью заданий копирования контейнеров.
  • Иерархические ключи секций в настоящее время поддерживаются только для API для учетных записей NoSQL. В настоящее время API для MongoDB и Cassandra не поддерживаются.
  • Иерархические ключи разделов в настоящее время не поддерживаются в функции пользователей и разрешений. Невозможно назначить разрешение на частичный префикс пути иерархического раздела ключа. Разрешения могут быть назначены только всему пути ключа логического раздела. Например, если вы разделили по TenantId - >UserId, вы не можете назначить право, которое соответствует определенному значению TenantId. Однако вы можете назначить разрешение для ключа раздела, если указать значение как для TenantId, так и для UserId.

Дальнейшие действия