Ignite DataStreamer中可能的内存泄漏

时间:2019-04-18 19:06:11

标签: java memory-leaks kubernetes garbage-collection ignite

我正在启用持久性的Kubernetes集群中运行Ignite。每台机器都有一个24 GB的Java堆,其中20 GB专门用于持久内存,内存限制为110 GB。我相关的JVM选项是-XX:+AlwaysPreTouch -XX:+UseG1GC -XX:+ScavengeBeforeFullGC。在每个节点上运行DataStreamers几个小时后,群集中的节点达到k8s内存限制,触发了OOM终止。运行Java NMT之后,我惊讶地发现分配给内部存储器的大量空间。

Java Heap (reserved=25165824KB, committed=25165824KB)
(mmap: reserved=25165824KB, committed=25165824KB)  

Internal (reserved=42425986KB, committed=42425986KB)
(malloc=42425954KB #614365) 
(mmap: reserved=32KB, committed=32KB) 

Kubernetes指标证实了这一点:

enter image description here

“点燃缓存”是内核页面缓存。最后一个面板“堆+持久+缓冲区”是引燃指标HeapMemoryUsed + PhysicalMemorySize + CheckpointBufferSize的总和。

我知道这不是数据积累的结果,因为DataStreamer在读取每个文件后(最大约250MB最大)会刷新它们,并且没有节点一次读取4个以上的文件。在排除了其他问题之后,我尝试设置-XX:MaxDirectMemorySize=10G,并调用手动GC,但是除了定期关闭所有pod并重新启动之外,似乎没有任何影响。

我不确定从这里去哪里。 Ignite中是否有一种解决方法不强迫我使用第三方数据库?

编辑:我的DataStorageConfiguration

    <property name="dataStorageConfiguration">
        <bean class="org.apache.ignite.configuration.DataStorageConfiguration">
            <property name="metricsEnabled" value="true"/>
            <property name="checkpointFrequency" value="300000"/>
            <property name="storagePath" value="/var/lib/ignite/data/db"/>
            <property name="walFlushFrequency" value="10000"/>
            <property name="walMode" value="LOG_ONLY"/>
            <property name="walPath" value="/var/lib/ignite/data/wal"/>
            <property name="walArchivePath" value="/var/lib/ignite/data/wal/archive"/>               
            <property name="walSegmentSize" value="2147483647"/>
            <property name="maxWalArchiveSize" value="4294967294"/>
            <property name="walCompactionEnabled" value="false"/>
            <property name="writeThrottlingEnabled" value="False"/>
            <property name="pageSize" value="4096"/>                
            <property name="defaultDataRegionConfiguration">
                <bean class="org.apache.ignite.configuration.DataRegionConfiguration">
                    <property name="persistenceEnabled" value="true"/>
                    <property name="checkpointPageBufferSize" value="2147483648"/>
                    <property name="name" value="Default_Region"/>
                    <property name="maxSize" value="21474836480"/>
                    <property name="metricsEnabled" value="true"/>
                </bean>
            </property>
        </bean>
    </property> 

更新:禁用持久性后,内部存储器将被正确处理:

enter image description here

更新:here用一个可重现的示例演示了该问题。它可以在至少有22GB内存(用于docker)和约50GB存储空间的机器上运行。有趣的是,只有在传入字节数组或字符串作为值时,泄漏才真正引起注意。

4 个答案:

答案 0 :(得分:2)

TLDR

设置walSegmentSize=64mb(或仅删除设置并使用默认设置)并设置-XX:MaxDirectMemorySize=<walSegmentSize * 4>

说明

人们在计算Ignite的内存需求时经常忘记的一件事就是直接内存缓冲区的大小。

直接内存缓冲区是在Java进程中从单独的空间分配的JVM管理的缓冲区-它既不是Java堆,Ignite数据区域也不是Ignite检查点缓冲区。

直接内存缓冲区是Java中与非堆内存进行交互的常规方法。有很多东西可以使用它(从JVM的内部代码到应用程序),但是在Ignite服务器中,直接内存池的主要用户是预写日志。

默认情况下,Ignite使用内存映射文件写入WAL,该文件通过直接内存缓冲区工作。该缓冲区的大小就是WAL段的大小。接下来我们来看看有趣的东西。

您的WAL细分市场非常巨大! 2GB-很多。默认值为64mb,而我很少见到会使用更多的环境。对于某些特定的工作负载和某些磁盘,我们建议设置256mb。

因此,您在直接内存池中创建了2GB的缓冲区。默认情况下,直接内存的最大大小等于-Xmx-在您的情况下为24GB。我可以看到一种情况,您的直接内存池将膨胀到24GB(从尚未清除的旧缓冲区中膨胀),从而使应用程序的总大小至少为20 + 2 + 24 + 24 = 70GB!。

这说明了40GB的内部JVM内存(我认为这是数据区域+直接)。这也解释了为什么在关闭持久性时看不到问题-在这种情况下您没有WAL。

做什么

  1. 选择理智的walSegmentSize。我不知道选择2GB内存的原因,但是如果您确定WAL分段较小,建议您使用默认值64mb或256mb。

  2. 通过-XX:MaxDirectMemorySize=<size>为JVM的直接内存池设置限制。我发现将其设置为walSegmentSize * 4的值是安全的选择,即在256mb-1gb范围内。

即使您在进行了上述更改后看到内存消耗问题,也还是要保留它们,因为它们是99%群集中的最佳选择。

答案 1 :(得分:1)

内存泄漏似乎是由我的缓存模型中的值对象上的@QueryTextField注释触发的,该注释在Ignite中支持Lucene查询。

最初:case class Value(@(QueryTextField@field) theta: String)

将此行更改为:case class Value(theta: String)似乎可以解决此问题。我没有解释为什么这样做的原因,但是也许对Ignite代码库有很好理解的人可以解释为什么。

答案 2 :(得分:0)

我不知道您的情况是什么“内部”,但是Ignite通常会将其所有数据存储在Off-Heap内存中。请注意,它也不是“直接”内存。

您可以configure the amount of memory dedicated to Off-Heap,也可以configure Page Eviction

答案 3 :(得分:0)

在启用和未启用持久性的情况下,我可以从您的图形中看到点燃缓存指标方面的巨大差距。这意味着,通过持久性,您实际上是在将数据写入数据存储目录wal,walArchive。如果Kubernetes pod还在考虑该目录的内存限制,那么它可能很快就会耗尽内存。