Создаем таблицу и загружаем несколько строк в одной и той же транзакции:
=> CREATE DATABASE mvcc_freeze;
CREATE DATABASE
=> \c mvcc_freeze
You are now connected to database "mvcc_freeze" as user "postgres".
=> BEGIN;
BEGIN
=> CREATE TABLE t(n integer);
CREATE TABLE
=> COPY t FROM stdin WITH FREEZE;
=> 1
=> 2
=> 3
=> \.
COPY 3
=> COMMIT;
COMMIT
Проверяем версии строк:
=> CREATE EXTENSION pageinspect;
CREATE EXTENSION
=> CREATE VIEW t_v AS SELECT '(0,'||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 heap_page_items(get_raw_page('t',0)) ORDER BY lp;
CREATE VIEW
=> SELECT * FROM t_v;
ctid | state | xmin | xmin_age | xmin_c | xmin_a | xmax | t_ctid -------+--------+-------+----------+--------+--------+------+-------- (0,1) | normal | 55566 | 3 | t | t | 0 | (0,1) (0,2) | normal | 55566 | 3 | t | t | 0 | (0,2) (0,3) | normal | 55566 | 3 | t | t | 0 | (0,3) (3 rows)
В другом сеансе начнем транзакцию с уровнем изоляции Repeatable Read.
=> \c mvcc_freeze
You are now connected to database "mvcc_freeze" as user "postgres".
=> BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN
=> SELECT txid_current();
txid_current -------------- 55569 (1 row)
Обратите внимание, что эта транзакция не должна обращаться к таблице t.
Теперь опустошим таблицу и загрузим в нее новые строки в одной транзакции. Если бы параллельная транзакция прочитала содержимое t, команда TRUNCATE ожидала бы ее завершения.
=> BEGIN;
BEGIN
=> TRUNCATE t;
TRUNCATE TABLE
=> COPY t FROM stdin WITH FREEZE;
=> 10
=> 20
=> 30
=> \.
COPY 3
=> COMMIT;
COMMIT
Теперь параллельная транзакция видит новые данные, хотя это и нарушает изоляцию:
=> SELECT * FROM t;
n ---- 10 20 30 (3 rows)
=> COMMIT;
COMMIT
=> \q
Предварительно заморозим все транзакции во всех базах. Для этого удобно воспользоваться командой vacuumdb:
student$ vacuumdb --all --freeze
vacuumdb: vacuuming database "mvcc_freeze" vacuumdb: vacuuming database "postgres" vacuumdb: vacuuming database "template1"
Максимальный возраст незамороженных транзакций по всем БД:
=> SELECT datname, datfrozenxid, age(datfrozenxid) FROM pg_database;
datname | datfrozenxid | age -------------+--------------+------- postgres | 55571 | 0 mvcc_freeze | 55571 | 0 template1 | 55571 | 0 template0 | 548 | 55023 (4 rows)
Отключаем автоочистку.
=> ALTER SYSTEM SET autovacuum = off;
ALTER SYSTEM
Уменьшаем значения параметров:
=> ALTER SYSTEM SET vacuum_freeze_min_age = 1000;
ALTER SYSTEM
=> ALTER SYSTEM SET vacuum_freeze_table_age = 10000;
ALTER SYSTEM
=> ALTER SYSTEM SET autovacuum_freeze_max_age = 100000;
ALTER SYSTEM
Требуется перезагрузка сервера.
postgres$ pg_ctl -w -l /home/postgres/logfile -D /usr/local/pgsql/data restart
waiting for server to shut down.... done server stopped waiting for server to start.... done server started
student$ psql mvcc_freeze
Получить большое количество транзакций можно разными способами; например, можно воспользоваться утилитой pgbench. Попросим ее инициализировать свои таблицы и выполнить 100000 транзакций.
student$ pgbench -i mvcc_freeze
NOTICE: table "pgbench_history" does not exist, skipping NOTICE: table "pgbench_tellers" does not exist, skipping NOTICE: table "pgbench_accounts" does not exist, skipping NOTICE: table "pgbench_branches" does not exist, skipping creating tables... 100000 of 100000 tuples (100%) done (elapsed 0.14 s, remaining 0.00 s) vacuum... set primary keys... done.
student$ pgbench -t 100000 -P 5 mvcc_freeze
starting vacuum...end. progress: 5.0 s, 932.6 tps, lat 1.072 ms stddev 0.384 progress: 10.0 s, 946.6 tps, lat 1.057 ms stddev 0.362 progress: 15.0 s, 942.6 tps, lat 1.061 ms stddev 0.378 progress: 20.0 s, 936.8 tps, lat 1.068 ms stddev 0.387 progress: 25.0 s, 928.6 tps, lat 1.077 ms stddev 0.610 progress: 30.0 s, 936.0 tps, lat 1.068 ms stddev 0.278 progress: 35.0 s, 923.2 tps, lat 1.083 ms stddev 0.664 progress: 40.0 s, 943.4 tps, lat 1.060 ms stddev 0.376 progress: 45.0 s, 927.8 tps, lat 1.078 ms stddev 0.377 progress: 50.0 s, 934.0 tps, lat 1.071 ms stddev 0.341 progress: 55.0 s, 930.6 tps, lat 1.074 ms stddev 0.360 progress: 60.0 s, 890.2 tps, lat 1.123 ms stddev 1.717 progress: 65.0 s, 911.8 tps, lat 1.097 ms stddev 0.724 progress: 70.0 s, 926.4 tps, lat 1.079 ms stddev 0.355 progress: 75.0 s, 929.6 tps, lat 1.076 ms stddev 0.391 progress: 80.0 s, 919.8 tps, lat 1.087 ms stddev 0.386 progress: 85.0 s, 910.8 tps, lat 1.098 ms stddev 0.456 progress: 90.0 s, 914.6 tps, lat 1.093 ms stddev 0.350 progress: 95.0 s, 932.0 tps, lat 1.073 ms stddev 0.391 progress: 100.0 s, 932.4 tps, lat 1.073 ms stddev 0.248 progress: 105.0 s, 930.8 tps, lat 1.074 ms stddev 0.346 transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 number of transactions per client: 100000 number of transactions actually processed: 100000/100000 latency average = 1.079 ms latency stddev = 0.561 ms tps = 927.106045 (including connections establishing) tps = 927.125369 (excluding connections establishing)
Видно, что возраст незамороженных транзакций превышает установленное пороговое значение (100000):
=> SELECT datname, datfrozenxid, age(datfrozenxid) FROM pg_database;
datname | datfrozenxid | age -------------+--------------+-------- postgres | 55571 | 100013 mvcc_freeze | 55571 | 100013 template1 | 55571 | 100013 template0 | 131261 | 24323 (4 rows)
Теперь при выполнении команды VACUUM для любой таблицы будет запущен процесс автоочистки.
=> VACUUM t;
VACUUM
Среди процессов появился autovacuum worker:
postgres$ ps -o pid,command --ppid `head -n 1 /usr/local/pgsql/data/postmaster.pid`
PID COMMAND 26360 postgres: checkpointer process 26361 postgres: writer process 26362 postgres: wal writer process 26363 postgres: stats collector process 26364 postgres: bgworker: logical replication launcher 26389 postgres: postgres mvcc_freeze [local] idle 26487 postgres: autovacuum worker process postgres
И через некоторое время транзакции окажутся замороженными:
=> SELECT datname, datfrozenxid, age(datfrozenxid) FROM pg_database;
datname | datfrozenxid | age -------------+--------------+------- postgres | 154584 | 1000 mvcc_freeze | 154584 | 1000 template1 | 155584 | 0 template0 | 131261 | 24323 (4 rows)