Ремонт БД на лету с помощью pg_repack
Окончились новогодние праздники, а это значит, что пора с новыми силами кинуться в бой с ИТ-сложностью, ИТ-хаосом и другими ИТ бедами.
Одной из бед всех версионных БД является разбухание таблиц. Все бы ничего, но если количество активно используемых данных перестает влезать в оперативную память, то время обработки запросов к БД чрезвычайно сильно возрастает (об этом я напишу в ближайшем будущем). И чтобы «впихнуть» размеры таблицы в нужный размер, иногда приходится делать некоторые нетрадиционные трюки.
Но перейдем к практике. Пусть у нас есть таблица пользователей библиотеки users
и таблица, в которой хранится связка
пользователей и книг, которые они взяли books_users
. А у этой нашей библиотеки миллион пользователей.
test=# \d users
Table "public.users"
Column | Type | Modifiers
--------+---------+----------------------------------------------------
id | integer | not null default nextval('users_id_seq'::regclass)
email | text |
name | text |
gender | integer |
age | integer |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_age_idx" btree (age)
"users_email_idx" btree (email)
"users_gender_idx" btree (gender)
test=# select pg_size_pretty(pg_total_relation_size('users'));
pg_size_pretty
----------------
208 MB
(1 row)
В какой-то момент мы решаем денормализовать схему БД и добавить количество взятых в библиотеки книг в таблицу пользователей.
test=# alter table users add column books_count int;
test=# update users set books_count = (select count(*) from books_users where user_id = users.id);
UPDATE 1000000
test=# select pg_size_pretty(pg_total_relation_size('users'));
pg_size_pretty
----------------
454 MB
Как мы видим, после такой простой операции таблица разбухла в 2 раза. Что же делать в таком случае? Давайте попробуем
обычный vacuum
, ведь он предназначен именно для этого.
test=# vacuum users;
VACUUM
test=# select pg_size_pretty(pg_total_relation_size('users'));
pg_size_pretty
----------------
454 MB
Как мы видим, это не помогло. Понятно, что разбухла как таблица, так и индексы (а их там 4), поправить 3 из них можно с
помощью конкурентного пересоздания индексов, но сама таблица и индекс users_pkey
, который
отвечает за первичный ключ, останутся разбухшими.
Конечно, нам бы помог vacuum full
, но он требует эксклюзивного доступа к таблица, а это значит, что все время, пока он
будет выполняться (а это достаточно долгая операция), к таблице пользователей не будет доступа, что равносильно полной
недоступности вашего сервиса.
Беда-беда, подумал бы я, если бы не знал про проект pg_repack. Проект pg_repack — это форк проекта pg_reorg, который по каким-то причинам стагнировал.
В двух словах, pg_repack позволяет сделать vacuum full
на лету. Лучше, конечно, не допускать ситуаций, когда ваша БД
распухает, но иногда это все равно происходит даже у очень опытных инженеров.
Хочу сказать, что существуют и другие способы сделать то же самое. Например, в одном из проектов мы реплицировали разбухшую таблицу с помощью londiste, а потом подменяли старую таблицу на ее реплику. Но pg_reorg позволяет сделать то же самое одной командой, и делал это много раз без потери данных.
О тонких моментах использования этого инструмента я расскажу в следующий раз.