Статьи

Будьте уверены с Azure .NET — хранилище таблиц Azure (часть 2)

Хранение данных в службе хранения таблиц Microsoft Azure

В нашей  статье Be Sure с Azure .NET — хранилище таблиц Azure (часть 1)  мы подробно расскажем о хранилище таблиц Azure, например, работе с базами данных NoSQL, их сравнении с реляционными базами данных, разработке и создании таблицы Azure, а также обо всех доступные операции для сохранения данных. Во второй части таблицы Azure собирались охватить остальные аспекты службы хранения таблиц, такие как:

  1. Эффективные и неэффективные запросы
  2. Крысы! Мы попали в ловушку (работа с политиками повторных попыток)
  3. совпадение
  4. Безопасность
  5. Вторичные индексы (Бонус)

Запросы: эффективные и неэффективные

Теперь, когда мы спроектировали и сконструировали наши таблицы Azure и эффективно сохранили в них данные, следующим важным моментом будет то, как извлечь из них данные. Из-за характера и ограничений таблиц Azure запрос данных может быть просто неэффективным. Тем не менее, мы не подождали, пока нам не потребовалось получить данные, прежде чем мы начали думать о запросе данных, но начали с этого при разработке таблиц. Но, несмотря на шаги, которые мы предприняли, чтобы иметь возможность эффективно извлекать данные, нет никаких мер предосторожности, чтобы гарантировать, что мы не будем писать неэффективные запросы. Поэтому мы не только расскажем, как писать запросы и получать данные, но также рассмотрим, какие компоненты необходимы для написания эффективных запросов, а также как избежать написания неэффективных запросов.

Эффективные Запросы

Напомним, что в наших таблицах нет вторичного индекса, есть только кластерный индекс, составленный из ключей раздела и первичных ключей. Из-за этого наши наиболее эффективные запросы всегда будут включать ключи Partition и Row. Возможность извлечения отдельной сущности с использованием определенного ключа Partition и Row всегда будет иметь наилучшую производительность.

Единый объект по разделу и ключу строки

	
TableOperation query = TableOperation.Retrieve<Footwear>(partitionkey, rowKey);
TableResult result = table.Execute(query);
Footwear footwear = (Footwear) result.Result;

Сбор по определенному ключу раздела

Следующими наиболее эффективными запросами будут запросы с участием коллекций на основе определенного ключа раздела.

IQueryable<Footwear> query = table.CreateQuery<Footwear>().Where(f => f.PartitionKey == partitionKey);
List<Footwear> shoes = query.ToList();

Здесь вы заметите, что мы используем не  метод Retrieve,  а  CreateQuery,  который позволит вам создавать и выполнять запросы с помощью LINQ. У CreateQuery есть ограничения,  так  как он был специально оптимизирован для работы с хранилищем таблиц Azure, но вы можете просмотреть все  доступные поддерживаемые  операции LINQ .

Несколько менее эффективные запросы будут включать диапазоны ключей строк или диапазонов ключей разделов и / или строк.

IQueryable<Footwear> query =
table.CreateQuery<Footwear>()
.Where(f => f.PartitionKey.Equals(startPartitionKey) || f.PartitionKey.Equals(endPartitionKey));
List<Footwear> shoes = query.ToList();

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

Неэффективные Запросы

(Короткий рекламный перерыв)

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

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

Поэтому при большой нагрузке Azure может разделять разделы данных на свой отдельный сервер разделов. Из-за этого, когда ваши запросы охватывают несколько разделов данных, запрос может легко пересечь несколько границ. Эти границы могут включать сам раздел данных, а также серверы разделов. У каждого есть долговые обязательства, которые могут возникнуть. Вкратце, мы поговорим подробнее о том, как обрабатывать размеры разделов, а также о некоторых требованиях к запросам, которые пересекают разделы сервера, таких как использование маркеров продолжения.

Запрашивать все вещи

Для всех наших запросов до настоящего времени требовались либо ключ раздела, либо ключ строки, либо оба. Но Partition и Row Key — обычно не единственные два свойства в табличном объекте. Как только вы начнете писать запросы, использующие свойства сущностей, вы перейдете в область неэффективных запросов.

<pre>IQueryable<Footwear> query = table
.CreateQuery<Footwear>()
.Where(f => f.Gender == "Male"&& (f.Size > 4 && f.Size < 7));
IEnumerable<Footwear> shoes = query.ToList();

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

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

Жетоны продолжения

Токены продолжения — это более или менее закладки для запросов. Они позволяют получать запросы с того места, где они остановились. Существует ряд сценариев, в которых хранилище таблиц Azure требует использования маркеров продолжения для получения всех доступных результатов, которые вы запрашиваете, например:

  • Запрос превысил максимальный результат возврата 1000 объектов
  • Выполнение запроса превысило 5 секунд
  • Запрос перешел границу раздела

Из-за этих ограничений вы можете видеть, как размер раздела может легко повлиять на производительность. Показано, что слишком большое количество разделов напрямую влияет на возможность масштабирования, а слишком маленькое может легко повлиять на производительность. Запрос, имеющий результат 5 сущностей, каждая из которых имеет собственный раздел, может заставить вас обработать 4 маркера продолжения, чтобы полностью получить все результаты. Итак, как нам обращаться с токеном продолжения?

Хорошая новость заключается в том, что если вы не хотите, то Storage Client SDK обрабатывает маркеры продолжения для вас. Однако существуют случаи, когда вам нужно более детально подходить к вашим запросам, как в случае, когда вы хотите убедиться, что вы не отбрасываете подавляющее количество сущностей или, возможно, хотите реализовать разбиение на страницы. Ниже приведен пример использования  токенов продолжения :

TableQuery<Footwear> query = table.CreateQuery<Footwear>().Where(f => f.PartitionKey == partitionKey).AsTableQuery();
TableContinuationToken token = null;
List<Footwear> shoes = newList<Footwear>();
do
{
  TableQuerySegment<Footwear> queryResult = query.ExecuteSegmented(token);
  token = queryResult.ContinuationToken;
  shoes.AddRange(queryResult.Results);
} while(token != null);

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

Крысы! Мы попали в ловушку (работа с политиками повторных попыток)

Является ли это тем, что интернет-хомяки спят или какой-то другой snafu, вы можете быть уверены, что будут ситуации, когда наши попытки не удаются. К счастью для нас, мы можем определить  Политику Повтора,  которая обеспечивает средство для повторения неудачной операции при необходимости. Это тема, которую мы не затронули в первой статье «Будьте уверены с Azure» в  хранилище BLOB-объектов,  но применимы ко всем службам хранилища. Доступно несколько готовых политик, а также возможность создавать собственные политики повторов. Доступны 3 политики:

  • линейный
  • экспоненциальный
  • Никто

Мы можем установить  DefaultRequestOptions  для нашего  CloudTableClient,  который позволяет нам указывать такие параметры, как наша политика повторных попыток. В дополнение к настройке политики повторных попыток в приведенном ниже примере мы также устанавливаем  MaximumExecutionTime просто для того, чтобы указать на это. Правила  повторных попыток позволяют указать время  задержки Delta между повторными попытками, а также количество максимальных попыток:

TableClient.DefaultRequestOptions = newTableRequestOptions
{
  RetryPolicy = newExponentialRetry(TimeSpan.FromSeconds(10), 5),
  MaximumExecutionTime = TimeSpan.FromSeconds(10)
};

Политика экспоненциальных повторов заставит период времени между попытками экспоненциально расти таким образом, что в приведенном выше примере будет запущена первая повторная попытка через 5 секунд, 10 секунд между следующей повторной попыткой, затем 20 секунд и т. Д. До максимального числа попыток было достигнуто. Это определяется как первый параметр конструктора  deltaBackoff. Кроме того, мы можем указать максимальное количество повторов.

Линейные  повторы работают таким образом, что интервал между попытками остается согласованным на основе указанного  времени deltaBackoffНи один  не политика , а просто возвращает  значение False  для  ShouldRetry ()  метод , когда проверяется CloudTableClient.

Итак, как мы узнаем, когда произошла повторная попытка?

Часто задаваемый вопрос: как узнать, произошла ли операция и была ли предпринята попытка повторной попытки? Есть несколько доступных вариантов. Одним из них является использование Microsoft  Application Transient Fault Application Block,  легко доступного через  Nuget,  и привязка к его обработке событий и встроенная способность узнавать о переходных сбоях при их возникновении.

RetryPolicy<StorageTransientErrorDetectionStrategy> retryPolicy = newRetryPolicy<StorageTransientErrorDetectionStrategy>(newIncremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3)));
retryPolicy.Retrying += (obj, eventArgs) => System.Console.WriteLine("Retrying");
TableClient.DefaultRequestOptions = newTableRequestOptions
{
  RetryPolicy = newNoRetry(),
};
TableOperation query = TableOperation.Retrieve<Footwear>(partitionKey, rowKey);
TableResult result = retrypolicy.ExecuteAction(() => table.Execute(query));
Footwear shoe = (Footwear) result.Result;

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

Доступные текущие политики повторных попыток не повторяют для HTTP-кодов состояния 4xx, 306, 501, 505. Поэтому, если вы пишете свою собственную настраиваемую политику повторных попыток, вы также захотите ее обработать.

совпадение

В последней статье о хранилище BLOB-объектов я описал различные механизмы управления параллелизмом — оптимистический и пессимистичный параллелизм. Если у вас не было возможности или вы не знакомы с этими различными элементами управления,  переверните  и прочитайте, прежде чем двигаться дальше. Хорошим товаром с таблицами Azure является то, что они используют оптимистичный параллелизм в качестве механизма по умолчанию. Кроме того, параллелизм находится на уровне объекта, а не на уровне таблицы. Это одно из различий между параллелизмом хранилища BLOB-объектов, который предоставляется как на уровне BLOB-объекта, так и на уровне контейнера.

ETag  является свойством , что Azure Tables использовать для проведения оптимистического параллелизма. Когда объект извлекается, предоставляется  свойство ETag . Когда вы сохраняете обновление сущности,  ETag  является обязательным свойством, которое необходимо установить. Служба выполнит сравнение, чтобы убедиться, что оно соответствует  ETag  текущего объекта в таблице, чтобы обновление было успешным. Если нет, будет возвращен код состояния HTTP 412.

Однако, если вам необходимо принудительно обновить обновление, вы можете назначить подстановочный знак «*»  свойству ETag  для принудительного обновления. Что касается сервиса Table Storage, не все запросы требуют  ETag.  Storage SDK отражает то, что требуется API хранилища Azure, и при попытке выполнить   операцию замены  или  объединения таблиц клиент предупредит вас, если вы не предоставите  ETag  или если он не подстановочный знак «*» перед отправкой. Но при попытке к  InsertOrMerge или  InsertOrReplace , то  ETag  не требуется , так как услуга не требует.

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

Если вы реализуете какой-либо публичный API любого типа, подходите к использованию  операций InsertOrReplace  и InsertOrMerge  с осторожностью, так как вы по умолчанию будете использовать сценарий последней записи-выигрыша, который может быть нежелателен.

Безопасность

Подписи общего доступа

Параметры безопасности Microsoft AzureЧто касается безопасности в отношении служб хранилища Azure, то существует один элемент управления безопасностью, который используется всеми службами хранилища. Этот контроль безопасности называется Shared Access Signatures (SAS). Подписи общего доступа предоставляют вам возможность указать, какие разрешения на какие конкретные ресурсы хранения может иметь потребитель.

В качестве переподготовки для тех, кто не прочитал все сложные детали, которые были найдены  в предыдущей статье о хранилище BLOB-объектов , подпись общего доступа представляет собой HMAC SHA-256 хэш, состоящий из ряда параметров строки запроса, которые определяют детали, такие как конкретные ресурсы время истечения предоставленного доступа и предоставленных разрешений, и это лишь некоторые из них.

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

Специальная подпись общего доступа

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

Чтобы увидеть, как это можно использовать, в следующем примере демонстрируется сценарий внешнего клиента, который может запросить подпись общего доступа из вашего API. Этот SAS может затем использоваться для получения доступа к объектам хранения таблиц, для которых ему был предоставлен доступ:

//generate Shared Access Policy
SharedAccessTablePolicy policy = newSharedAccessTablePolicy
{
    Permissions = SharedAccessTablePermissions.Add | SharedAccessTablePermissions.Update |
  SharedAccessTablePermissions.Query,
  SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1),
};
//generate Shared Access Signature using the policy
stringsas = table.GetSharedAccessSignature(
    policy,
    null,
    "Athletics",
    null,
    "Running",
    null
);

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

StorageCredentials creds = newStorageCredentials(sas);

//endpoint created in this fashion to make clear what it represents
stringendpoint = string.Format("http://{0}.table.core.windows.net", accountName);
CloudTableClient tableClient = newCloudTableClient(newUri(endpoint), creds);
CloudTable table = tableClient.GetTableReference("footwear");

Отсюда мы можем создавать и выполнять  операции TableOperation  в соответствии с разрешениями. При попытке работать за пределами разрешений, предоставляемых подписью общего доступа, служба будет работать так, как будто объект не может быть найден. Это очевидно при попытке запроса сущностей и службы, возвращающей ноль с кодом состояния HTTP 404 (в  TableResult) . В сочетании, при попытке выполнить какое-либо обновление или вставку без разрешения, будет  вызвано исключение StorageException  с кодом состояния HTTP 404.

Сигнатуры управляемого общего доступа

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

Первой операцией будет создание и хранение политики хранения в нашей учетной записи хранения. Мы можем сделать это, сначала создав политику и вызвав  SetPermissions  в  CloudTableClient. Ссылка на нашу подпись общего доступа будет идентификатором, который мы назначаем политике. В этом примере мы назвали его «tablepolicy1»:

TablePermissions permissions = newTablePermissions();
permissions.SharedAccessPolicies.Add("tablepolicy1", newSharedAccessTablePolicy
{
  SharedAccessExpiryTime = DateTime.UtcNow.AddDays(2),
  Permissions = SharedAccessTablePermissions.Add | SharedAccessTablePermissions.Query
});
table.SetPermissions(permissions);

Отсюда и дело, как обычно, с небольшим отличием в указании идентификатора политики сохраненного доступа при создании подписи общего доступа:

//generate Shared Access Signature using the policy identifier
stringsas = table.GetSharedAccessSignature(
    null,
    "tablepolicy1",
    "Athletics",
    null,
    "Running",
    null
);

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

StorageCredentials creds = newStorageCredentials(sas);
//endpoint created in this fashion to make clear what it represents
stringendpoint = string.Format("http://{0}.table.core.windows.net", accountName);
CloudTableClient tableClient = newCloudTableClient(newUri(endpoint), creds);
CloudTable table = tableClient.GetTableReference("footwear");

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

Вторичные индексы (Бонус)

Я знаю, что вы думаете: «Хранилище таблиц Azure не поддерживает вторичные индексы!» и это правильно. Но я подумал, что для бонусного раздела я бы упомянул, что пользователи базы данных NoSQL, которые не поддерживают вторичные индексы, не полностью отказались от идеи найти способ оптимизировать запросы, не основанные на первичных ключах.

Мы уже рассматривали один из способов в  части 1  «Денормализация», когда обсуждали дублирование сущностей с разными ключами строк. Преимущество этого заключается в предоставлении типа вторичного индекса, но оно также сопряжено с большими накладными расходами на обслуживание и в зависимости от размера ваших данных оказывает значительное влияние на общий размер базы данных. Однако, если вы заинтересованы в исследовании других подходов к созданию вторичных индексов, вы можете прочитать  здесь о том, как.

Вывод

Нам удалось многое рассказать во второй части, посвященной хранилищу таблиц Azure, включая написание эффективных запросов и способы предотвращения неэффективных запросов, использование политик повторов, параллелизма и безопасности. Вы можете ясно видеть, что есть чему поучиться. Но одним из приятных аспектов .NET Storage Client SDK является то, что он позволяет довольно легко начать работу. Если вы заглянули в часть 2, ознакомьтесь с  частью 1,  чтобы получить первую половину истории и узнать, как начать работу со службой хранения таблиц Microsoft Azure.

Некоторые полезные ссылки

  1. Поддерживаемые операторы запросов
  2. Повторить политику
  3. Enterprise Library 6 Обработка ошибок
  4. Параллельность в хранилище таблиц Azure
  5. Вторичные индексы