我目前正在使用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();
}
}
这个性能不太好,很可能是因为我在
IRepository.Save()
方法中调用了DbContext.SaveChanges()
。每个事件都会这样做。接下来应该探索哪些选项呢?我不想花费几天时间追逐可能只是略微改进的想法。
目前我看到以下几个选项:
- 坚持使用EF,但批量处理事件(即每X个事件进行一次新/保存上下文),只要投影落后。
- 尝试做更低层次的SQL,例如使用ADO.NET。
- 不使用SQL存储投影(即使用NoSQL)
基准测试:
- 当前解决方案(EF,在每个事件后保存)每秒处理约200个事件(每个投影)。它与活动投影数量的直接比例不成比例(即N个投影处理的事件量少于N * 200个事件/秒)。
- 当投影不保存上下文时,每秒事件数略微增加(不到两倍)
- 当投影什么也不做(单个返回语句)时,我的原型管道的处理速度为全局约30,000个事件/秒
- 通过ADO.NET
TableAdapter
进行单线程插入(每次迭代都是新的DataSet
和新的TableAdapter
):每秒约2,500次插入。没有与投影管道一起测试,而是独立测试。 - 通过ADO.NET
TableAdapter
进行单线程插入,在插入后不进行SELECT
:每秒约3,000次插入- 单线程ADO.NET
TableAdapter
批量插入10,000行(单个数据集,在内存中有10,000行):每秒超过10,000次插入(我的样本大小和窗口太小了)
- 单线程ADO.NET
async repository.Save();
这样的东西存在吗?我的意思是,如果你不需要repository.Save();
返回的内容,那么“最终”保存它对你来说应该是可行的,不是吗? - gtramontina