lesson ── postgres-labs ── ~22 мин ── 5 шагов
UPDATE в PostgreSQL не меняет строку на месте - он пишет новую версию.
HOT-обновление делает это, не трогая индексы, если индексируемые колонки
не изменились и в странице есть место. Сейчас ты поймаешь оба случая по
счётчикам pg_stat_all_tables и увидишь, как fillfactor возвращает
обновления в HOT-русло.
Подключись к базе: psql на клиент-ноде настроен на базу lab.
Перед каждым шагом сначала предскажи, что покажет счётчик, потом проверь.
интерактивный sandbox
Поднимется контейнер postgreslab/postgres-base с PostgreSQL 17 и psql. В браузере откроется терминал, база lab уже настроена. Каждый шаг проверяется автоматически. Сеть air-gapped, наружу контейнер не ходит.
stack ── PostgreSQL 17 · psql · 1 GB RAM · air-gapped · самоуничтожается через 45 мин простоя
CREATE TABLE acc (id int PRIMARY KEY, balance int, note text);
CREATE INDEX acc_note_idx ON acc (note);
INSERT INTO acc VALUES (1, 100, 'alice');
SELECT pg_stat_reset();
Индекс по note нам нужен, чтобы было что «ломать»: пока мы не
трогаем note, обновления остаются HOT.
✓ Таблица и два индекса (pkey + note) на месте, счётчики обнулены.
UPDATE acc SET balance = 200 WHERE id = 1;
SELECT n_tup_upd, n_tup_hot_upd
FROM pg_stat_all_tables WHERE relname = 'acc';
balance не входит ни в один индекс, строка маленькая, место в
странице есть. Предскажи: вырастет ли n_tup_hot_upd?
Статистика обновляется чуть позже коммита - если счётчик ещё ноль, повтори SELECT.
✓ Обновление осталось HOT — индексы не тронуты.
UPDATE acc SET note = 'alice2' WHERE id = 1;
SELECT n_tup_upd, n_tup_hot_upd
FROM pg_stat_all_tables WHERE relname = 'acc';
note входит в acc_note_idx, значит индекс обязан получить новую
запись - это уже не HOT. Предскажи: какой счётчик вырастет, а какой
нет?
✓ n_tup_upd обогнал n_tup_hot_upd — последний UPDATE не был HOT.
Сделаем серию HOT-обновлений и заглянем в страницу через pageinspect.
UPDATE acc SET balance = balance + 1 WHERE id = 1;
UPDATE acc SET balance = balance + 1 WHERE id = 1;
UPDATE acc SET balance = balance + 1 WHERE id = 1;
SELECT balance FROM acc WHERE id = 1; -- чтение может запустить prune
SELECT lp, lp_flags, t_ctid
FROM heap_page_items(get_raw_page('acc', 0));Найди указатели: lp_flags = 1 (LP_NORMAL) ведут к версиям,
lp_flags = 2 (LP_REDIRECT) - след HOT-цепочки, оставленный prune.
Redirect появляется не всегда сразу — он результат внутристраничной очистки.
✓ Страница прочитана — ты видишь указатели и их статусы своими глазами.
fillfactor оставляет в странице резерв под новые версии. Поставим
70% и перепишем таблицу, чтобы настройка подействовала на её страницы.
ALTER TABLE acc SET (fillfactor = 70);
VACUUM FULL acc; -- переписывает таблицу с новым fillfactor
SELECT reloptions FROM pg_class WHERE relname = 'acc';
На живой таблице fillfactor действует только на новые страницы;
VACUUM FULL переписал существующие, так что резерв появился сразу.
✓ fillfactor=70 записан — 30% страницы теперь в резерве под HOT.
HOT-обновление не пишет в индексы, если индексируемые колонки не изменились и версия влезает в страницу. Счётчики n_tup_upd и n_tup_hot_upd показывают, остаётся ли UPDATE в HOT-русле, а fillfactor резервирует место под новые версии.
команды
SELECT n_tup_upd, n_tup_hot_upd FROM pg_stat_all_tables WHERE relname='acc';доля HOT-обновленийALTER TABLE acc SET (fillfactor = 70);резерв под HOT-версииSELECT lp, lp_flags FROM heap_page_items(get_raw_page('acc',0));статусы указателей страницыконцепции