Подтвердить что ты не робот

Получение нескольких строк с помощью TableBatchOperation не поддерживается?

Вот фрагмент кода, который инициализирует приложение TableBatchOperation, предназначенное для извлечения двух строк в одной партии:

 TableBatchOperation batch = new TableBatchOperation();
 batch.Add(TableOperation.Retrieve("somePartition", "rowKey1"));
 batch.Add(TableOperation.Retrieve("somePartition", "rowKey2")); 
 //second call throws an ArgumentException:
 //"A batch transaction with a retrieve operation cannot contain 
 //any other operation"

Как упоминалось, генерируется исключение, и кажется, что не поддерживается для извлечения N строк в одной партии. Для меня это очень важно, поскольку мне нужно получить около 50 строк на запрос. Эта проблема имеет такую ​​же производительность, как и стоимость. Как вы знаете, ценообразование Azure Table Storage основано на количестве транзакций, что означает, что 50 операций по извлечению в 50 раз дороже, чем одна партия операции.

Я что-то пропустил?

Боковое примечание Я использую новый Azure Storage api 2.0. Я заметил, что этот вопрос никогда не поднимался в Интернете. Возможно, это ограничение было добавлено недавно?

изменить

Я нашел здесь связанный с этим вопрос: Очень медленный запрос на хранение в Azure Table в разделе PartitionKey/RowKey List. Похоже, что использование TableQuery с "или" в rowkeys приведет к полному сканированию таблицы. Здесь действительно серьезная проблема...

4b9b3361

Ответ 1

При разработке схемы разделов ключа (PK) и Row Key (RK) в Azure Table Storage (ATS) ваше основное внимание должно быть таким, как вы собираетесь получать данные. Как вы сказали, каждый запрос, который вы используете, стоит как деньги, так и, что еще важнее, так что вам нужно вернуть все данные в один эффективный запрос. Эффективные запросы, которые вы можете запускать в ATS, имеют следующие типы:

  • Точные ПК и РК
  • Точный диапазон PK, RK
  • Диапазон PK
  • Диапазон PK, диапазон RK

Основываясь на ваших комментариях, я предполагаю, что у вас есть некоторые данные, похожие на это:

PK    RK     Data
Guid1 A      {Data:{...}, RelatedRows: [{PK:"Guid2", RK:"B"}, {PK:"Guid3", RK:"C"}]}
Guid2 B      {Data:{...}, RelatedRows: [{PK:"Guid1", RK:"A"}]
Guid3 C      {Data:{...}, RelatedRows: [{PK:"Guid1", RK:"A"}];}

и вы получили данные в Guid1, и теперь вам нужно загрузить Guid2 и Guid3. Я также предполагаю, что эти строки не имеют общего знаменателя, как и все для одного и того же пользователя. Имея это в виду, я бы создал дополнительную "таблицу индексов", которая может выглядеть так:

PK      RK      Data
Guid1-A Guid2-B {Data:{....}}
Guid1-A Guid3-C {Data:{....}}
Guid2-B Guid1-A {Data:{....}}
Guid2-B Guid1-A {Data:{....}}

Если PK является комбинированным PK и RK родителя, а RK представляет собой комбинированный PK и RK дочерней строки. Затем вы можете запустить запрос, который говорит, что возвращает все строки с PK = "Guid1-A", и вы получите все связанные данные только с одним вызовом (или двумя вызовами в целом). Самые большие накладные расходы, которые это создает, - это ваши записи, поэтому теперь, когда вы правы, вам также нужно писать строки для каждой из соответствующих строк, а также следить за тем, чтобы данные постоянно обновлялись (это может быть не проблема для вас, если это одноразовый сценарий записи).

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

Ответ 2

Попробуйте что-то вроде этого:

TableQuery<DynamicTableEntity> query = new TableQuery<DynamicTableEntity>()
                                                .Where(TableQuery.CombineFilters(
                                                    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "partition1"),
                                                    TableOperators.And,
                                                    TableQuery.CombineFilters(
                                                        TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "row1"),
                                                        TableOperators.Or,
                                                        TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "row2"))));

Ответ 3

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

Я сталкивался с тем же типом проблемы. В моем сценарии мне нужно было искать сотни элементов в одном разделе, где есть миллионы строк (представьте GUID как строку-ключ). Я проверил пару вариантов поиска 10 000 строк

  • (PK & RK)
  • (PK & RK1) || (PK и RK2) ||...
  • PK && (RK1 || RK2 ||...)

Я использовал API Async с максимальным значением 10 градусов parallelism (максимум 10 невыполненных запросов). Я также проверил несколько разных партий (10 строк, 50, 100).

Test                        Batch Size  API calls   Elapsed (sec)
(PK && RK)                  1           10000       95.76
(PK && RK1) || (PK && RK2)  10          1000        25.94
(PK && RK1) || (PK && RK2)  50          200         18.35
(PK && RK1) || (PK && RK2)  100         100         17.38
PK && (RK1 || RK2 || … )    10          1000        24.55
PK && (RK1 || RK2 || … )    50          200         14.90
PK && (RK1 || RK2 || … )    100         100         13.43

NB: все они находятся в одном разделе - только несколько ключевых строк.

Я был бы рад просто сократить количество вызовов API. Но в качестве дополнительного преимущества прошедшее время также значительно меньше, экономя на вычислительных затратах (по крайней мере, на моем конце!).

Не удивительно, что партии из 100 рядов доставили наилучшую истекшую производительность. Разумеется, есть и другие соображения производительности, особенно использование сети (например, # 1 вряд ли использует сеть, например, в то время как другие усиливают ее)

ИЗМЕНИТЬ Будьте внимательны при запросе для многих rowkeys. В запросе есть (или конечно) ограничение длины URL-адреса. Если вы превысите длину, запрос будет по-прежнему успешным, потому что служба не может сказать, что URL был усечен. В нашем случае мы ограничили общую длину запроса примерно до 2500 символов (URL-код!)

Ответ 4

Пакетные операции "Получить" не поддерживаются хранилищем таблиц Azure. Поддерживаемые операции: добавление, удаление, обновление и объединение. Вам нужно будет выполнять запросы как отдельные запросы. Для более быстрой обработки вы можете выполнять эти запросы параллельно.

Ответ 5

Лучше всего создать запрос выбора Linq/OData... который получит то, что вы ищете.

Для повышения производительности вы должны сделать один запрос на раздел и выполнить эти запросы одновременно.

Я не проверял это лично, но думаю, что это сработает.

Ответ 6

Сколько объектов у вас есть на раздел? С помощью одной операции извлечения вы можете вернуть до 1000 записей для каждого запроса. Затем вы можете выполнить фильтрацию строк в ячейке памяти и оплатить только одну операцию.

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

Ответ 7

Хорошо, поэтому операция извлечения партии, лучший сценарий - это запрос таблицы. Менее оптимальная ситуация потребует параллельных операций извлечения.

В зависимости от вашего ПК, RK-дизайна вы можете на основе списка (PK, RK) выяснить, что представляет собой самый маленький/самый эффективный набор операций извлечения/запроса, которые вам нужно выполнить. Затем вы извлекаете все эти данные параллельно и сортируете точную сторону ответа клиента.

IMAO, Microsoft предложила добавить метод Retrieve к классу TableBatchOperation, поскольку он передает семантику, не поддерживаемую API хранения таблиц.

Прямо сейчас, я не в настроении написать что-то суперэффективное, поэтому я просто оставлю это супер простое решение здесь.

var retrieveTasks = new List<Task<TableResult>>();

foreach (var item in list)
{
    retrieveTasks.Add(table.ExecuteAsync(TableOperation.Retrieve(item.pk, item.rk)));
}

var retrieveResults = new List<TableResult>();

foreach (var retrieveTask in retrieveTasks)
{
    retrieveResults.Add(await retrieveTask);
}

Этот асинхронный блок кода будет извлекать объекты в list параллельно и сохранять результат в retrieveResults, сохраняя заказ. Если у вас есть непрерывные диапазоны сущностей, которые нужно извлечь, вы можете улучшить это, используя запрошенный запрос.

Там сладкое пятно (которое вам нужно найти, проверяя это) - это то, где, вероятно, быстрее/дешевле запрашивать больше объектов, чем вам может понадобиться для определенного получения партии, а затем отбрасывать полученные вами результаты, необходимо.

Если у вас есть небольшой раздел, вам может понадобиться такой запрос:

where pk=partition1 and (rk=rk1 or rk=rk2 or rk=rk3)

Если лексикографическое (то есть порядок сортировки) велико между вашими ключами, вы можете захотеть их получить параллельно. Например, если вы храните алфавит в хранилище таблиц, выборка a и z, которые находятся далеко друг от друга, лучше всего делать с параллельными процессами извлечения при извлечении a, b и c, которые находятся близко друг к другу лучше всего делать с запросом. Извлечение a, b c и z выиграет от гибридного подхода.

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