简体   繁体   English

EF Core 在大循环中出现 ABP 性能问题

[英]EF Core with ABP performance issue in huge loops

I have a background job that processes a list of items.我有一个处理项目列表的后台作业。 However, the larger the list, the time to complete it grows exponentially.然而,列表越大,完成它的时间呈指数级增长。 Not just that, the further down the list, the app gradually eats up my server's 64gb ram and slows down the server to a crawl.不仅如此,列表越往下,应用程序逐渐耗尽我服务器的 64gb 内存并使服务器变慢。

I use Hangfire as my background job manager, and the job takes a list of id and performs multiple database actions on them.我使用 Hangfire 作为我的后台作业管理器,该作业获取一个 id 列表并对它们执行多个数据库操作。 I access the database through ef core, using the repository pattern provided by as.netboilerplate framework.我通过 ef core 访问数据库,使用 as.netboilerplate 框架提供的存储库模式。

I assume it is slow because of the growing db context, so here is what I have tried:我认为它很慢,因为数据库上下文不断增长,所以这是我尝试过的:

  • Split the list across multiple jobs.将列表拆分为多个作业。

     var splitIdList = ListExtensions.ChunkBy(idList, 100); foreach (var split in splitIdList) { await _backgroundJobManager.EnqueueAsync<MyJob, MyJobArgs>(new MyJobArgs(...)) }
  • Split the job into multiple unit of work将作业拆分为多个工作单元

    var splitIdList = ListExtensions.ChunkBy(idList, 100); foreach (var split in splitIdList) { using (var uow = UnitOfWorkManager.Begin()) { foreach(var id in split) { //create related entity.... //create barcode.... //link entity to created barcode.... } uow.Complete(); } }
  • Split the job into multiple unit of work with new transactions使用新事务将作业拆分为多个工作单元

    var splitIdList = ListExtensions.ChunkBy(idList, 100); foreach (var split in splitIdList) { using (var uow = UnitOfWorkManager.Begin(System.Transactions.TransactionScopeOption.RequiresNew)) { foreach(var id in split) { //create related entity.... //create barcode.... //link entity to created barcode.... } uow.Complete(); } }

None seems to solve the problem, any help would be appreciated, thank you.似乎没有解决问题,任何帮助将不胜感激,谢谢。

EDIT Here is what I do to each entity in the job:编辑这是我对工作中的每个实体所做的:

  1. I create another entity and link them together.我创建了另一个实体并将它们链接在一起。
  2. I create a qrcode, which is a running number, and link it to the entity我创建了一个二维码,它是一个流水号,并将其链接到实体

Operations involved are create, get, insert, and update涉及的操作有create、get、insert、update

I had the same issue.我遇到过同样的问题。 In case this happens to anyone else, here is what I found.万一其他人发生这种情况,这就是我发现的。

TLDR; TLDR;

When adding items to a collection of a parent Entity, make sure you release the Entity if those collection items are no longer needed to free memory.将项目添加到父实体的集合时,如果不再需要这些集合项目以释放 memory,请确保释放实体。

Explination解释

I have a BackgroundJob processing a file.我有一个BackgroundJob正在处理一个文件。 The File table record holds information regarding the file and a FileExceptions table logs any errors processing any single records. File 表记录保存有关文件的信息,FileExceptions 表记录处理任何单个记录的任何错误。 The job opened the File record and passed it into the record processing function.作业打开文件记录并将其传递到记录处理 function。

Record processing function would open a new transaction in the unit of work and call await uow.CompleteAsync();记录处理 function 将在工作单元中打开一个新事务并调用await uow.CompleteAsync(); at the end.在最后。 When an Exception was added to the FileExceptions, I found that the File record held all of those exceptions in memory.当 Exception 添加到 FileExceptions 时,我发现 File 记录包含 memory 中的所有这些异常。

Here is my original version's pseudo-code这是我原始版本的伪代码

public class FileProcessJob : BackgroundJob<long>, ITransientDependency {
    
    [UnitOfWork]
    public override void Execute(long fileId)
    {
        MyFile file = _repository.FirstOrDefault(fileId);
        
        //Show we are processing the file and update database
        using (var uow = _unitOfWorkManager.Begin(System.Transactions.TransactionScopeOption.RequiresNew))
        {
            file.Status = FileStatus.Processing;
            await _fileRepository.UpdateAsync(bfile);
            await uow.CompleteAsync();
        }


        //Process the file
        ProcessFile(file);


        //Show the file has been processed
        using (var uow = _unitOfWorkManager.Begin())
        {
            file.Status = FileStatus.Processed;
            await _fileRepository.UpdateAsync(file);
            await uow.CompleteAsync();
        }            
    }
    

    private void ProcessFile(MyFile file) {
        foreach(var record in fileRecords) {
            ProcessRecord(file, record);
        }
    }


    private void ProcessRecord(MyFile file, object record) {
        using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
        {
            //...Process the record...

            if(proessingError) _fileExceptionRepository.Insert(exception);
            await _fileManager.UpdateAsync(file);
            await uow.CompleteAsync(); 
        }
    }
}

Because the file object was scoped outside ProcessRecord, the file.Exceptions collection grew.因为文件 object 的范围在 ProcessRecord 之外,所以 file.Exceptions 集合变大了。 Simple solution was as follows.简单的解决方案如下。

Updated更新

public class FileProcessJob : BackgroundJob<long>, ITransientDependency {
    
    [UnitOfWork]
    public override void Execute(long fileId)
    {
        MyFile file = _repository.FirstOrDefault(fileId);
        
        //Show we are processing the file and update database
        using (var uow = _unitOfWorkManager.Begin(System.Transactions.TransactionScopeOption.RequiresNew))
        {
            file.Status = FileStatus.Processing;
            await _fileRepository.UpdateAsync(bfile);
            await uow.CompleteAsync();
        }


        //Process the file
        ProcessFile(file);


        //Show the file has been processed
        using (var uow = _unitOfWorkManager.Begin())
        {
            /* NOTE: ANY UPDATES DONE TO THIS ENTITY 
            IN ProcessFile OR ProcessRecord WILL HAVE 
            BEEN LOST.  THIS ENTITY IS EXACTLY AS IT WAS
            WHEN IT WAS LAST UPDATED ABOVE */
            file.Status = FileStatus.Processed;
            await _fileRepository.UpdateAsync(file);
            await uow.CompleteAsync();
        }            
    }
    

    private void ProcessFile(MyFile file) {
        foreach(var record in fileRecords) {
            ProcessRecord(file.Id, record);
        }
    }


    private void ProcessRecord(long fileId, object record) {
        using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
        {
            MyFile file = _repository.FirstOrDefault(fileId);

            //...Process the record...

            if(proessingError) _fileExceptionRepository.Insert(exception);
            await _fileManager.UpdateAsync(file);
            await uow.CompleteAsync(); 
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM