Возможная утечка памяти в Ignite DataStreamer - PullRequest
16 голосов
/ 18 апреля 2019

Я использую 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 и вызвать ручной сборщик мусора, но, похоже, ничто не повлияло на это, кроме периодического выключения всех моих модулей и их перезапуска.

Яне уверен, куда идти отсюда.Есть ли обходной путь в 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 ГБ памяти.Интересно, что утечка действительно заметна только при передаче в качестве значения байтового массива или строки.

Ответы [ 4 ]

2 голосов
/ 21 апреля 2019

TLDR

Установить 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% кластеров.

1 голос
/ 30 апреля 2019

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

Первоначально: case class Value(@(QueryTextField@field) theta: String)

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

0 голосов
/ 21 апреля 2019

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

0 голосов
/ 19 апреля 2019

Я не знаю, что является «внутренним» в вашем случае, но Ignite обычно хранит все свои данные в памяти вне кучи.Обратите внимание, что это не «прямая» память.

Вы можете настроить объем памяти, выделенный для автономной памяти , а также настроить Page Eviction .

...