[英]Why is this eating memory?
我写了一个应用程序,目的是从一张大表(9000万)中读取日志,并将其处理为易于理解的统计数据,多少,多长时间等。
第一次运行耗时7.5个小时,仅需处理9000万个中的27个。 我想加快速度。 因此,我试图并行运行查询。 但是,当我运行下面的代码时,在几分钟之内,我遇到了内存不足异常而崩溃。
环境:
同步
测试:26个应用程序,1500万条日志,检索到500万条,<20mb,耗时20秒
生产:56个应用程序,9000万个日志,2700万个检索到的,<30mb,耗时7.5小时
异步
测试:26个应用程序,1500万条日志,检索到500万条,<20mb,耗时3秒
生产:56个应用程序,9000万个日志,2700万个已检索,内存异常
public void Run()
{
List<Application> apps;
//Query for apps
using (var ctx = new MyContext())
{
apps = ctx.Applications.Where(x => x.Type == "TypeIWant").ToList();
}
var tasks = new Task[apps.Count];
for (int i = 0; i < apps.Count; i++)
{
var app = apps[i];
tasks[i] = Task.Run(() => Process(app));
}
//try catch
Task.WaitAll(tasks);
}
public void Process(Application app)
{
//Query for logs for time period
using (var ctx = new MyContext())
{
var logs = ctx.Logs.Where(l => l.Id == app.Id).AsNoTracking();
foreach (var log in logs)
{
Interlocked.Increment(ref _totalLogsRead);
var l = log;
Task.Run(() => ProcessLog(l, app.Id));
}
}
}
不建议创建56个上下文吗?
在检索到一定数量的日志后,是否需要处理并重新创建上下文?
也许我误会了IQueryable的工作方式? <-我的猜测
我的理解是,它将根据需要检索日志,我想这对于循环意味着像产量吗? 还是我的问题是有56个“线程”调用数据库,并且我将2700万条日志存储在内存中?
附带问题
结果并不完全相同。 根据测试环境的结果,我希望生产仅需几分钟。 我假设增加与表中的记录数直接相关。
对于2700万行,问题是流处理之一,而不是并行执行。 您需要像使用SQL Server的SSIS或任何其他ETL工具一样处理该问题:每个处理步骤都是一个转换过程,处理其输入并将其输出发送到下一步。
通过使用单独的线程运行每个步骤来实现并行处理。 某些步骤还可以使用多个线程来处理多个输入,直至达到限制。 为每个步骤的线程数和输入缓冲区设置限制可确保您实现最大吞吐量,而不会在等待任务的情况下淹没机器。
.NET的TPL数据流恰好解决了这种情况。 它提供了从输入到输出的转换块(TransformBlock),将集合拆分为单个消息(TransformManyBlock),无需转换即可执行动作(ActionBlock),将数据成批合并(BatchBlock)等。
您还可以为每个步骤指定最大并行度,例如 您每次仅执行1个日志查询,但使用10个任务进行日志处理。
就您而言,您可以:
步骤#3可以分为许多其他步骤。 例如,如果你不需要处理所有的应用程序日志条目在一起,你可以使用一个步骤来处理各个条目。 或者,您可以先按日期对它们进行分组。
另一种选择是创建一个自定义块,以使用DbDataReader从数据库读取数据,并将每个条目立即发布到下一步,而不是等待所有行返回。 这样,您就可以在每个条目到达时对其进行处理,而不必等待接收所有条目。
如果每个应用程序日志包含许多条目,则可能会占用大量内存并节省时间
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.