Skip to content

Утечка памяти в плагине Instance #232

Description

@ky0uraku

Описание проблемы

Плагин Instance (mamonsu/plugins/pgsql/instance.py) мутирует атрибут класса self.Items на каждом цикле run(), из-за чего количество столбцов в SELECT-запросе постоянно растёт, пока не упирается в лимит PostgreSQL MaxTupleAttributeNumber (1664 столбца) и не начинает падать с ошибкой:

[ERROR] plugin instance caught error: target lists can have at most 1664 entries

После этого ошибка логируется на каждом цикле сбора (~60 сек), при этом Python-процесс непрерывно течёт по памяти.

Причина

В instance.py, метод run():

def run(self, zbx):
    all_items = self.Items           # ← ссылка на список класса, НЕ копия
    if Pooler.server_version_greater("12.0"):
        all_items += self.Items_pg_12  # ← эквивалент self.Items.extend(self.Items_pg_12)
    if Pooler.server_version_greater("18.0"):
        all_items += self.Items_pg_18  # ← то же самое: мутирует self.Items на месте

all_items = self.Items привязывает all_items к тому же объекту, что и атрибут класса. Оператор += для списка — это list.__iadd__, эквивалент .extend(): он мутирует список на месте и возвращает тот же объект. Поэтому на каждом вызове run():

  1. self.Items вырастает на len(self.Items_pg_12) элементов (и на len(self.Items_pg_18) при PG ≥ 18).
  2. Генерируемый SQL SELECT получает больше столбцов с каждым циклом.
  3. Через ~80–90 минут на PG ≥ 12 (в зависимости от размера Items_pg_12) количество столбцов превышает 1664 и PostgreSQL отклоняет запрос.

Та же ошибка присутствует в методе keys_and_queries() в том же файле:

def keys_and_queries(self, template_zabbix):
    all_items = self.Items           # ← та же ошибка со ссылкой
    if Pooler.server_version_greater("12.0"):
        all_items += self.Items_pg_12
    if Pooler.server_version_greater("18.0"):
        all_items += self.Items_pg_18

Аналогичная проблема в issue #187

В issue #187 была описана идентичная проблема в плагине Statementssum(wal_bytes), sum(wal_records), sum(wal_fpi) добавлялись в запрос на каждом цикле. Исправлено в v3.5.2 (коммит 3d0e0ce, changelog: "fixed Statements plugin list of metrics creation") заменой на:

all_items = self.Items.copy()  # ← создаёт новый список каждый раз

Источник бага

Баг внесён при рефакторинге для поддержки PostgreSQL 18. До этого коммита в instance.py использовалась безопасная конкатенация:

all_items = self.Items + self.Items_pg_12  # создаёт НОВЫЙ список

Коммит, заменивший её на мутирующий паттерн:

  • Коммит: c1a761b5bb (2025-11-17, "feat: add columns support for PostgreSQL 18")
  • Diff:
    -        all_items = self.Items + self.Items_pg_12
    +        all_items = self.Items
    +        if Pooler.server_version_greater("12.0"):
    +            all_items += self.Items_pg_12
    +        if Pooler.server_version_greater("18.0"):
    +            all_items += self.Items_pg_18

Предлагаемый фикс

Применить тот же фикс, что был сделан для Statements в 3.5.2 — использовать .copy():

--- a/mamonsu/plugins/pgsql/instance.py
+++ b/mamonsu/plugins/pgsql/instance.py
@@ in method run():
-        all_items = self.Items
+        all_items = self.Items.copy()
         if Pooler.server_version_greater("12.0"):
             all_items += self.Items_pg_12
         if Pooler.server_version_greater("18.0"):
             all_items += self.Items_pg_18

@@ in method keys_and_queries():
-        all_items = self.Items
+        all_items = self.Items.copy()
         if Pooler.server_version_greater("12.0"):
             all_items += self.Items_pg_12
         if Pooler.server_version_greater("18.0"):
             all_items += self.Items_pg_18

Окружение

  • mamonsu 3.5.16-1.noble
  • PostgreSQL 16
  • Ubuntu 24.04 (noble)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions