Возможная утечка памяти в 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 подтвердили это:
"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>
ОБНОВЛЕНИЕ: Когда я отключаю постоянство, внутренняя память должным образом удаляется:
ОБНОВЛЕНИЕ: проблема продемонстрирована здесь на воспроизводимом примере. Он работает на машине с минимум 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.
Что делать
-
Выберите вменяемый walSegmentSize
. Я не знаю причину выбора 2 ГБ, но я бы порекомендовал выбрать значение по умолчанию 64 МБ или 256 МБ, если вы уверены, что у вас есть проблемы с небольшими сегментами WAL.
-
Установите ограничение на пул прямой памяти JVM через -XX:MaxDirectMemorySize=<size>
. Я считаю, что это безопасный выбор, чтобы установить его в значение walSegmentSize * 4
, то есть где-то в диапазоне 256 МБ-1 ГБ.
Даже если вы видите проблемы с использованием памяти после внесения вышеуказанных изменений - все равно сохраните их, просто потому, что они являются лучшим выбором для 99% кластеров.
Ответ 3
Я не знаю, что такое "внутренний" в вашем случае, но Ignite обычно хранит все свои данные в памяти вне кучи. Обратите внимание, что это не "прямая" память.
Вы можете настроить объем памяти, выделенный для автономной памяти, а также настроить Page Eviction.
Ответ 4
С включенным постоянством и без него я вижу огромный разрыв в показателях воспламенения в вашем графике. это означает, что с постоянством вы фактически записываете данные в каталог хранилища данных, wal, walArchive. Если модуль Kubernetes также рассматривает этот каталог в пределе памяти, то он может скоро выйти из памяти.