Технические заметки одного Евтуховича

Рассказ о серых трудовых буднях инженера программных систем

Ремонт БД на лету с помощью 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 позволяет сделать то же самое одной командой, и делал это много раз без потери данных.

О тонких моментах использования этого инструмента я расскажу в следующий раз.

Комментарии