2016-04-20 2 views
3

У меня есть программа node.js, вызывающая функцию Postgres (Amazon RDS micro instance), get_jobs в транзакции, 18 раз в секунду, используя пакет node-postgres от brianc.Узел вызывает функцию postgres с временными таблицами, вызывающими «утечку памяти»

Код узла просто расширенная версия brianc's basic client pooling example, примерно как ...

var pg = require('pg'); 
var conString = "postgres://username:[email protected]/database"; 

function getJobs(cb) { 
    pg.connect(conString, function(err, client, done) { 
    if (err) return console.error('error fetching client from pool', err); 
    client.query("BEGIN;"); 
    client.query('select * from get_jobs()', [], function(err, result) { 
     client.query("COMMIT;"); 
     done(); //call `done()` to release the client back to the pool 
     if (err) console.error('error running query', err); 
     cb(err, result); 
    }); 
    }); 
} 

function poll() { 
    getJobs(function(jobs) { 
    // process the jobs 
    }); 
    setTimeout(poll, 55); 
} 

poll(); // start polling 

Так Postgres получает:

2016-04-20 12:04:33 UTC:172.31.9.180(38446):[email protected]:[5778]:LOG: statement: BEGIN; 
2016-04-20 12:04:33 UTC:172.31.9.180(38446):[email protected]:[5778]:LOG: execute <unnamed>: select * from get_jobs(); 
2016-04-20 12:04:33 UTC:172.31.9.180(38446):[email protected]:[5778]:LOG: statement: COMMIT; 

... повторяется каждые 55ms.

get_jobs пишется временные таблицы, что-то вроде этого

CREATE OR REPLACE FUNCTION get_jobs (
) RETURNS TABLE (
    ... 
) AS 
$BODY$ 
DECLARE 
    _nowstamp bigint; 
BEGIN 

    -- take the current unix server time in ms 
    _nowstamp := (select extract(epoch from now()) * 1000)::bigint; 

    -- 1. get the jobs that are due 
    CREATE TEMP TABLE jobs ON COMMIT DROP AS 
    select ... 
    from really_big_table_1 
    where job_time < _nowstamp; 

    -- 2. get other stuff attached to those jobs 
    CREATE TEMP TABLE jobs_extra ON COMMIT DROP AS 
    select ... 
    from really_big_table_2 r 
    inner join jobs j on r.id = j.some_id 

    ALTER TABLE jobs_extra ADD PRIMARY KEY (id); 

    -- 3. return the final result with a join to a third big table 
    RETURN query (

    select je.id, ... 
    from jobs_extra je 
     left join really_big_table_3 r on je.id = r.id 
    group by je.id 

); 

END 
$BODY$ LANGUAGE plpgsql VOLATILE; 

я использовал the temp table pattern, потому что я знаю, что jobs всегда будет небольшой отрывок из строк из really_big_table_1, в надежде, что это будет масштабироваться лучше, чем один запрос с несколькими объединениями и множество условий. (Я использовал это с большим успехом с SQL Server, и теперь я не доверяю оптимизатору запросов, но, пожалуйста, скажите мне, если это неправильный подход для Postgres!)

Запрос выполняется в 8 мс на небольших таблицах (как измерено от узла), достаточно времени, чтобы завершить одно задание «опрос» до следующего запуска.

Проблема: после примерно трех часов опроса с такой скоростью на сервере Postgres заканчивается память и сбои.

То, что я уже пробовал ...

  • Если я перепишу функцию без временных таблиц, Postgres не работают из памяти, но использовать шаблон временной таблицы много, так что не является решением.

  • Если я остановлю программу узла (которая убивает 10 подключений, используемых для выполнения запросов), память освобождается. Простое создание узла в ожидании минуты между сеансами опроса не оказывает такого же эффекта, поэтому, очевидно, есть ресурсы, которые поддерживает бэкэнд Postgres, связанный с объединенным соединением.

  • Если я запускаю VACUUM во время опроса, это не влияет на потребление памяти, и сервер продолжает свой путь к смерти.

  • Уменьшение частоты опроса только изменяет время до того, как сервер умрет.

  • Добавление DISCARD ALL; после каждого COMMIT; не действует.

  • Явный вызов DROP TABLE jobs; DROP TABLE jobs_extra; после RETURN query() вместо ON COMMIT DROP с на CREATE TABLE с. Сервер все еще падает.

  • Предложение Пер CFrei добавило pg.defaults.poolSize = 0 к коду узла, чтобы отключить объединение. Сервер все еще разбился, но занял гораздо больше времени, и обмен был намного выше (второй всплеск), чем все предыдущие тесты, которые выглядели как первый всплеск ниже. Позднее я узнал, что pg.defaults.poolSize = 0may not disable pooling as expected.

Swap memory usage on Postgres server

  • На основе this: «. Временные таблицы не могут быть доступны автовакуумной Поэтому, соответствующий вакуум и анализировать операции должны выполняться с помощью команд сеанса SQL», я пытался запустите команду VACUUM с узла сервера (в качестве некоторой попытки сделать команду VACUUM командой «in session»). Я не мог заставить этот тест работать. У меня много объектов в моей базе данных, и VACUUM, работающий на всех объектах, слишком долго выполнял каждую итерацию работы. Ограничение VACUUM только для временных таблиц было невозможно - (a) вы не можете запустить VACUUM в транзакции и (b) за пределами транзакции временные таблицы не существуют. : P EDIT: Позже на форуме Postgres IRC, полезный парень объяснил, что VACUUM не относится к самим временным таблицам, но может быть полезен для очистки созданных и удаленных строк из pg_attributes, которые вызывают TEMP TABLES. В любом случае, VACUUMING «в сеансе» не был ответом.

  • DROP TABLE ... IF EXISTS до CREATE TABLE, а не ON COMMIT DROP. Сервер все еще умирает.

  • CREATE TEMP TABLE (...) и insert into ... (select...) вместо CREATE TEMP TABLE ... AS, а не ON COMMIT DROP. Сервер умирает.

И вот ON COMMIT DROP не выпускает все связанные ресурсы? Что еще может содержать память? Как его отпустить?

+0

Можете ли вы показать код узла, который вы используете для выполнения запроса? – robertklep

+0

Эта ошибка продолжается, когда вы используете 'require ('pg-native')' вместо 'pg'? Что делать, если вы создаете новое соединение каждый раз, когда запрашиваете соединение с пулом? 'pg.defaults.poolSize = 0'. – CFrei

+0

@robertklep Я добавил код. @ CFrei будет отчитываться после того, как мы протестировали - для каждого цикла требуется некоторое время. – poshest

ответ

0

Использование, CTE для создания частичного результата устанавливает вместо временных таблиц.

CREATE OR REPLACE FUNCTION get_jobs (
) RETURNS TABLE (
    ... 
) AS 
$BODY$ 
DECLARE 
    _nowstamp bigint; 
BEGIN 

    -- take the current unix server time in ms 
    _nowstamp := (select extract(epoch from now()) * 1000)::bigint; 

    RETURN query (

    -- 1. get the jobs that are due 
    WITH jobs AS (

     select ... 
     from really_big_table_1 
     where job_time < _nowstamp; 

    -- 2. get other stuff attached to those jobs 
    ), jobs_extra AS (

     select ... 
     from really_big_table_2 r 
     inner join jobs j on r.id = j.some_id 

    ) 

    -- 3. return the final result with a join to a third big table 
    select je.id, ... 
    from jobs_extra je 
     left join really_big_table_3 r on je.id = r.id 
    group by je.id 

); 

END 
$BODY$ LANGUAGE plpgsql VOLATILE; 

Планировщик будет оценивать каждый блок последовательно так, как я хотел бы достичь с помощью временных таблиц.

Я знаю, что это прямо не решает проблему утечки памяти (я уверен, что с внедрением Postgres что-то не так, по крайней мере, так, как они проявляются в конфигурации RDS).

Однако запрос работает, он запрограммирован так, как я намеревался, и использование памяти стабильно теперь после 3 дней выполнения задания, и мой сервер не сбой.

Я вообще не изменил код узла.

0

Я использовал это с большим эффектом с SQL Server и не доверяйте ни одному Оптимизатор запросов в настоящее время

Тогда не использовать их. Вы можете выполнять запросы напрямую, как показано ниже.

но, пожалуйста, скажите мне, если это неправильный подход для Postgres!

Это не совсем неправильный подход, это просто очень неудобно, поскольку вы пытаетесь создать нечто, что было реализовано другими для более простого использования. В результате вы делаете много ошибок, которые могут привести к возникновению многих проблем, в том числе утечек памяти.

Сравнить с простотой точно такой же пример, который использует pg-promise:

var pgp = require('pg-promise')(); 
var conString = "postgres://username:[email protected]/database"; 
var db = pgp(conString); 

function getJobs() { 
    return db.tx(function (t) { 
     return t.func('get_jobs'); 
    }); 
} 

function poll() { 
    getJobs() 
     .then(function (jobs) { 
      // process the jobs 
     }) 
     .catch(function (error) { 
      // error 
     }); 

    setTimeout(poll, 55); 
} 

poll(); // start polling 

получает еще проще при использовании ES6 синтаксис:

var pgp = require('pg-promise')(); 
var conString = "postgres://username:[email protected]/database"; 
var db = pgp(conString); 

function poll() { 
    db.tx(t=>t.func('get_jobs')) 
     .then(jobs=> { 
      // process the jobs 
     }) 
     .catch(error=> { 
      // error 
     }); 

    setTimeout(poll, 55); 
} 

poll(); // start polling 

Единственное, что я не совсем понимаю, в ваш пример - использование транзакции для выполнения одного SELECT. Это не то, что транзакции, как правило, для вас, поскольку вы не меняете никаких данных. Я предполагаю, что вы пытались сжать реальный фрагмент кода, который у вас был, что также изменяет некоторые данные.

В случае, если вам не нужны транзакции, ваш код может быть дополнительно снижена до:

var pgp = require('pg-promise')(); 
var conString = "postgres://username:[email protected]/database"; 
var db = pgp(conString); 

function poll() { 
    db.func('get_jobs') 
     .then(jobs=> { 
      // process the jobs 
     }) 
     .catch(error=> { 
      // error 
     }); 

    setTimeout(poll, 55); 
} 

poll(); // start polling 

UPDATE

Было бы опасный подход, однако, не контролировать конец предыдущего запроса, который также может создавать проблемы с памятью/соединением.

Безопасный подход должен быть:

function poll() { 
    db.tx(t=>t.func('get_jobs')) 
     .then(jobs=> { 
      // process the jobs 

      setTimeout(poll, 55); 
     }) 
     .catch(error=> { 
      // error 

      setTimeout(poll, 55); 
     }); 
} 
+1

Я предположил, что транзакция используется только для запуска 'ON COMMIT DROP' временных таблиц, как своего рода сборка мусора. – robertklep

+0

@robertklep, хорошо, может быть, я просто не был чист в этой части. Во всяком случае, я привел пример с транзакцией, если это необходимо;) –

+0

Эй, жизнерадостный, я ценю, что в вашей библиотеке много отличных функций, но ваш ответ был бы более убедительным, если бы вы определили, где в MY code «ошибки, которые могут привести к многие проблемы, в том числе утечки памяти ". Сказав это, я отдам вашей библиотеке и дам вам знать, разрешит ли она мою проблему. – poshest

Смежные вопросы