Я использую узкое место в производительности при использовании хранилища таблиц Azure. Мое желание состоит в том, чтобы использовать таблицы как своего рода кеш, поэтому длительный процесс может привести к тому, что от сотни до нескольких тысяч строк данных. Затем данные могут быть быстро запрошены ключами разделов и строк.
Запросы работают довольно быстро (очень быстро, когда используются только клавиши разделов и строк, немного медленнее, но все же приемлемы при поиске свойств для определенного соответствия).
Тем не менее, как вставка, так и удаление строк очень медленны.
Разъяснение
Я хочу пояснить, что даже вставка одной партии из 100 элементов занимает несколько секунд. Это не просто проблема с общей пропускной способностью тысяч строк. Это влияет на меня, когда я вставляю только 100.
Вот пример моего кода, чтобы сделать пакетную вставку в мою таблицу:
static async Task BatchInsert( CloudTable table, List<ITableEntity> entities )
{
int rowOffset = 0;
while ( rowOffset < entities.Count )
{
Stopwatch sw = Stopwatch.StartNew();
var batch = new TableBatchOperation();
// next batch
var rows = entities.Skip( rowOffset ).Take( 100 ).ToList();
foreach ( var row in rows )
batch.Insert( row );
// submit
await table.ExecuteBatchAsync( batch );
rowOffset += rows.Count;
Trace.TraceInformation( "Elapsed time to batch insert " + rows.Count + " rows: " + sw.Elapsed.ToString( "g" ) );
}
}
Я использую пакетные операции, и вот один образец отладочного вывода:
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Starting asynchronous request to http://127.0.0.1:10002/devstoreaccount1.
Microsoft.WindowsAzure.Storage Verbose: 4 : b08a07da-fceb-4bec-af34-3beaa340239b: StringToSign = POST..multipart/mixed; boundary=batch_6d86d34c-5e0e-4c0c-8135-f9788ae41748.Tue, 30 Jul 2013 18:48:38 GMT./devstoreaccount1/devstoreaccount1/$batch.
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Preparing to write request data.
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Writing request data.
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Waiting for response.
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Response received. Status code = 202, Request ID = , Content-MD5 = , ETag = .
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Response headers were processed successfully, proceeding with the rest of the operation.
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Processing response body.
Microsoft.WindowsAzure.Storage Information: 3 : b08a07da-fceb-4bec-af34-3beaa340239b: Operation completed successfully.
iisexpress.exe Information: 0 : Elapsed time to batch insert 100 rows: 0:00:00.9351871
Как вы можете видеть, этот пример занимает почти 1 секунду, чтобы вставить 100 строк. В среднем моя машина dev (3,4 ГГц) работает на 0,8 секунды.
Это кажется смешным.
Ниже приведен пример операции пакетного удаления:
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Starting asynchronous request to http://127.0.0.1:10002/devstoreaccount1.
Microsoft.WindowsAzure.Storage Verbose: 4 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: StringToSign = POST..multipart/mixed; boundary=batch_7e3d229f-f8ac-4aa0-8ce9-ed00cb0ba321.Tue, 30 Jul 2013 18:47:41 GMT./devstoreaccount1/devstoreaccount1/$batch.
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Preparing to write request data.
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Writing request data.
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Waiting for response.
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Response received. Status code = 202, Request ID = , Content-MD5 = , ETag = .
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Response headers were processed successfully, proceeding with the rest of the operation.
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Processing response body.
Microsoft.WindowsAzure.Storage Information: 3 : 4c271cb5-7463-44b1-b2e5-848b8fb10a93: Operation completed successfully.
iisexpress.exe Information: 0 : Elapsed time to batch delete 100 rows: 0:00:00.6524402
Постоянно более 0,5 секунд.
Я запустил это развертывание на Azure (маленький экземпляр), а также записал время в 20 минут, чтобы вставить 28000 строк.
В настоящее время я использую версию библиотеки хранилища хранилища 2.1 RC: Блог MSDN
Я должен делать что-то очень неправильно. Любые мысли?
UPDATE
Я пробовал parallelism с чистым эффектом общего улучшения скорости (и 8 максимальных логических процессоров), но все еще почти 150 строк в секунду в моей машине dev.
В целом лучше, чем я могу сказать, и, может быть, даже хуже, когда его развертывают в Azure (малый экземпляр).
Я увеличил пул потоков и увеличил максимальное количество HTTP-соединений для своего WebRole, выполнив этот совет.
Я все еще чувствую, что мне не хватает чего-то фундаментального, которое ограничивает мои вставки/удаления до 150 ROPS.
ОБНОВЛЕНИЕ 2
После анализа некоторых журналов диагностики из моего небольшого экземпляра, развернутого на Azure (с использованием нового ведения журнала, встроенного в 2.1 RC Storage Client), у меня есть немного больше информации.
Первый журнал клиента хранилища для вставки пакета находится в 635109046781264034
тиках:
caf06fca-1857-4875-9923-98979d850df3: Starting synchronous request to https://?.table.core.windows.net/.; TraceSource 'Microsoft.WindowsAzure.Storage' event
Затем почти через 3 секунды я вижу этот журнал в 635109046810104314
тиках:
caf06fca-1857-4875-9923-98979d850df3: Preparing to write request data.; TraceSource 'Microsoft.WindowsAzure.Storage' event
Затем еще несколько журналов, которые занимают комбинированную 0,15 секунды, заканчивающуюся этим типом 635109046811645418
, который завершает вставку:
caf06fca-1857-4875-9923-98979d850df3: Operation completed successfully.; TraceSource 'Microsoft.WindowsAzure.Storage' event
Я не уверен, что делать с этим, но он довольно согласован во всех журналах вставки пакета, которые я рассмотрел.
ОБНОВЛЕНИЕ 3
Вот код, используемый для пакетной вставки в параллель. В этом коде, просто для тестирования, я гарантирую, что я вставляю каждую партию 100 в уникальный раздел.
static async Task BatchInsert( CloudTable table, List<ITableEntity> entities )
{
int rowOffset = 0;
var tasks = new List<Task>();
while ( rowOffset < entities.Count )
{
// next batch
var rows = entities.Skip( rowOffset ).Take( 100 ).ToList();
rowOffset += rows.Count;
string partition = "$" + rowOffset.ToString();
var task = Task.Factory.StartNew( () =>
{
Stopwatch sw = Stopwatch.StartNew();
var batch = new TableBatchOperation();
foreach ( var row in rows )
{
row.PartitionKey = row.PartitionKey + partition;
batch.InsertOrReplace( row );
}
// submit
table.ExecuteBatch( batch );
Trace.TraceInformation( "Elapsed time to batch insert " + rows.Count + " rows: " + sw.Elapsed.ToString( "F2" ) );
} );
tasks.Add( task );
}
await Task.WhenAll( tasks );
}
Как указано выше, это помогает улучшить общее время для вставки тысяч строк, но каждая партия из 100 по-прежнему занимает несколько секунд.
ОБНОВЛЕНИЕ 4
Итак, я создал совершенно новый проект Azure Cloud Service, используя VS2012.2, с ролью Web как шаблон одной страницы (новый с образцом TODO в нем).
Это прямо из коробки, никаких новых пакетов NuGet или чего-то еще. По умолчанию используется клиентская библиотека хранилища v2, а EDM и связанные с ней библиотеки v5.2.
Я просто изменил код HomeController следующим образом (используя некоторые случайные данные для имитации столбцов, которые я хочу сохранить в реальном приложении):
public ActionResult Index( string returnUrl )
{
ViewBag.ReturnUrl = returnUrl;
Task.Factory.StartNew( () =>
{
TableTest();
} );
return View();
}
static Random random = new Random();
static double RandomDouble( double maxValue )
{
// the Random class is not thread safe!
lock ( random ) return random.NextDouble() * maxValue;
}
void TableTest()
{
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
CloudConfigurationManager.GetSetting( "CloudStorageConnectionString" ) );
// create the table client
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
// retrieve the table
CloudTable table = tableClient.GetTableReference( "test" );
// create it if it doesn't already exist
if ( table.CreateIfNotExists() )
{
// the container is new and was just created
Trace.TraceInformation( "Created table named " + "test" );
}
Stopwatch sw = Stopwatch.StartNew();
// create a bunch of objects
int count = 28000;
List<DynamicTableEntity> entities = new List<DynamicTableEntity>( count );
for ( int i = 0; i < count; i++ )
{
var row = new DynamicTableEntity()
{
PartitionKey = "filename.txt",
RowKey = string.Format( "$item{0:D10}", i ),
};
row.Properties.Add( "Name", EntityProperty.GeneratePropertyForString( i.ToString() ) );
row.Properties.Add( "Data", EntityProperty.GeneratePropertyForString( string.Format( "data{0}", i ) ) );
row.Properties.Add( "Value1", EntityProperty.GeneratePropertyForDouble( RandomDouble( 10000 ) ) );
row.Properties.Add( "Value2", EntityProperty.GeneratePropertyForDouble( RandomDouble( 10000 ) ) );
row.Properties.Add( "Value3", EntityProperty.GeneratePropertyForDouble( RandomDouble( 1000 ) ) );
row.Properties.Add( "Value4", EntityProperty.GeneratePropertyForDouble( RandomDouble( 90 ) ) );
row.Properties.Add( "Value5", EntityProperty.GeneratePropertyForDouble( RandomDouble( 180 ) ) );
row.Properties.Add( "Value6", EntityProperty.GeneratePropertyForDouble( RandomDouble( 1000 ) ) );
entities.Add( row );
}
Trace.TraceInformation( "Elapsed time to create record rows: " + sw.Elapsed.ToString() );
sw = Stopwatch.StartNew();
Trace.TraceInformation( "Inserting rows" );
// batch our inserts (100 max)
BatchInsert( table, entities ).Wait();
Trace.TraceInformation( "Successfully inserted " + entities.Count + " rows into table " + table.Name );
Trace.TraceInformation( "Elapsed time: " + sw.Elapsed.ToString() );
Trace.TraceInformation( "Done" );
}
static async Task BatchInsert( CloudTable table, List<DynamicTableEntity> entities )
{
int rowOffset = 0;
var tasks = new List<Task>();
while ( rowOffset < entities.Count )
{
// next batch
var rows = entities.Skip( rowOffset ).Take( 100 ).ToList();
rowOffset += rows.Count;
string partition = "$" + rowOffset.ToString();
var task = Task.Factory.StartNew( () =>
{
var batch = new TableBatchOperation();
foreach ( var row in rows )
{
row.PartitionKey = row.PartitionKey + partition;
batch.InsertOrReplace( row );
}
// submit
table.ExecuteBatch( batch );
Trace.TraceInformation( "Inserted batch for partition " + partition );
} );
tasks.Add( task );
}
await Task.WhenAll( tasks );
}
И это результат, который я получаю:
iisexpress.exe Information: 0 : Elapsed time to create record rows: 00:00:00.0719448
iisexpress.exe Information: 0 : Inserting rows
iisexpress.exe Information: 0 : Inserted batch for partition $100
...
iisexpress.exe Information: 0 : Successfully inserted 28000 rows into table test
iisexpress.exe Information: 0 : Elapsed time: 00:01:07.1398928
Это немного быстрее, чем в моем другом приложении, более 460 ROPS. Это все еще неприемлемо. И снова в этом тесте мой процессор (8 логических процессоров) почти максимизирован, а доступ к диску почти простаивает.
Я не понимаю, что не так.
ОБНОВЛЕНИЕ 5
Круглые и круглые шаги и настройки делают некоторые улучшения, но я просто не могу получить его намного быстрее, чем 500-700 (иш). ROPS выполняет пакетные операции InsertOrReplace (в партиях 100).
Этот тест выполняется в облаке Azure, используя небольшой экземпляр (или два). Основываясь на комментариях ниже, я смирился с тем, что локальное тестирование будет в лучшем случае медленным.
Вот несколько примеров. Каждый пример - это очень собственный PartitionKey:
Successfully inserted 904 rows into table org1; TraceSource 'w3wp.exe' event
Elapsed time: 00:00:01.3401031; TraceSource 'w3wp.exe' event
Successfully inserted 4130 rows into table org1; TraceSource 'w3wp.exe' event
Elapsed time: 00:00:07.3522871; TraceSource 'w3wp.exe' event
Successfully inserted 28020 rows into table org1; TraceSource 'w3wp.exe' event
Elapsed time: 00:00:51.9319217; TraceSource 'w3wp.exe' event
Может быть, это моя учетная запись MSDN Azure с некоторыми ограничениями производительности? Я не знаю.
На этом этапе я думаю, что я покончил с этим. Может быть, это достаточно быстро, чтобы использовать его для моих целей, или, может быть, я пойду по другому пути.
Заключение
Все ответы ниже хороши!
По моему конкретному вопросу, я смог увидеть скорость до 2 КБов на маленьком экземпляре Azure, более типично около 1 к. Поскольку мне нужно снизить затраты (и, следовательно, размеры экземпляров), это определяет, для чего я смогу использовать таблицы.
Спасибо всем за помощь.