Как стать автором
Обновить

Комментарии 29

А «create table as select» не пробовали? Если пробовали, на сколько медленнее внешнего скрипта?
Цель — наполнять новую таблицу чанками, для большей отказоустойчивости и уменьшения нагрузки на боевую базу
Почему-то мне кажется, что вы удалили новую таблицу…
BEGIN;
...
ALTER TABLE task RENAME TO task_old;
ALTER TABLE task_new RENAME TO task; -- новая таблица теперь task
...
COMMIT;
...
DROP TABLE task CASCADE; -- удаляем новую...
Спасибо большое, поправил в тексте
То, что DDL частично транзакционен в PostgreSQL (хотя в общем-то не обязан быть) спасает от того, что в момент переименования таблиц часть вставляемых данных потерялась бы. Ну и внезапный вопрос — а дальше так же чистить будете? Более или менее стандартное решение для такого рода задач — партицировать таблицу по id/дням/неделям/месяцам, триггером проводить вставку в нужный партишен, старые партишены дропать. Ну а вообще для очереди задач обычно пользуются Rabbitmq или Kafka, последняя умеет хранить какое-то количество обработанных задач на случай сбоев в обработчике
Таблицу взял упрощенную для примера, в реальном кейсе использовалась более сложная таблица, которая использовалась в бизнес логике и ее нельзя выносить в rabbit и kafka.

Что касается партиционирования — рецепт на случай, когда сразу его не предусмотрели, но настал момент когда нужно почистить табличку (после чистки уже можно и партиционирование настроить, кстати)
А нельзя было сразу на будущее сделать?
Списаться с разработчиками софтин которые эту таблицу tasks запонолняют и переделать в несколько таблиц по годам?
tasks_2020, tasks_2021? Можно даже по кварталам разбить tasks_2020q1, tasks_2020q2…
Просто я работал с большой базой 200 гб на хардах.

Реально если предотвратить рост таблицы заранее — база всегда будет довольно шутсро работать.
Это и есть рецепт на тот случай, когда сразу не сделали партиционирование)
а как выбирать данные из нескольких периодов?
вьюху писать с union?
ну мы использовали процедуры.
Стратегия очень сильно зависит от данных в таблице: сколько процентов данных нужно оставить, а сколько удалить?

Почему
решение почти в лоб

delete from task where id < 1000;
delete from task where id < 2000;
...

не подходит? Нагрузку можно в этом случае можно подобрать такую, чтобы прод ее не ощущал. И тестироваться такое должно проще.

Почему нам нужно это сделать быстро? Если таблица уже есть и она как-то работает, то немного подождать не должно быть большой проблемой.

Может быть я «параноик», но использование хитрых трюков для удаления данных из базы, по-моему, должно как-то более серьезно обосновываться, чем «так быстрее».
На наших тестах удаление 10 000 строк занимало около 1 минуты (база на SSD, но очень нагруженная таблица с кучей индексов и констрейнтов).
Нам нужно было убрать 100 млн строк — такая скорость нам не подходила.

Всего то 6 дней работы и удалили бы все.
Но раз вас клюнул резко петух в зад и ждать не вмоготу. С другой стороны возникает вопрос: а какого черта вы тянули резину и копили данные? И будите копить и дальше? Рецепт описанный в статье понятен, но непонятна мораль сей истории.


А сколько переливались данные из старой таблицы в новкю?

6 дней непрерывной работы. В связи с принятыми в компании требованиями по стабильности продового окружения, мы можем такие работы проводить только в ночное время.
Из старой таблицы в новую данные переливались чуть менее часа.
Это интересное дополнение, достойное упоминания в статье. Если delete — 6 дней, если create table — 1 час (+ написание и отладку скрипта сколько-то времени).
Спасибо)
Добавил в текст статьи эту информацию

Кажется так будет чуть прямее?


  1. Сделать партиционированную таблицу с текущей таблицей в качестве единственной партиции
  2. Добавить новую партицию, перенаправить запись туда
  3. Создать ещё одну табличку, куда заселектить нужные данные из старой, и переподключить её на место старой. Или спокойно удалить записи прямо из старой (если запись туда не производится, то можно хоть за один запрос, не?).
Читая кейс тоже возникла именно эта мысль.
Но надо быть осторожнее с индексами и ограничениями. При добавлении партиции к партиционной таблице, проверяются все индексы и огранчиения и создание этих индексов может занять кучу времени, если они не такие…
Сколько же на этой таблице индексов, что удаление 10 тысяч строк занимало минуту, а перенос сотен миллионов (вы писали про удаление только 100 млн.) занимает час?
И сколько потом все эти индексы и констрейнты снова строились?
индексов много (по размеру в 2 раза больше чем сама таблица), таблица очень нагруженная, на нее много сложных запросов идет
Строились, кстати, недолго — около 5 минут в режиме concurrently.
Я так и не понял, зачем для переноса данных из старой таблицы в новую потребовался внешний инструмент (питоновский скрипт). То же, но с меньшим потреблением ресурсов, могла выполнить хранимая процедура прямо на сервере.
Возможно, но нам хотелось более контролируемый процесс иметь при переносе.
Вот теперь даже больше чем не понял.

В чём контроль-то? по-любому процесс либо прошёл полностью, и получился ожидаемый результат, либо нет, и всё надо начинать сначала, исправив ошибки. Контролировать течение процесса, следя за изменением количества уже перенесённых записей? так и в процедуре это элементарно, просто надо понизить уровень транзакции и разрешить грязные чтения. Оборвать на середине, если что пошло не так? ну так и выгрузить процесс с процедурой — не проблема.

Или речь о том, что надо дополнительно на экране нарисовать прогресс-бар? ну тогда да, хранимка с этим не справится…

Вы забыли о том, что при изменении имени таблицы её внутренний tableoid не меняется и все объекты которые используют данную таблицу нужно будет менять — функции, представления...

Спасибо за уточнение, дополню статью.
В нашем случае с таблицей не были связаны функции, триггеры и вьюхи, упустил этот момент.

Потому можно проще, делаете таблицу с триггером чтобы не терять новые данные во время работ, далее копируете те данные что нужны в новую таблицу (лучше через copy) и делаете truncate на текущую таблицу и возвращаете все данные назад из новой таблицы.

Это, конечно, проще, но на больших объемах (сотни миллионов строк) будет большая нагрузка на бд (т.к. все данные будут переноситься в рамках одной транзакции).
Для нас в production окружении это неприемлемо.

Утверждение "Для простоты предположим, что в базе нет входящих foreign key на таблицу task (при их наличии решение задачи немного усложняется)." содержит некое лукавство. Потому как наличие 2-3 слоев зависимостей по ключам приводит к необходимости отдельно собирать идентификаторы строк переносимых из каждой таблицы и заботиться о синхронизации переноса. Сбор этих данных "на лету" начинает очень дорого стоить.

Да, для таких кейсов можно использовать python-библиотеку из другой моей статьи :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий