У меня есть программа 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 = 0
may not disable pooling as expected.
На основе 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
не выпускает все связанные ресурсы? Что еще может содержать память? Как его отпустить?
Можете ли вы показать код узла, который вы используете для выполнения запроса? – robertklep
Эта ошибка продолжается, когда вы используете 'require ('pg-native')' вместо 'pg'? Что делать, если вы создаете новое соединение каждый раз, когда запрашиваете соединение с пулом? 'pg.defaults.poolSize = 0'. – CFrei
@robertklep Я добавил код. @ CFrei будет отчитываться после того, как мы протестировали - для каждого цикла требуется некоторое время. – poshest