Заморозка при COPY WITH FREEZE

Создаем таблицу и загружаем несколько строк в одной и той же транзакции:

=> 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)

COPY WITH FREEZE и изоляция

В другом сеансе начнем транзакцию с уровнем изоляции 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)