Возможная утечка памяти в Ignite DataStreamer

Я запускаю Ignite в кластере Kubernetes с включенным постоянством. Каждая машина имеет кучу Java объемом 24 ГБ, из которых 20 ГБ предназначены для надежной памяти с ограничением в 110 ГБ. Мои соответствующие параметры 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

"Ignite Cache" - это кеш страниц ядра. Последняя панель "Heap + Durable + Buffer" представляет собой сумму показателей воспламенения HeapMemoryUsed + PhysicalMemorySize + CheckpointBufferSize.

Я знал, что это не может быть результатом накопления данных, потому что DataStreamers сбрасываются после каждого прочитанного файла (максимум до 250 МБ), и ни один узел не читает более 4 файлов одновременно. После того, как я -XX:MaxDirectMemorySize=10G другие проблемы, я попытался установить -XX:MaxDirectMemorySize=10G и вызвать ручной -XX:MaxDirectMemorySize=10G, но, похоже, ничто не оказывает никакого влияния, кроме периодического выключения всех моих модулей и их перезапуска.

Я не уверен, куда идти отсюда. Есть ли обходной путь в Ignite, который не заставляет меня использовать стороннюю базу данных?

РЕДАКТИРОВАТЬ: My 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

ОБНОВЛЕНИЕ: проблема продемонстрирована здесь на воспроизводимом примере. Он работает на машине с минимум 22 ГБ памяти для докера и около 50 ГБ памяти. Интересно, что утечка действительно заметна только при передаче в качестве значения байтового массива или строки.

Ответы

Ответ 1

Утечки памяти, кажется, вызваны аннотацией @QueryTextField на объекте значения в моей модели кэша, которая поддерживает запросы Lucene в Ignite.

Первоначально: case class Value(@([email protected]) theta: String)

Изменение этой строки на: case class Value(theta: String) кажется, решает проблему. У меня нет объяснения, почему это работает, но, возможно, кто-то с хорошим пониманием базы кода Ignite сможет объяснить, почему.

Ответ 2

TL;DR

Установите walSegmentSize=64mb (или просто удалите настройку и используйте значение по умолчанию) И установите -XX:MaxDirectMemorySize=<walSegmentSize * 4>.

объяснение

Одна вещь, которую люди часто забывают при вычислении потребностей памяти Ignite, - это размер буфера прямой памяти.

Прямые буферы памяти - это управляемые JVM буферы, выделенные из отдельного пространства в процессе Java - это ни кучи Java, ни область данных Ignite, ни буфер контрольных точек Ignite.

Прямые буферы памяти - это нормальный способ взаимодействия с памятью без кучи в Java. Есть много вещей, которые используют это (от внутреннего кода JVM до приложений), но на серверах Ignite основным пользователем пула прямой памяти является запись с опережением записи.

По умолчанию Ignite выполняет запись в WAL с использованием файла с отображением в памяти, который работает через прямой буфер памяти. Размер этого буфера равен размеру сегмента WAL. И тут мы переходим к забавным вещам.

Ваши сегменты WAL огромны! 2Гб - это много. По умолчанию 64 МБ, и я редко видел среду, которая будет использовать больше, чем это. В некоторых конкретных рабочих нагрузках и для некоторых конкретных дисков мы рекомендуем устанавливать 256 МБ.

Итак, у вас есть 2 ГБ буфера, которые создаются в пуле прямой памяти. Максимальный размер прямой памяти по умолчанию равен -Xmx - в вашем случае 24 ГБ. Я вижу сценарий, когда ваш пул прямой памяти увеличится до 24 ГБ (из еще не очищенной старой буферизованной), в результате чего общий размер вашего приложения будет не менее 20 + 2 + 24 + 24 = 70GB !

Это объясняет 40 ГБ внутренней памяти JVM (я думаю, что область данных + прямой). Это также объясняет, почему вы не видите проблемы, когда постоянство отключено - в этом случае у вас нет WAL.

Что делать

  1. Выберите вменяемый walSegmentSize. Я не знаю причину выбора 2 ГБ, но я бы порекомендовал выбрать значение по умолчанию 64 МБ или 256 МБ, если вы уверены, что у вас есть проблемы с небольшими сегментами WAL.

  2. Установите ограничение на пул прямой памяти JVM через -XX:MaxDirectMemorySize=<size>. Я считаю, что это безопасный выбор, чтобы установить его в значение walSegmentSize * 4, то есть где-то в диапазоне 256 МБ-1 ГБ.

Даже если вы видите проблемы с использованием памяти после внесения вышеуказанных изменений - все равно сохраните их, просто потому, что они являются лучшим выбором для 99% кластеров.

Ответ 4

С включенным постоянством и без него я вижу огромный разрыв в показателях воспламенения в вашем графике. это означает, что с постоянством вы фактически записываете данные в каталог хранилища данных, wal, walArchive. Если модуль Kubernetes также рассматривает этот каталог в пределе памяти, то он может скоро выйти из памяти.