Описание


Инструмент PGHist ведет историю изменений таблиц и позволяет получить лог(аудит) изменений по строкам, список изменений по полям с указанием пользователя, времени изменения, SQL-запроса, транзакции, другой технической информации и таблицу по состоянию на момент времени в прошлом (версионирование). Для отображения информации в пользовательском интерфейсе возможно определить SQL-выражения для описания изменных строк и полей таблицы, а также переопределить функции названия операции и имени пользователя.


Устройство и принцип работы



PGHist - это схема с процедурами и общими таблицами: транзакции, SQL-выражения. При включении ведения истории (процедура pghist.hist_enable) для указанной таблицы создаются дополнительная таблица, триггеры на insert,update,delete,truncate, хранимые процедуры и представление получения данных. При изменении таблицы срабатывают триггеры, которые сохраняют старые значения, первичный ключ и ссылку на SQL-выражение в таблицу истории. Также имеются событийные триггеры на DDL-команды, которые перестраивают таблицу истории и пересоздают хранимые процедуры.


Основные функции и представление



pghist.hist_enable([schema],[table]) включение ведения истории
[schema].[table]_hist лог(аудит) изменений по строкам, оптимизирован для анализа
[schema].[table]_changes список изменений по полям, оптимизирован для показа пользователю
[schema].[table]_at_timestamp таблица по состоянию на момент времени (версионирование)

Более подробно на странице Документация


Простой пример


-- Создаем таблицу
create table example(
  id int primary key,
  name varchar(20),
  number numeric(10,2),
  date date
);

-- Включаем ведение истории
call pghist.hist_enable('example');

-- Изменяем данные 
insert into example values (1, 'Пример', 10, current_date);
update example set number=20, date=date-1;

-- Получаем лог изменений по строкам
select * from example_hist;

-- Получаем изменения по полям
select * from example_changes();

-- Получаем таблицу в прошлом
select * from example_at_timestamp(now()-interval '10 second');

-- drop table example cascade;


Ключевые особенности



Ограничения



Тест производительности (benchmark)


При выполнении одной DML-операции триггер уровня инструкции (for each statement) фиксирует изменения и распределяет их по 4 таблицам.

Распределение записей по таблицам
ТаблицаКол-во записейЗаписываемые данные
Транзакции
pghist.hist_transaction
1Общая информация по операции (время, пользователь, IP-адрес и т.д.)
SQL-выражения
pghist.hist_statement
1Идентификатор и информация по SQL-выражению
Тексты SQL-запросов
pghist.hist_query
0 или 1Исходный код SQL-запроса без дублирования
Таблица истории
pghist.data$[schema]_[table]
кол-во строк
в SQL-запросе
Первичный ключ, внешний ключ на master-таблицу (опционально) и только изменившиеся данные из строк, попавших в переходную таблицу триггера (new table/old table)

Соответственно, при изменении 1 строки в целевой таблице будет записано 4 дополнительные строки, при изменении 1000 строк - 1003 дополнительные строки.

Ожидаемые результаты тестирования: значительное снижение скорости записи и отсутствие влияния на чтение.
Особо следует отметить, что тестирование производительности замеряет исключительно показатели базы данных и их надо соотносить со временем сетевого взаимодействия, работы бизнес-логики (например, пересчет остатков при изменении товарной накладной или сложное заполнение автополей).

Bash-скрипт замера производительности с использованием утилиты pgbench


#!/bin/bash
set -e
set -x

# Test configuration
DB=benchmark
OPTIONS="-j $(nproc) -c $(( $(nproc)*4 )) -t 1000" 

# Recreate DB, init pghist extension
psql -q -c "drop database if exists $DB" -c "create database $DB"
psql $DB -q -f pghist_init.sql
pgbench $DB -i

# Run baseline tests
pgbench $DB $OPTIONS
pgbench $DB $OPTIONS -b select-only

# Enable history tracking on target tables
psql $DB -q -c "call pghist.hist_enable('pgbench_accounts')"
psql $DB -q -c "call pghist.hist_enable('pgbench_branches')"
psql $DB -q -c "call pghist.hist_enable('pgbench_tellers')"

# Run post-pghist tests
pgbench $DB $OPTIONS
pgbench $DB $OPTIONS -b select-only

Лог тестирования


postgres@db-debian:~$ ./benchmark.sh
+ DB=benchmark
++ nproc
++ nproc
+ OPTIONS='-j 2 -c 8 -t 1000'
+ psql -q -c 'drop database if exists benchmark' -c 'create database benchmark'
+ psql benchmark -q -f pghist_init.sql
+ pgbench benchmark -i
dropping old tables...
creating tables...
generating data (client-side)...
vacuuming...
creating primary keys...
done in 1.00 s (drop tables 0.00 s, create tables 0.02 s, client-side generate 0.57 s, vacuum 0.22 s, primary keys 0.20 s).
+ pgbench benchmark -j 2 -c 8 -t 1000
pgbench (17.10 (Debian 17.10-1.pgdg13+1))
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 2
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 8000/8000
number of failed transactions: 0 (0.000%)
latency average = 27.442 ms
initial connection time = 34.556 ms
tps = 291.529019 (without initial connection time)
+ pgbench benchmark -j 2 -c 8 -t 1000 -b select-only
pgbench (17.10 (Debian 17.10-1.pgdg13+1))
starting vacuum...end.
transaction type: <builtin: select only>
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 2
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 8000/8000
number of failed transactions: 0 (0.000%)
latency average = 0.899 ms
initial connection time = 39.618 ms
tps = 8896.985145 (without initial connection time)
+ psql benchmark -q -c 'call pghist.hist_enable('\''pgbench_accounts'\'')'
+ psql benchmark -q -c 'call pghist.hist_enable('\''pgbench_branches'\'')'
+ psql benchmark -q -c 'call pghist.hist_enable('\''pgbench_tellers'\'')'
+ pgbench benchmark -j 2 -c 8 -t 1000
pgbench (17.10 (Debian 17.10-1.pgdg13+1))
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 2
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 8000/8000
number of failed transactions: 0 (0.000%)
latency average = 51.236 ms
initial connection time = 31.243 ms
tps = 156.139254 (without initial connection time)
+ pgbench benchmark -j 2 -c 8 -t 1000 -b select-only
pgbench (17.10 (Debian 17.10-1.pgdg13+1))
starting vacuum...end.
transaction type: <builtin: select only>
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 2
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 8000/8000
number of failed transactions: 0 (0.000%)
latency average = 1.025 ms
initial connection time = 32.404 ms
tps = 7801.795388 (without initial connection time)

Скорость записи упала в ~2 раза, скорость чтения не изменилась, ошибок не было

Выводы



Схема данных


Схема данных



PGHist vs pgAudit, temporal_tables, Logidze, supa_audit


Сравнительный анализ архитектур


СвойствоPGHistpgAudittemporal_tablesJSONB-триггеры
(Logidze, supa_audit)
Место храненияСтруктурированные реляционные таблицыСтандартный лог сервера PostgreSQLОтдельная таблица аналогичная целевойОбщая таблица или дополнительное поле
Структура данныхПервичный ключ и изменившиеся поляЛог-запись: SQL-выражение с параметрами, пользователь и т.д.Копия строки с периодом версионированияJSON-дельты изменений
Язык реализацииPL/pgSQLСиСиPL/pgSQL,
Ruby on Rails
Метод перехватаDML и событийные триггерыТочки перехвата ядраТриггеры расширенияDML-триггеры
Безопасность и праваИзменения хранятся в отдельной схеме и доступны только через защищенные представления (security definer)Файлы логов защищены правами операционной системыПрава доступа к архивной таблице настраивает администраторПользователи могут изменять общую таблицу или лог-поле таблицы

Сравнение функционала


ФункционалPGHistpgAudittemporal_tablesJSONB-триггеры
(Logidze, supa_audit)
Cписок изменений
для пользователя
✅ Полный
с описанием значений
❌ Отсутствует⚠️ Ограниченный
данные и время
⚠️ Полный
парсинг JSON
Аудит безопасности⚠️ Только запись✅ Полный❌ Отсутствует⚠️ Только запись
Данные в прошлом
(версионирование)
⚠️ Длительная подготовка❌ Нет данных✅ Готовы сразу⚠️ JSON-формат
Контекст
выполнения
✅ Полный
строка→запрос→транзакция
⚠️ Ограниченный❌ Отсутствует⚠️ Ограниченный
Накладные расходы при
единичных изменениях
❌ Значительные
4 строки в 4 таблицах
✅ Минимальные✅ Минимальные
копирование строки
⚠️ Умеренные
Накладные расходы
при bulk-операциях
⚠️ Умеренные
триггеры for each statement
✅ Минимальные⚠️ Умеренные
дублирование данных
⚠️ Умеренные
Простота установки
и переносимость
✅ Высокая
отдельная SQL-схема
⚠️ Средняя
стандартное расширение
❌ Низкая
необходима компиляция
✅ Высокая
универсальный триггер
Поддержка DDL✅ Полная
событийные триггеры
⚠️ Частичная
только факт DDL
❌ Отсутствует⚠️ Частичная
Безопасность✅ Изолированная
доступ через представления
✅ Абсолютная
вне базы данных
⚠️ Условная
ручная настройка
❌ Низкая
нет разграничения прав

Выводы




Видео


Обзорное видео размещено на YouTube, RUTUBE