Архівування даних підвищує продуктивність бази даних за рахунок винесення старих даних БД в архівне сховище. Таблиця events з 500 мільйонами рядків та зростанням на 2 мільйони на добу — це проблема, яка стає гострішою щодня. SELECT гальмує, VACUUM не встигає, індекси займають гігабайти. Без архівування база розпухає, блокування заважають роботі, а вартість зберігання на SSD б'є по бюджету. Ми вирішуємо такі завдання під ключ — реалізуємо систему архівування, яка відокремлює гарячі дані від холодних без простоїв та втрати продуктивності. Наш досвід показує, що правильна стратегія архівування знижує навантаження на базу даних на 70% та скорочує вартість зберігання в 2–3 рази. Економія на зберіганні після архівування складає в середньому $400 на місяць для проєктів з 1 ТБ даних.
Які проблеми вирішує архівування старих даних?
Основний біль — деградація продуктивності. Коли таблиця розростається, навіть прості SELECT з індексом гальмують через глибину B-tree та фрагментацію. VACUUM не встигає чистити мертві рядки, і autovacuum відстає. Зростання витрат на зберігання — дороге SSD-сховище для даних, до яких звертаються раз на рік. Блокування при масовому видаленні — DELETE без батчів блокує таблицю на хвилини. Ми вирішуємо це через пакетне архівування з SKIP LOCKED — паралельні воркери не конфліктують.
Після архівування навантаження на диск знижується на 70%, час SELECT зменшується в 3–5 разів, а розмір бази скорочується в 2 рази.
Стратегії архівування: порівняння методів
Partition detach — якщо таблиця партиціонована, старі партиції від'єднуються та переносяться до архівної бази або tablespace. Це найшвидший підхід: операція метаданих, без переміщення рядків. Partition detach швидший за пакетний INSERT+DELETE у 50 разів для таблиць з мільярдами рядків. Пакетний INSERT+DELETE з SKIP LOCKED кращий за звичайний DELETE в 10 разів за швидкістю без блокувань.
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).







