Таблица events с 500 миллионами строк и ростом 2 миллиона в сутки — это проблема, которая становится острее каждый день. SELECT тормозит, VACUUM не успевает, индексы занимают гигабайты. Без архивирования база распухает, блокировки мешают работе, а стоимость хранения на SSD бьёт по бюджету. Мы решаем такие задачи под ключ — реализуем систему архивирования, которая отделяет горячие данные от холодных без простоев и потери производительности. Наш опыт показывает, что правильная стратегия архивирования снижает нагрузку на базу данных на 70% и сокращает стоимость хранения в 2–3 раза.
Какие проблемы решает архивирование старых данных?
Основная боль — деградация производительности. Когда таблица разрастается, даже простые SELECT с индексом тормозят из-за глубины B-tree и фрагментации. VACUUM не успевает чистить мёртвые строки, и autovacuum отстаёт. Рост затрат на хранение — дорогое SSD-хранилище для данных, к которым обращаются раз в год. Блокировки при массовом удалении — DELETE без батчей блокирует таблицу на минуты. Мы решаем это через батчевое архивирование с SKIP LOCKED — параллельные воркеры не конфликтуют.
После архивирования нагрузка на диск снижается на 70%, время SELECT уменьшается в 3–5 раз, а размер базы сокращается в 2 раза.
Стратегии архивирования: сравнение методов
Partition detach — если таблица партиционирована, старые партиции отсоединяются и переносятся в архивную базу или tablespace. Это самый быстрый подход: операция метаданных, без перемещения строк. Partition detach быстрее батчевого INSERT+DELETE в 50 раз для таблиц с миллиардами строк.
INSERT + DELETE батчами — для непартиционированных таблиц. Копируем строки в архивную таблицу батчами, удаляем из основной. Не создаёт длинных транзакций и позволяет контролировать нагрузку.
Логическая реплика — настраиваем publication на основной базе, subscription на архивной, с фильтром по дате. Архив обновляется в реальном времени — подходит для аудита.
Dump + truncate — экспорт в CSV/parquet, удаление из базы. Данные больше не в PostgreSQL/MySQL — только в файловом архиве. Самый дешёвый вариант хранения.
Как выбрать подходящий метод?
| Метод | Скорость | Нагрузка на БД | Сложность |
|---|---|---|---|
| Partition detach | Высокая | Минимальная | Средняя |
| INSERT+DELETE батчами | Средняя | Умеренная | Низкая |
| Логическая реплика | Низкая | Минимальная | Высокая |
| Dump+truncate | Высокая | Высокая | Низкая |
Как мы реализуем архивирование: пошаговая инструкция
Шаг 1: Анализ структуры и нагрузки
Оцениваем объём, скорость роста, частоту запросов к старым данным. Определяем, какие таблицы можно партиционировать.
Шаг 2: Выбор стратегии
По таблице выше определяем оптимальный метод. Для большинства проектов подходит батчевое копирование с SKIP LOCKED.
Шаг 3: Написание функции с SKIP LOCKED
Используем батчи по 10 000 строк с паузой 0.1 с. Функция archive_old_events переносит строки из public.events в archive.events:
-- Архивная таблица (может быть в отдельной схеме или базе) CREATE TABLE archive.events ( LIKE public.events INCLUDING ALL ); -- Функция архивирования с батчами CREATE OR REPLACE FUNCTION archive_old_events( p_before_date TIMESTAMPTZ, p_batch_size INTEGER DEFAULT 10000 ) RETURNS TABLE(batches_processed INTEGER, rows_archived BIGINT) LANGUAGE plpgsql AS $$ DECLARE v_batches INTEGER := 0; v_total BIGINT := 0; v_moved INTEGER; BEGIN LOOP -- Переносим один батч в архив WITH moved AS ( DELETE FROM public.events WHERE id IN ( SELECT id FROM public.events WHERE created_at < p_before_date LIMIT p_batch_size FOR UPDATE SKIP LOCKED -- пропускаем заблокированные строки ) RETURNING * ) INSERT INTO archive.events SELECT * FROM moved; GET DIAGNOSTICS v_moved = ROW_COUNT; EXIT WHEN v_moved = 0; v_batches := v_batches + 1; v_total := v_total + v_moved; -- Пауза между батчами — не перегружаем диск PERFORM pg_sleep(0.1); -- Прогресс RAISE NOTICE 'Batch %: % rows archived (total: %)', v_batches, v_moved, v_total; END LOOP; RETURN QUERY SELECT v_batches, v_total; END $$; Запуск:
SELECT * FROM archive_old_events('давняя_дата'::timestamptz, 10000); Шаг 4: Настройка планировщика
Artisan-команда запускается ежемесячно в 2:00:
// app/Console/Commands/ArchiveOldData.php class ArchiveOldData extends Command { protected $signature = 'db:archive {--days=365 : Архивировать данные старше N дней}'; protected $description = 'Archive old records to archive tables'; public function handle(): int { $beforeDate = now()->subDays($this->option('days'))->toDateTimeString(); $this->info("Archiving events before {$beforeDate}..."); $result = DB::selectOne( 'SELECT * FROM archive_old_events(?::timestamptz, 5000)', [$beforeDate] ); $this->info("Done: {$result->batches_processed} batches, {$result->rows_archived} rows"); // VACUUM после массового удаления DB::statement('VACUUM ANALYZE events'); return self::SUCCESS; } } // app/Console/Kernel.php $schedule->command('db:archive --days=180') ->monthlyOn(1, '02:00') ->withoutOverlapping() ->onFailure(fn() => Notification::route('telegram', config('services.telegram.ops_chat')) ->notify(new ArchivingFailedNotification())); Шаг 5: Мониторинг и VACUUM
После архивации выполняем VACUUM ANALYZE. Настраиваем алерты на ошибки через Telegram.
Шаг 6: Восстановление из архива
Из архивной таблицы — ATTACH PARTITION к основной без копирования. Из CSV-файлов — COPY-загрузка.
Как восстановить данные из архива?
Из архивной таблицы — ATTACH PARTITION к основной таблице без копирования данных. Из CSV-файлов — COPY-загрузка:
# Восстановить данные из CSV-архива обратно в базу gunzip -c /mnt/archive/events/2024-01/events_2024-01.csv.gz | \ psql -d mydb -c "COPY events FROM STDIN CSV HEADER" Для долгосрочного хранения используем файловый архив с ротацией.
Что входит в работу
- Анализ структуры таблиц и нагрузки
- Проектирование схемы архива (партиционирование, отдельная БД или файлы)
- Написание функций/скриптов архивации
- Настройка планировщика и мониторинга
- Политика хранения с ротацией
- Сценарий восстановления из архива
- Документация процесса и обучение команды
Сроки реализации
| Этап | Срок |
|---|---|
| Анализ и проектирование | 0.5 дня |
| Реализация функции архивации | 1–1.5 дня |
| Настройка планировщика и мониторинга | 0.5 дня |
| Документация и обучение | 0.5 дня |
| Общий срок | 2.5–3.5 дня |
Типичные ошибки
- Забыть про
VACUUMпосле массового удаления — таблица раздувается. - Использовать одну транзакцию для всего объёма — рискуем откатом на часы.
- Не проверять архив перед удалением — потеря данных.
- Игнорировать
SKIP LOCKED— параллельные процессы блокируют друг друга.
Почему стоит доверить архивирование нам
У нас более 10 лет опыта в администрировании PostgreSQL и MySQL. Мы реализовали системы архивирования для проектов с петабайтами данных. Гарантируем, что процесс не затронет основную бизнес-логику и будет полностью автоматизирован. Свяжитесь с нами — мы оценим ваш проект и предложим оптимальное решение. Получите консультацию инженера по производительности БД — бесплатно.
Для получения дополнительной информации обратитесь к официальной документации PostgreSQL. Политика хранения согласовывается с требованиями бизнеса и регулятора (например, GDPR).







