Мы оцениваем использование PostgreSQL для реализации многопользовательской базы данных,
В настоящее время мы проводим несколько тестов для модели с одной базой данных и несколькими схемами.
(в основном все арендаторы имеют одинаковый набор объектов базы данных в соответствии с собственной схемой в одной базе данных).
Приложение будет поддерживать пул соединений, который будет использоваться всеми арендаторами / схемами.
например. Если база данных имеет 500 арендаторов / схем и каждый арендатор имеет 200 таблиц / представлений,
общее количество таблиц / просмотров будет 500 * 200 = 100 000.
Поскольку пул соединений будет использоваться всеми арендаторами, в конечном итоге каждое соединение будет попадать во все таблицы / представления.
В наших тестах, когда соединение достигло большего числа представлений, мы обнаружили, что использование памяти процессом бэкэнда увеличивается довольно быстро, и большинство из них являются частной памятью.
Эта память будет храниться до тех пор, пока соединение не будет закрыто.
У нас есть тестовый случай, когда один бэкэнд-процесс использует больше памяти объемом 30 ГБ и в итоге выдает ошибку нехватки памяти.
Чтобы помочь понять проблему, я написал код для создания упрощенных тестовых случаев
- MTDB_destroy: используется для очистки схем арендатора
- MTDB_Initialize: используется для создания многопользовательской БД
- MTDB_RunTests: упрощенный контрольный пример, в основном выбираемый из всех представлений арендаторов один за другим.
Тесты, которые я провел, были на PostgreSQL 9.0.3 на CentOS 5.4
Чтобы убедиться, что у меня чистая среда, я заново создал кластер базы данных и оставил большинство конфигураций по умолчанию,
(Единственное, что мне нужно изменить, - это увеличить max_locks_per_transaction, так как MTDB_destroy должен отбрасывать много объектов.)
Вот что я делаю, чтобы воспроизвести проблему:
- создать новую базу данных
- создать три функции, используя прикрепленный код
подключиться к новой созданной БД и запустить сценарии инициализации
- Инициализировать
выберите MTDB_Initialize ('tenant', 100, 100, true);
- не уверен, что анализ вакуума здесь полезен, я просто запускаю его
анализ вакуума;
- проверить созданные таблицы / представления
выберите table_schema, table_type, count (*) из information_schema.tables, где table_schema похожа на группу 'tenant%' по table_schema, порядок table_type по table_schema, table_type;
открыть другое соединение с новой созданной БД и запустить тестовые сценарии
- получить идентификатор внутреннего процесса для текущего соединения
SELECT pg_backend_pid ();
- открыть консоль linux и запустить ps -p и посмотреть VIRT, RES и SHR
- запустить тесты
выбрать MTDB_RunTests ('tenant', 1);
Замечания:
при первом создании соединения для запуска тестов
VIRT = 182 МБ, RES = 6240 КБ, SHR = 4648 К
после запуска тестов один раз, (заняло 175 секунд)
VIRT = 1661 МБ RES = 1,5 ГБ SHR = 55 МБ
заново запустить тест (заняло 167 секунд)
VIRT = 1661 МБ RES = 1,5 ГБ SHR = 55 МБ
заново запустить тест (заняло 165 секунд)
VIRT = 1661 МБ RES = 1,5 ГБ SHR = 55 МБ
По мере увеличения количества таблиц использование памяти увеличивается и в тестах.
Может кто-нибудь помочь объяснить, что здесь происходит?
Есть ли способ контролировать использование памяти внутренним процессом PostgreSQL?
Спасибо.
Samuel
-- MTDB_destroy
create or replace function MTDB_destroy (schemaNamePrefix varchar(100))
returns int as $$
declare
curs1 cursor(prefix varchar) is select schema_name from information_schema.schemata where schema_name like prefix || '%';
schemaName varchar(100);
count integer;
begin
count := 0;
open curs1(schemaNamePrefix);
loop
fetch curs1 into schemaName;
if not found then exit; end if;
count := count + 1;
execute 'drop schema ' || schemaName || ' cascade;';
end loop;
close curs1;
return count;
end $$ language plpgsql;
-- MTDB_Initialize
create or replace function MTDB_Initialize (schemaNamePrefix varchar(100), numberOfSchemas integer, numberOfTablesPerSchema integer, createViewForEachTable boolean)
returns integer as $$
declare
currentSchemaId integer;
currentTableId integer;
currentSchemaName varchar(100);
currentTableName varchar(100);
currentViewName varchar(100);
count integer;
begin
-- clear
perform MTDB_Destroy(schemaNamePrefix);
count := 0;
currentSchemaId := 1;
loop
currentSchemaName := schemaNamePrefix || ltrim(currentSchemaId::varchar(10));
execute 'create schema ' || currentSchemaName;
currentTableId := 1;
loop
currentTableName := currentSchemaName || '.' || 'table' || ltrim(currentTableId::varchar(10));
execute 'create table ' || currentTableName || ' (f1 integer, f2 integer, f3 varchar(100), f4 varchar(100), f5 varchar(100), f6 varchar(100), f7 boolean, f8 boolean, f9 integer, f10 integer)';
if (createViewForEachTable = true) then
currentViewName := currentSchemaName || '.' || 'view' || ltrim(currentTableId::varchar(10));
execute 'create view ' || currentViewName || ' as ' ||
'select t1.* from ' || currentTableName || ' t1 ' ||
' inner join ' || currentTableName || ' t2 on (t1.f1 = t2.f1) ' ||
' inner join ' || currentTableName || ' t3 on (t2.f2 = t3.f2) ' ||
' inner join ' || currentTableName || ' t4 on (t3.f3 = t4.f3) ' ||
' inner join ' || currentTableName || ' t5 on (t4.f4 = t5.f4) ' ||
' inner join ' || currentTableName || ' t6 on (t5.f5 = t6.f5) ' ||
' inner join ' || currentTableName || ' t7 on (t6.f6 = t7.f6) ' ||
' inner join ' || currentTableName || ' t8 on (t7.f7 = t8.f7) ' ||
' inner join ' || currentTableName || ' t9 on (t8.f8 = t9.f8) ' ||
' inner join ' || currentTableName || ' t10 on (t9.f9 = t10.f9) ';
end if;
currentTableId := currentTableId + 1;
count := count + 1;
if (currentTableId > numberOfTablesPerSchema) then exit; end if;
end loop;
currentSchemaId := currentSchemaId + 1;
if (currentSchemaId > numberOfSchemas) then exit; end if;
end loop;
return count;
END $$ language plpgsql;
-- MTDB_RunTests
create or replace function MTDB_RunTests(schemaNamePrefix varchar(100), rounds integer)
returns integer as $$
declare
curs1 cursor(prefix varchar) is select table_schema || '.' || table_name from information_schema.tables where table_schema like prefix || '%' and table_type = 'VIEW';
currentViewName varchar(100);
count integer;
begin
count := 0;
loop
rounds := rounds - 1;
if (rounds < 0) then exit; end if;
open curs1(schemaNamePrefix);
loop
fetch curs1 into currentViewName;
if not found then exit; end if;
execute 'select * from ' || currentViewName;
count := count + 1;
end loop;
close curs1;
end loop;
return count;
end $$ language plpgsql;