gpt4 book ai didi

c# - 痛苦地缓慢 Azure 表插入和删除批处理操作

转载 作者:IT王子 更新时间:2023-10-29 04:01:27 25 4
gpt4 key购买 nike

使用 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 行。我的开发机器(3.4 Ghz 四核)上的平均时间似乎约为 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 Blog

我一定是做错了什么。有什么想法吗?

更新

我已经尝试了具有整体速度改进(和 8 个最大化逻辑处理器)的净效果的并行性,但在我的开发机器上每秒仍然只有 150 行插入。

我可以说总体上没有更好的,部署到 Azure(小实例)时可能更糟。

我增加了线程池,并按照 this advice 增加了 WebRole 的最大 HTTP 连接数。 .

我仍然觉得我缺少一些基本的东西,将我的插入/删除限制为 150 ROPS。

更新 2

在分析了我部署到 Azure 的小实例的一些诊断日志(使用 2.1 RC 存储客户端内置的新日志记录)之后,我获得了更多信息。

批量插入的第一个存储客户端日志位于 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 云服务项目,使用 VS2012.2,将 Web 角色作为单页模板(新的包含 TODO 示例)。

这是开箱即用的,没有新的 NuGet 包或任何东西。默认情况下,它使用 Storage 客户端库 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。这仍然是 Not Acceptable 。再次在这个测试中,我的 CPU(8 个逻辑处理器)几乎用尽,磁盘访问几乎空闲。

我不知道出了什么问题。

更新 5

一轮又一轮的摆弄和调整已经产生了一些改进,但我无法比 500-700(ish) 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 帐户有一些性能上限?我不知道。

在这一点上,我想我已经完成了。也许它的速度足以满足我的目的,或者我可能会走不同的道路。

结语

下面所有答案都很好!

对于我的具体问题,我已经能够在小型 Azure 实例上看到高达 2k ROPS 的速度,通常在 1k 左右。由于我需要降低成本(因此需要降低实例大小),这定义了我将能够使用表的目的。

感谢大家的帮助。

最佳答案

好的,第三个回答有魅力吗?

http://blogs.msdn.com/b/windowsazurestorage/archive/2010/11/06/how-to-get-most-out-of-windows-azure-tables.aspx

一些东西 - 存储模拟器 - 来自一位认真研究它的 friend 。

“一切都在单个数据库中的单个表中(更多分区不会影响任何事情)。每个表插入操作至少是 3 个 sql 操作。每个批次都在一个事务内。根据事务隔离级别,这些批次将受到限制并行执行的能力。

由于 sql server 的行为,串行批处理应该比单个插入更快。 (单个插入本质上是每次刷新到磁盘的小事务,而真正的事务作为一个组刷新到磁盘)。”

使用多个分区的 IE 不会影响模拟器的性能,但它会影响真正的 azure 存储。

还启用日志记录并稍微检查您的日志 - c:\users\username\appdata\local\developmentstorage

批量大小为 100 似乎提供了最佳的实际性能,关闭 naggle,关闭期待 100,加强连接限制。

还要确保您不会意外插入重复项,这会导致错误并减慢一切。

并针对真实存储进行测试。有一个相当不错的库可以为您处理大部分内容 - http://www.nuget.org/packages/WindowsAzure.StorageExtensions/ ,只需确保您实际在添加上调用 ToList ,例如在枚举之前它不会真正执行。此外,该库使用 dynamictableentity,因此序列化的性能很小,但它确实允许您使用没有 TableEntity 内容的纯 POCO 对象。

~ JT

关于c# - 痛苦地缓慢 Azure 表插入和删除批处理操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17955557/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com