如果出现垃圾回收问题,Spring Batch是否可以阻止我的程序停止9400万笔交易?

时间:2019-01-08 00:00:21

标签: spring-boot mariadb spring-batch

这看起来像是与Performance optimization for processing of 115 million records for inserting into Oracle类似的问题,但是我认为这是一个不同的问题,并且另一个问题由于缺乏明确性而没有明确的答案。

我正在将包含以下变量和维度的netCDF文件加载到数据库的三个表中,以从多个数据源收集数据

Variables:
Time: 365 entries in hours since Jan 1, 1900
Latitude: 360 entries, center of 1/2 degree latitude bands
Longitude: 720 entries, center of 1/2 degree longitude bands
Precipitation: 3 Dimensional Array Time, Lat, Lon in dimensions

我正在构建的三个表是这样的:

UpdateLog:
uid    year    updateTime

Location:
lid    lat    lon

(hidden MtM table) UpdateLog_Location:
uid    lid

Precipitation:
pid    lid    uid    month    day    amount

如果您进行数学运算,则此文件(仅2017年)的位置(和隐藏表)每个条目将有大约25万个条目,而降水表将具有多达9,400万个条目。

现在,我只是在使用Spring Boot,试图读取数据并更新以Location开头的表。

当我的批处理大小为1时,数据库开始相当快地开始更新,但是随着时间的推移停滞了。当时我没有进行任何配置,所以我不确定为什么。

当我将其设置为500时,由于它减慢了每次更新的速度,所以我开始清楚地注意到这些步骤,但是它的启动速度比批处理大小1快得多。

我将其设置为250,000,它在大约3分钟内更新了前250,000个条目,而批量为1时,甚至没有72小时。但是,我开始对程序进行性能分析,并发现了一些问题。这似乎不是数据库的问题(提交所有这些条目只需要35-40秒),但是对于Java来说,这似乎是一个问题,因为垃圾回收似乎无法跟上所有旧的POJO。

现在,我一直在研究针对此问题的2种可能的解决方案。 Spring Batch,只需将CSV直接导入到MariaDB。如果可能的话,我宁愿使用前者来保持统一。但是,我注意到Spring Batch还让我为每个项目创建POJO。

Spring Batch是否可以为我解决此问题?我可以使用线程管理器修复该问题并对该操作进行多线程处理,以便一次运行多个GC吗?还是应该直接将CSV文件直接导入到MariaDB?

问题在于,即使我能在几天之内完成这个文件,我们仍会建立一个各种历史天气的数据库。将有更多文件要导入,我想建立一个可用于每个文件的可行框架。这个数据源甚至还有116年的数据!

编辑:从昨晚的运行中添加了一些指标,这些指标支持我认为问题是垃圾收集。

194880 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
1165541217 nanoseconds spent preparing 518405 JDBC statements;
60891115221 nanoseconds spent executing 518403 JDBC statements;
2167044053 nanoseconds spent executing 2 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
6042527312343 nanoseconds spent executing 259203 flushes (flushing a total of 2301027603 entities and 4602055206 collections);
5673283917906 nanoseconds spent executing 259202 partial-flushes (flushing a total of 2300518401 entities and 2300518401 collections)

如您所见,与实际工作相比,它要花费2个数量级以上的刷新内存。

3 个答案:

答案 0 :(得分:1)

4张桌子?我会用4列制作一张表,即使原始数据不是那样,

dt DATETIME  -- y/m/d:h 
lat SMALLINT
lng SMALLINT
amount ...
PRIMARY KEY (dt, lat, lng)

而且,我可能会直接在SQL中完成所有工作。

  1. LOAD DATA INFILE放入与文件匹配的任何内容。
  2. 运行一些SQL语句以转换为上面的模式。
  3. 将任何所需的二级索引添加到上表中。

(在一个应用程序中,我将小时数转换为MEDIUMINT,它只有3个字节。我需要跨多个表的超过9400万行的那种列。)

充其量,您的lid将是一个3字节的MEDIUMINT,后面是两个2字节的SMALLINTs。所增加的复杂性可能仅节省94MB。

总大小:约5GB。不错。

答案 1 :(得分:1)

  

我注意到Spring Batch也让我为每个项目创建POJO。

Spring Batch不会强制您解析数据并将其映射为POJO。您可以使用PassThroughLineMapper并以原始格式处理项目(如果需要,甚至可以使用二进制格式)。

我建议在您的用例中使用分区。

答案 2 :(得分:0)

我要感谢那些为我提供帮助的人,因为我找到了我的问题的几个答案,在这里将对它们进行概述。

问题源于一个事实,即Hibernate最终为每个POJO创建了1,000个垃圾回收作业,并且不是一个非常好的批处理系统。大批量的任何补救措施都将避免完全使用Hibernate。

我发现的第一种方法是在没有Hibernate的情况下利用Spring Boot。通过在存储库界面中创建自己的批量保存方法,我可以将其直接绑定到SQL插入查询,而无需POJO或使用休眠创建查询。这是如何执行此操作的示例:

@Query(value = "insert ignore into location (latitude, longitude) values(:latitude, :longitude)",
       nativeQuery = true)
public void bulkSave(@Param("latitude") float latitude, @Param("longitude") float longitude);

这样做可以大大减少垃圾收集的开销,从而使进程运行,而不会随着时间的推移而变慢。但是,就我的目的而言,虽然快了一个数量级,但这对于我的目的来说仍然太慢了,需要3天才能完成9400万行。

向我展示的另一种方法是使用Spring Batch批量发送查询,而不是一次发送一个。由于我不寻常的数据源,它不是一个平面文件,因此我不得不处理数据并将其一次输入到ItemReader中,以使其看起来好像直接来自文件。这也提高了速度,但是在尝试此方法之前,我发现了一种更快的方法。

我发现最快的方法是将我想要的表写到CSV文件中,然后进行压缩,然后将结果文件传输到数据库中,然后可以将其解压缩并直接导入数据库中。可以使用以下SQL命令在上表中完成此操作:

LOAD DATA
INFILE `location.csv`IGNORE
INTO TABLE Location
COLUMNS TERMINATED BY `,`
OPTIONALLY ENCLOSED BY '\"'  
LINES TERMINATED BY `\n`
(latitude, longitude)
SET id = NULL;

此过程需要15分钟来加载文件,5分钟来压缩2.2 Gbs文件,5分钟来解压缩文件,以及2-3分钟来创建文件。文件的传输将取决于您的网络功能。在30分钟加上网络传输时间之后,这是迄今为止将我需要的大量数据导入数据库的最快方法,尽管根据情况可能需要您做更多的工作。

因此,我发现了针对此问题的3种可能解决方案。第一种使用相同的框架,可以轻松理解和实施解决方案。第二个使用框架的扩展,并允许在同一时期进行更大的传输。最后一个是迄今为止最快的,如果数据量过大,它很有用,但需要您自己来构建软件。