Установим для демонстрации параметры заморозки.
Небольшой возраст транзакции:
=> ALTER SYSTEM SET vacuum_freeze_min_age = 1;
ALTER SYSTEM
Возраст, после которого будет выполняться заморозка всех страниц:
=> ALTER SYSTEM SET vacuum_freeze_table_age = 3;
ALTER SYSTEM
И отключим автоматическую очистку, чтобы запускать ее вручную в нужный момент.
=> ALTER SYSTEM SET autovacuum = off;
ALTER SYSTEM
=> SELECT pg_reload_conf();
pg_reload_conf ---------------- t (1 row)
Создадим таблицу с данными. Установим минимальный fillfactor: на каждой странице будет всего две строки.
=> CREATE DATABASE mvcc_freeze;
CREATE DATABASE
=> \c mvcc_freeze
You are now connected to database "mvcc_freeze" as user "postgres".
=> CREATE TABLE t(id integer, s char(300)) WITH (fillfactor = 10);
CREATE TABLE
Создадим представление для того, чтобы наблюдать за битами-подсказками на первых двух страницах таблицы.
Сейчас нас интересует только xmin и биты, которые относятся к нему, поскольку версии строк с ненулевым xmax будут очищены. Кроме того, выведем и возраст транзакции xmin.
=> CREATE EXTENSION pageinspect;
CREATE EXTENSION
=> CREATE VIEW t_v AS SELECT '('||blkno||','||lp||')' as ctid, CASE lp_flags WHEN 0 THEN 'unused' WHEN 1 THEN 'normal' WHEN 2 THEN 'redirect to '||lp_off WHEN 3 THEN 'dead' END AS state, t_xmin AS xmin, age(t_xmin) AS xmin_age, CASE WHEN (t_infomask & 256) > 0 THEN 't' END AS xmin_c, CASE WHEN (t_infomask & 512) > 0 THEN 't' END AS xmin_a, t_xmax AS xmax, t_ctid FROM ( SELECT 0 blkno, * FROM heap_page_items(get_raw_page('t',0)) UNION ALL SELECT 1 blkno, * FROM heap_page_items(get_raw_page('t',1)) ) q ORDER BY blkno, lp;
CREATE VIEW
Для того, чтобы заглянуть в карту видимости и заморозки, воспользуемся еще одним расширением:
=> CREATE EXTENSION pg_visibility;
CREATE EXTENSION
Вставляем данные. Сразу выполним очистку, чтобы заполнить карту видимости.
=> INSERT INTO t(id, s) SELECT g.id, 'FOO' FROM generate_series(1,100) g(id);
INSERT 0 100
=> VACUUM t;
VACUUM
Сразу после очистки обе страницы отмечены в карте видимости (all_visible):
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('t',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | f (2 rows)
Каков возраст транзакции, создавшей строки?
=> SELECT * FROM t_v;
ctid | state | xmin | xmin_age | xmin_c | xmin_a | xmax | t_ctid -------+--------+------+----------+--------+--------+------+-------- (0,1) | normal | 655 | 1 | t | | 0 | (0,1) (0,2) | normal | 655 | 1 | t | | 0 | (0,2) (1,1) | normal | 655 | 1 | t | | 0 | (1,1) (1,2) | normal | 655 | 1 | t | | 0 | (1,2) (4 rows)
Возраст равен 1; версии строк с такой транзакцией еще не будут заморожены.
Обновим строку на нулевой странице. Новая версия попадет на ту же страницу благодаря небольшому значению fillfactor.
=> UPDATE t SET s = 'BAR' WHERE id = 1;
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmin_age | xmin_c | xmin_a | xmax | t_ctid -------+--------+------+----------+--------+--------+------+-------- (0,1) | normal | 655 | 2 | t | | 656 | (0,3) (0,2) | normal | 655 | 2 | t | | 0 | (0,2) (0,3) | normal | 656 | 1 | | | 0 | (0,3) (1,1) | normal | 655 | 2 | t | | 0 | (1,1) (1,2) | normal | 655 | 2 | t | | 0 | (1,2) (5 rows)
Сейчас нулевая страница уже будет обработана заморозкой:
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('t',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | f | f 1 | t | f (2 rows)
Выполняем очистку.
=> VACUUM t;
VACUUM
=> SELECT * FROM t_v;
ctid | state | xmin | xmin_age | xmin_c | xmin_a | xmax | t_ctid -------+---------------+------+----------+--------+--------+------+-------- (0,1) | redirect to 3 | | | | | | (0,2) | normal | 655 | 2 | t | t | 0 | (0,2) (0,3) | normal | 656 | 1 | t | | 0 | (0,3) (1,1) | normal | 655 | 2 | t | | 0 | (1,1) (1,2) | normal | 655 | 2 | t | | 0 | (1,2) (5 rows)
Очистка обработала измененную страницу. У одной версии строки установлены оба бита - это признак заморозки. Другая версия строки слишком молода и поэтому не была заморожена.
Теперь обе страницы отмечены в карте видимости (все версии строк на них актуальны). Очистка теперь не будет обрабатывать ни одну из этих страниц, и незамороженные версии строк так и останутся незамороженными.
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('t',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | f (2 rows)
Именно для такого случая и требуется параметр vacuum_freeze_table_age, определяющий, в какой момент нужно просмотреть страницы, отмеченные в карте видимости, если они не отмечены в карте заморозки.
Для каждой таблицы сохраняется номер самой последней (наименее старой) замороженной транзакции. Ее возраст и сравнивается со значением параметра.
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 't';
relfrozenxid | age --------------+----- 655 | 2 (1 row)
Сымитируем выполнение еще одной транзакции, чтобы возраст relfrozenxid таблицы достиг значения параметра vacuum_freeze_table_age.
=> SELECT txid_current();
txid_current -------------- 657 (1 row)
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 't';
relfrozenxid | age --------------+----- 655 | 3 (1 row)
=> VACUUM t;
VACUUM
Теперь, поскольку гарантированно была проверена вся таблица, номер замороженной транзакции можно увеличить - мы уверены, что в страницах не осталось более старой незамороженной транзакции.
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 't';
relfrozenxid | age --------------+----- 657 | 1 (1 row)
Вот что получилось в страницах:
=> SELECT * FROM t_v;
ctid | state | xmin | xmin_age | xmin_c | xmin_a | xmax | t_ctid -------+---------------+------+----------+--------+--------+------+-------- (0,1) | redirect to 3 | | | | | | (0,2) | normal | 655 | 3 | t | t | 0 | (0,2) (0,3) | normal | 656 | 2 | t | t | 0 | (0,3) (1,1) | normal | 655 | 3 | t | t | 0 | (1,1) (1,2) | normal | 655 | 3 | t | t | 0 | (1,2) (5 rows)
Обе страницы теперь отмечены в карте заморозки.
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('t',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | t 1 | t | t (2 rows)
Номер последней замороженной транзакции есть и на уровне всей БД:
=> SELECT datfrozenxid, age(datfrozenxid) FROM pg_database WHERE datname = 'postgres';
datfrozenxid | age --------------+----- 548 | 110 (1 row)
Он устанавливается в минимальное значение из relfrozenxid всех таблиц этой БД. Если возраст datfrozenxid превысит значение параметра autovacuum_freeze_max_age, автоочистка будет запущена принудительно.
Конец демонстрации.