Всем привет. Меня зовут Олег, я техлид в команде разработки CRM для менеджеров ипотечного кредитования в Домклике.  Сегодня я хотел бы поделиться рецептом по обрезанию большой таблицы PostgreSQL в production. Пример: мы имеем в продовой БД достаточно большую таблицу с именем task (несколько сотен миллионов строк) с устаревшими данными, которые нам уже не нужны. Точнее, они мешают — БД долго дампится, а индексы становятся неэффективными. Мы хотим обрезать эту таблицу (удалить старые строки ранее определенной даты). Для простоты предположим, что в базе нет входящих foreign key на таблицу task (при их наличии решение задачи немного усложняется).  Структура таблицы такая (упрощенный пример): Решение в лоб (delete from task where id < 1234567) работает очень долго из-за большого количества индексов и ограничений в таблице и нас не устраивает. В нашем случае скорость удаления составляла 10 000 строк в минуту (нужно было удалить около 100 млн строк).  С учетом того, что данные запросы мы могли бы выполнять только в ночное время, чистка таблицы этим способом заняла бы около месяца. Более быстрый алгоритм (который позволил достичь нам очистить таблицу всего за час): Создаем новую таблицу task_new, в которую перенесем актуальные строки:CREATE TABLE task_new LIKE tasks;ALTER TABLE task_new ADD PRIMARY KEY (id); Запускаем триггер для обновления новой таблицы. Запускаем скрипт для переноса актуальных строк. Добавляем индексы и ограничения для новой таблицы. Меняем таблицы местами. Новую таблицу создаем на основе текущей (вместе с типами данных, ограничениями на NULL и значениями по умолчанию). Индексы и ограничения навесим позднее, чтобы не замедлять копирование данных. Далее нам нужно задать триггер, который будет обновлять данные в соответствии с исходной таблицей (команды insert/update/delete). После запуска триггера нам нужно запустить скрипт, который перенесет остальные актуальные данные из текущей таблицы в новую. Входные параметры скрипта:  conn_str — строка для подключения к БД; first_row_id — начиная с этого идентификатора мы переносим строки в новую таблицу (можно получить запросом select id from task where created::date = '2020-01-01'::date - interval '1 day' limit 1;); first_trigger_id — идентификатор первой строки, созданной в новой таблице триггером (можно получить запросом select id from task_new order by id limit 1); sleep_ms — сколько миллисекунд должен спать скрипт между итерациями; chunk_size — количество строк, переносимых за один раз. Команда для запуска скрипта: Теперь нам нужно навесить на новую таблицу индексы и ограничения в соответствии со старой. Получаем запросы для добавления индексов в текущую таблицу (кроме PK): Получили набор команд: Команды нужно немного преобразовать: добавить ключевое слово CONCURRENTLY, заменить название таблицы на новую и добавить префикс или суффикс “_new” к названиям индексов: Теперь получим команды для добавления ограничения для новой таблицы: Получили набор команд (в них ничего менять не нужно): Теперь нужно выполнить в транзакции эти команды: Последний этап: нужно поменять таблицы местами, переключить sequence со старой таблицы на новую и удалить триггер. Таким образом можно быстро очистить большую таблицу от устаревших данных, которые уже не нужны, но ухудшают производительность базы данных. Если на таблицу есть входящие foreign keys, перед чисткой основной таблицы аналогично чистим ссылающиеся на нее (по условию на колонку c foreign key на нужную). Если есть вопросы - пишите в комментариях. Техлид