Навіть транзакція, що лише читає і не тримає конфліктних блокувань, впливає на всю базу через MVCC.
Горизонт вакууму. VACUUM може прибрати мертву версію рядка, лише якщо вона невидима жодній активній транзакції. Найстаріша відкрита транзакція (чи знімок) встановлює горизонт: усі версії, що з'явилися після її початку, захищені від очищення - у всіх таблицях бази, не лише тих, що вона читала.
Що відбувається, поки транзакція висить годинами:
- Мертві версії накопичуються в таблицях з частими оновленнями (черги, лічильники, сесії).
- Таблиці й індекси роздуваються, запити сповільнюються - бо читають сторінки, повні мертвих рядків.
- Autovacuum працює, але марно: «не може видалити, бо ще потрібні».
- Після закриття транзакції роздуття саме не зникає - місце повторно використовується, але файли лишаються великими.
Джерела довгих транзакцій:
idle in transaction- застосунок відкрив транзакцію й робить щось повільне поза базою (HTTP-запит, обробка файлу) або впав, не закривши її.- Довгі звіти й аналітика на primary.
- Репліка з
hot_standby_feedback = on, де йде довгий запит, - вона теж тримає горизонт на primary. - Забуті prepared transactions і неактивні слоти реплікації (утримують WAL і горизонт).
Як знайти:
SELECT pid, state, now() - xact_start AS duration, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start
LIMIT 10;
Захист:
idle_in_transaction_session_timeout- розривати сесії, що зависли в транзакції.transaction_timeout(PostgreSQL 17+) - обмеження на тривалість усієї транзакції.- Аналітику - на репліку без
hot_standby_feedbackчи в окреме сховище. - У коді - не робити повільних зовнішніх дій усередині
DB::transaction().
Докладніше в документації: Налаштування клієнтських з'єднань