我目前正在研究C#中使用CQRS和事件源的原型,并且在我对SQL数据库的预测中遇到了性能瓶颈。
我的第一个原型是使用Entity Framework 6构建的,代码优先。这个选择主要是为了开始,因为读取方将受益于LINQ。
每个(适用的)事件都由多个投影使用,这些投影可以创建或更新相应的实体。
这样的投影目前看起来像这样:
public async Task HandleAsync(ItemPlacedIntoStock @event)
{
var bookingList = new BookingList();
bookingList.Date = @event.Date;
bookingList.DeltaItemQuantity = @event.Quantity;
bookingList.IncomingItemQuantity = @event.Quantity;
bookingList.OutgoingItemQuantity = 0;
bookingList.Item = @event.Item;
bookingList.Location = @event.Location;
bookingList.Warehouse = @event.Warehouse;
using (var repository = new BookingListRepository())
{
repository.Add(bookingList);
await repository.Save();
}
}
效果不佳,很可能是因为我在DbContext.SaveChanges()
方法中调用了IRepository.Save()
。每个活动一个。
接下来我应该探索哪些选项?我不想花几天时间追逐那些可能稍微好一点的想法。
我目前看到以下选项:
我希望看到数百万个事件,因为我们计划采购大型遗留应用程序并以事件的形式迁移数据。新的预测也会经常添加,因此处理速度是一个实际问题。
基准:
更新了基准
TableAdapter
(新DataSet
和新TableAdapter
)进行单线程插入:约2.500次插入/秒。没有使用投影管道进行测试,而是单独测试TableAdapter
的ADO.NET SELECT
的单线程插入:~3000插入/秒
TableAdapter
批量插入(单个数据集,内存中10,000行):> 10.000次插入/秒(我的样本大小和窗口太小)答案 0 :(得分:2)
作为Projac的作者,我建议您查看它提供的内容,并窃取合适的内容。我专门构建它是因为LINQ / EF在读取模型/投影方面的选择很差......
答案 1 :(得分:1)
一次将一条记录保存到SQL Server总是会表现不佳。你有两个选择;
使用表变量在单个调用中将多个记录保存到存储过程
使用批量插入ADO库批量复制
中的数据除了连接处理之外,其中任何一个都不会受益于EF。
如果您的数据是简单的键值对,我也不会这样做;使用RDBMS可能不太适合。可能Mongo \ Raven或其他平面数据存储的性能会更好。
答案 2 :(得分:1)
当批量提交并改进我的整体投影引擎时,我已经看到了几个数量级的性能改进,即使使用实体框架也是如此。
这是通过使用以下技术和工具实现的:
TransformBlock
完成的,其中BoundedCapacity
大于100,最大并行度为Environment.ProcessorCount
(即4或8) 。我看到性能大幅提升,队列大小为100-200而不是10:从200-300个事件到每秒10.000个事件。这很可能意味着10的缓冲区导致了太多的欠载,因此过于频繁地承担了工作单元。ActionBlock
批量大小为1000个事件和队列大小为200的值是实验结果。这还通过独立调整每个投影的这些值来显示进一步的改进选项。当使用批量大小为10.000时,为每个事件添加新行的投影会显着减慢 - 而仅更新少数实体的其他投影会从更大的批量大小中受益。
反序列化队列大小对于良好的性能也至关重要。
所以,TL; DR:
实体框架足够快,每秒可处理多达10,000个修改 - 在并行线程上。
利用您的工作单元并避免每次更改 - 特别是在CQRS中,其中投影是唯一对数据进行任何更改的线程。
正确交错并行任务,不要盲目async
一切。