如何在特定情况下避免 OutOfMemory 问题
How to avoid OutOfMemory issues in particular scenario
我们有 background 作业,它一次从特定的 table 获取记录,批量为 1000 条记录。
此作业以 30 分钟的间隔 运行。
现在,
这些记录有电子邮件(键)和原因(值)。
问题是我们必须根据数据仓库查找这些记录(某种过滤之类的东西——从仓库中获取最近 180 天的数据)。
因为调用数据仓库非常耗时(大约 45 分钟)。
所以,现有的场景是这样的。
我们检查磁盘上的平面文件。如果它不存在。我们调用数据仓库,获取记录(大小范围从 20 万到 25 万)
并将这些记录写入磁盘。
在后续调用中,我们仅从平面文件进行查找。
- 将整个文件加载到内存中并进行内存中搜索和过滤。
这导致了多次 OutOfMemory 问题。
所以,我们修改了这样的逻辑。
我们在 4 个等间隔 中修改了数据仓库调用,并使用 ObjectOutputStream 再次将每个结果存储在文件中,
现在,在后续调用中,我们按时间间隔将数据加载到内存中,即 0-30 天、31-60 天等。
但是,这也于事无补。专家能否建议解决此问题的理想方法是什么?高级团队中有人 建议使用 CouchDB 来存储和查询数据。但是,乍一看,如果 现有基础设施 有任何好的解决方案可用,我更愿意?如果没有那么可以考虑使用其他工具。
过滤代码截至目前。
private void filterRecords(List<Record> filterRecords) {
long start = System.currentTimeMillis();
logger.error("In filter Records - start (ms) : "+start);
Map<String, String> invalidDataSet=new HashMap<String, String>(5000);
try{
boolean isValidTempResultFile = isValidTempResultFile();
//1. fetch invalid leads data from DWHS only if existing file is 7 days older.
String[] intervals = {"0","45","90","135"};
if(!isValidTempResultFile){
logger.error("#CCBLDM isValidTempResultFile false");
for(String interval : intervals){
invalidDataSet.clear();
// This call takes approx 45 minutes to fetch the data
getInvalidLeadsFromDWHS(invalidDataSet, interval, start);
filterDataSet(invalidDataSet, filterRecords);
}
}
else{
//Set data from temporary file in interval to avoid heap space issue
logger.error("#CCBLDM isValidTempResultFile true");
intervals = new String[]{"1","2","3","4"};
for(String interval : intervals){
// Here GC won't happen at immediately causing OOM issue.
invalidDataSet.clear();
// Keeps 45 days of data in memory at a time
setInvalidDataSetFromTempFile(invalidDataSet, interval);
//2. mark current record as incorrect if it belongs to invalid email list
filterDataSet(invalidDataSet, filterRecords);
}
}
}catch(Exception filterExc){
Scheduler.log.error("Exception occurred while Filtering Records", filterExc);
}finally{
invalidDataSet.clear();
invalidDataSet = null;
}
long end = System.currentTimeMillis();
logger.error("Total time taken to filter all records ::: ["+(end-start)+"] ms.");
}
这是批处理代码的典型用例。您可以引入一个新列,例如 'isProcessed' 和 'false' 值。阅读 1-100 行(其中 id>=1 && id<=100),处理它们并将该标志标记为 true。然后阅读说下一个 100 等等。在作业结束时,再次将所有标志标记为假(重置)。但随着时间的推移,在这种自定义框架上维护和开发功能可能会变得困难。有开源替代品。
Spring批处理对于这些用例来说是一个非常好的框架,可以考虑:https://docs.spring.io/spring-batch/trunk/reference/html/spring-batch-intro.html。
还有 Easy batch: https://github.com/j-easy/easy-batch 不需要 'Spring framework' 知识
但如果它是一个庞大的数据集并且将来会继续增长,请考虑转向 'big data' 技术栈
我强烈建议稍微改变一下您的基础设施。 IIYC 您正在查找文件中的内容和地图中的内容。使用文件很痛苦,将所有内容加载到内存会导致 OOME。您可以使用内存映射文件做得更好,它允许快速和简单的访问。
有一个 Chronicle-Map 为堆外存储的数据提供 Map
接口。实际上,数据驻留在磁盘上并根据需要占用主内存。您需要制作您的键和值 Serializable
(AFAIK 您已经做过)或使用替代方法(可能更快)。
它不是数据库,它只是一个 ConcurrentMap
,这使得使用它非常简单。整个安装只是添加一行 compile "net.openhft:chronicle-map:3.14.5"
到 build.gradle
(或一些 maven 行)。
有其他选择,但 Chronicle-Map 是我尝试过的(刚开始使用它,但到目前为止一切正常)。
还有 Chronicle-Queue,以防您需要批处理,但我强烈怀疑您是否需要它,因为您受到磁盘而非主内存的限制。
我们有 background 作业,它一次从特定的 table 获取记录,批量为 1000 条记录。 此作业以 30 分钟的间隔 运行。 现在, 这些记录有电子邮件(键)和原因(值)。 问题是我们必须根据数据仓库查找这些记录(某种过滤之类的东西——从仓库中获取最近 180 天的数据)。 因为调用数据仓库非常耗时(大约 45 分钟)。 所以,现有的场景是这样的。 我们检查磁盘上的平面文件。如果它不存在。我们调用数据仓库,获取记录(大小范围从 20 万到 25 万) 并将这些记录写入磁盘。 在后续调用中,我们仅从平面文件进行查找。 - 将整个文件加载到内存中并进行内存中搜索和过滤。 这导致了多次 OutOfMemory 问题。
所以,我们修改了这样的逻辑。 我们在 4 个等间隔 中修改了数据仓库调用,并使用 ObjectOutputStream 再次将每个结果存储在文件中, 现在,在后续调用中,我们按时间间隔将数据加载到内存中,即 0-30 天、31-60 天等。 但是,这也于事无补。专家能否建议解决此问题的理想方法是什么?高级团队中有人 建议使用 CouchDB 来存储和查询数据。但是,乍一看,如果 现有基础设施 有任何好的解决方案可用,我更愿意?如果没有那么可以考虑使用其他工具。
过滤代码截至目前。
private void filterRecords(List<Record> filterRecords) {
long start = System.currentTimeMillis();
logger.error("In filter Records - start (ms) : "+start);
Map<String, String> invalidDataSet=new HashMap<String, String>(5000);
try{
boolean isValidTempResultFile = isValidTempResultFile();
//1. fetch invalid leads data from DWHS only if existing file is 7 days older.
String[] intervals = {"0","45","90","135"};
if(!isValidTempResultFile){
logger.error("#CCBLDM isValidTempResultFile false");
for(String interval : intervals){
invalidDataSet.clear();
// This call takes approx 45 minutes to fetch the data
getInvalidLeadsFromDWHS(invalidDataSet, interval, start);
filterDataSet(invalidDataSet, filterRecords);
}
}
else{
//Set data from temporary file in interval to avoid heap space issue
logger.error("#CCBLDM isValidTempResultFile true");
intervals = new String[]{"1","2","3","4"};
for(String interval : intervals){
// Here GC won't happen at immediately causing OOM issue.
invalidDataSet.clear();
// Keeps 45 days of data in memory at a time
setInvalidDataSetFromTempFile(invalidDataSet, interval);
//2. mark current record as incorrect if it belongs to invalid email list
filterDataSet(invalidDataSet, filterRecords);
}
}
}catch(Exception filterExc){
Scheduler.log.error("Exception occurred while Filtering Records", filterExc);
}finally{
invalidDataSet.clear();
invalidDataSet = null;
}
long end = System.currentTimeMillis();
logger.error("Total time taken to filter all records ::: ["+(end-start)+"] ms.");
}
这是批处理代码的典型用例。您可以引入一个新列,例如 'isProcessed' 和 'false' 值。阅读 1-100 行(其中 id>=1 && id<=100),处理它们并将该标志标记为 true。然后阅读说下一个 100 等等。在作业结束时,再次将所有标志标记为假(重置)。但随着时间的推移,在这种自定义框架上维护和开发功能可能会变得困难。有开源替代品。
Spring批处理对于这些用例来说是一个非常好的框架,可以考虑:https://docs.spring.io/spring-batch/trunk/reference/html/spring-batch-intro.html。
还有 Easy batch: https://github.com/j-easy/easy-batch 不需要 'Spring framework' 知识
但如果它是一个庞大的数据集并且将来会继续增长,请考虑转向 'big data' 技术栈
我强烈建议稍微改变一下您的基础设施。 IIYC 您正在查找文件中的内容和地图中的内容。使用文件很痛苦,将所有内容加载到内存会导致 OOME。您可以使用内存映射文件做得更好,它允许快速和简单的访问。
有一个 Chronicle-Map 为堆外存储的数据提供 Map
接口。实际上,数据驻留在磁盘上并根据需要占用主内存。您需要制作您的键和值 Serializable
(AFAIK 您已经做过)或使用替代方法(可能更快)。
它不是数据库,它只是一个 ConcurrentMap
,这使得使用它非常简单。整个安装只是添加一行 compile "net.openhft:chronicle-map:3.14.5"
到 build.gradle
(或一些 maven 行)。
有其他选择,但 Chronicle-Map 是我尝试过的(刚开始使用它,但到目前为止一切正常)。
还有 Chronicle-Queue,以防您需要批处理,但我强烈怀疑您是否需要它,因为您受到磁盘而非主内存的限制。