Модуль pageinspect: примеры использования
В статье рассматриваются основные функции pageinspect и принципы их работы.
Подробнее о модуле pageinspect читайте в документации.
Ограничения и область применения
Для воспроизведения шагов описанных в данной статье, необходимо установить расширение pageinspect:
create extension pageinspect;
В качестве примера будут использоваться таблицы test_pango и test_index_pango:
create table test_pango(id int primary key, name char(10));
insert into test_pango select x, concat('A', cast(x as char(9))) from generate_series(1, 5) x;
create table test_index_pango(id int, name text);
insert into test_index_pango select x, repeat(cast(x as text), 2500) from generate_series(1, 2000) x;
create index t_idx on test_index_pango(name) with(fillfactor = 10);
-- в каждом блоке индекса помещается 5-6 строк
Устройство страниц PostgreSQL
Структура страниц
Страницы PostgreSQL имеют разделы:
- заголовок;
- массив указателей на версии строк;
- свободное пространство;
- версии строк;
- специальную область.
На низком уровне структуру страницы можно увидеть при помощи функции pageinspect – get_raw_page, или утилитой ОС xxd.
Функция get_raw_page считывает указанный блок отношения и возвращает копию значения bytea (на данный момент таблица test_pango содержит 5 записей, которые уместились на 0 странице):
select get_raw_page('test_pango', 0); -- Вывод get_raw_page отредактирован для удобства сравнения с выводом утилиты xxd,
пустое пространство, состоящее из 0, заменено на ...
\x0300 0000 2024 019e 0000 0000 2c00 381f
0020 0420 0000 0000 d89f 4e00 b09f 4e00
889f 4e00 609f 4e00 389f 4e00 0000 0000
0000 0000 0000 0000 1517 0000 0000 0000
0000 0000 0000 0000 0500 0200 0208 1800
0500 0000 1741 3520 2020 2020 2020 2000
1517 0000 0000 0000 0000 0000 0000 0000
0400 0200 0208 1800 0400 0000 1741 3420
2020 2020 2020 2000 1517 0000 0000 0000
0000 0000 0000 0000 0300 0200 0208 1800
0300 0000 1741 3320 2020 2020 2020 2000
1517 0000 0000 0000 0000 0000 0000 0000
0200 0200 0208 1800 0200 0000 1741 3220
2020 2020 2020 2000 1517 0000 0000 0000
0000 0000 0000 0000 0100 0200 0208 1800
0100 0000 1741 3120 2020 2020 2020 2000
Ту же информацию по странице можно получить воспользовавшись утилитой ОС xxd:
xxd -a /pgdata/05/data/base/14422/relfilenode_test_pango
00000000: 0300 0000 2024 019e a2a4 0000 2c00 381f .... $......,.8.
00000010: 0020 0420 0000 0000 d89f 4e00 b09f 4e00 . . ......N...N.
00000020: 889f 4e00 609f 4e00 389f 4e00 0000 0000 ..N.`.N.8.N.....
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
*
00001f30: 0000 0000 0000 0000 1517 0000 0000 0000 ................
00001f40: 0000 0000 0000 0000 0500 0200 0208 1800 ................
00001f50: 0500 0000 1741 3520 2020 2020 2020 2000 .....A5 .
00001f60: 1517 0000 0000 0000 0000 0000 0000 0000 ................
00001f70: 0400 0200 0208 1800 0400 0000 1741 3420 .............A4
00001f80: 2020 2020 2020 2000 1517 0000 0000 0000 .........
00001f90: 0000 0000 0000 0000 0300 0200 0208 1800 ................
00001fa0: 0300 0000 1741 3320 2020 2020 2020 2000 .....A3 .
00001fb0: 1517 0000 0000 0000 0000 0000 0000 0000 ................
00001fc0: 0200 0200 0208 1800 0200 0000 1741 3220 .............A2
00001fd0: 2020 2020 2020 2000 1517 0000 0000 0000 .........
00001fe0: 0000 0000 0000 0000 0100 0200 0208 1800 ................
00001ff0: 0100 0000 1741 3120 2020 2020 2020 2000 .....A1 .
Заголовок страницы
Заголовок страницы располагается в младших адресах страницы и имеет фиксированные размер – 24 байта.
Структура заголовка выглядит следующим образом (файл src/include/storage/bufpage.h):
typedef struct PageHeaderData
{
/* XXX LSN является членом *любого* блока, а не только страниц */
PageXLogRecPtr pd_lsn; /* LSN: следующий байт после последнего байта записи xlog
* для последнего изменения этой страницы */
uint16 pd_checksum; /* контрольная сумма */
uint16 pd_flags; /* биты флагов, см. ниже */
LocationIndex pd_lower; /* смещение начала свободного пространства */
LocationIndex pd_upper; /* смещение конца свободного пространства */
LocationIndex pd_special; /* смещение начала специальной области */
uint16 pd_pagesize_version;
TransactionId pd_prune_xid; /* старейший XID, который можно очистить, или 0, если нет */
ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* массив указателей строк */
} PageHeaderData;
Функция page_header принимает в качестве аргумента образ кучи или индекса, полученный в результате вызова get_raw_page и выводит заголовок страницы:
select * from page_header(get_raw_page('test_pango', 0));
lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid
------------+----------+-------+-------+-------+---------+----------+---------+-----------
3/9E012420 | 0 | 0 | 44 | 7992 | 8192 | 8192 | 4 | 0
(1 row)
lsn– указатель в WAL на первый байт после последней записи, менявшей данную страницу;checksum– контрольная сумма;flags– битовые флаги (4-all_visible);lower– смещение начала свободного пространства (от0доlowerнаходятся заголовок (24 байта) + массив указателей на версии строк (каждый указатель 4 байта));upper– смещение конца свободного пространства;special– смещение начала специальной области (междуupperиspecialлежат версии строк);pagesize– размер страницы;version– версия страницы;prune_xid– самый старый XID среди потенциально подлежащих удалению кортежей на странице.
Возвращаемые столбцы соответствуют полям в структуре PageHeaderData.
При желании вывод утилиты xxd или функции get_raw_page можно расшифровать так, как это делает утилита page_header.
Заголовок располагается в младших адресах станицы, первые 24 байта:
0300 0000 2024 019e 0000 0000 2c00 381f
0020 0420 0000 0000 ...
Последовательности байт в HEX читаются побайтово справа налево, один байт представлен двумя цифрами (например, pd_upper = 1f38 = 7992):
lsn=3/9E012420 checksum flags pd_lower=44 pd_upper=7992 pd_special=8192 version pgsz prunexid
|-------| |-------| |--------| |-----| |-----------| |-------------| |---------------| |-----| |----| |---------|
0300 0000 2024 019e 0000 0000 2c00 381f 0020 04 20 0000 0000
Версии строк
Строки расположены перед специальной областью. В примере специальная область отсутствует, так как она используется некоторыми типами индексов, а также для размещения базы для реализации 64-битных счетчиков начиная с Pangolin 6.1.
Функция heap_page_items принимает в качестве аргумента образ страницы кучи, полученный в результате вызова get_raw_page, и выводит все кортежи, находящиеся на странице, независимо от видимости в снимке MVCC:
select * from heap_page_items(get_raw_page('test_pango', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+----------------------------------
1 | 8152 | 1 | 39 | 5909 | 0 | 0 | (0,1) | 2 | 2050 | 24 | | | \x010000001741312020202020202020
2 | 8112 | 1 | 39 | 5909 | 0 | 0 | (0,2) | 2 | 2050 | 24 | | | \x020000001741322020202020202020
3 | 8072 | 1 | 39 | 5909 | 0 | 0 | (0,3) | 2 | 2050 | 24 | | | \x030000001741332020202020202020
4 | 8032 | 1 | 39 | 5909 | 0 | 0 | (0,4) | 2 | 2050 | 24 | | | \x040000001741342020202020202020
5 | 7992 | 1 | 39 | 5909 | 0 | 0 | (0,5) | 2 | 2050 | 24 | | | \x050000001741352020202020202020
(5 rows)
lp– номер указателя внутри страницы;lp_off– смещение внутри страницы (указывает на начало следующего кортежа, либоlpпри HOT-update);lp_flags– статус версии строки;lp_len– длина строки вместе с заголовком;t_xmin– номер создавшей транзакции;t_xmax– номер удалившей транзакции;t_field3– номер внутри транзакции;t_ctid– ссылка на обновленную строк;t_infomask2– информационные биты;t_infomask– информационные биты;t_hoff– размер заголовка вместе с NULL bitmap и с учетом выравнивания;t_data– сами данные.
В тестовой таблице test_pango каждая строка занимает 40 байт = 24 байта заголовок строки + 4 байта тип данных int + 11 байт (тип данных char(10) + дополнительный байт для хранения char) = 39 + выравнивание (в 64-разрядных ОС длина любой строки и заголовка строки выравниваются по 8 байт) = 40 байт.
В качестве примера возьмем последние 40 байт (1 строка в таблице):
... 1517 0000 0000 0000
0000 0000 0000 0000 0100 0200 0208 1800
0100 0000 1741 3120 2020 2020 2020 2000
| t_data |
xmin=5909 xmax=0. t_cid=0 t_ctid posid infomask2=2 infomask=2050 t_hoff=24 id=1 доп. байт + name='A1 '
|-------| |-------| |-------| |--------| |--| |-------------| |-------| |-------| |--------------------------------------|
1517 0000 0000 0000 0000 0000 0000 0000 0100 0200 0208 1800 0100 0000 1741 3120 2020 2020 2020 2000
Для хранения короткой строки (до 126 байт) требуется дополнительный 1 байт плюс размер самой строки, включая дополняющие пробелы для типа character. Для строк длиннее требуется не 1, а 4 дополнительных байта.
Указатели на версии строк
Массив указателей на версии строк служит оглавлением страницы и располагается сразу за заголовком страницы. Размер каждого указателя – 4 байта. src/include/storage/itemid.h:
typedef struct ItemIdData
{
unsigned lp_off:15, /* смещение кортежа (от начала страницы) */
lp_flags:2, /* состояние указателя строки, см. ниже */
lp_len:15; /* длина кортежа в байтах */
} ItemIdData;
Функция heap_page_items также отображает информацию о номере указателя внутри страницы (lp), смещение внутри страницы (lp_off), статус версии строки (lp_flags) и длину строки (lp_len):
select * from heap_page_items(get_raw_page('test_pango', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+----------------------------------
1 | 8152 | 1 | 39 | 5909 | 0 | 0 | (0,1) | 2 | 2050 | 24 | | | \x010000001741312020202020202020
Указатель на первую версию строки:
d89f 4e00 = 00000000010011101001111111011000
lp_len=39 lp_flags=1 lp_off=8152 (8152 - указывает, откуда начинается следующая строка)
|-------------| |----------| |-------------|
000000000100111 01 001111111011000
Статус версий строк
Статус строки
Статус версий строк определяется при помощи заголовка версии строки.
lp_flags - статус версии строки:
#define LP_UNUSED 0 /* не используется (должен всегда иметь lp_len=0) */
#define LP_NORMAL 1 /* используется (должен всегда иметь lp_len>0) */
#define LP_REDIRECT 2 /* перенаправление HOT (должно иметь lp_len=0) */
#define LP_DEAD 3 /* мертвый (может иметь или не иметь хранилище) */
Расшифровка t_infomask и t_infomask2
Функция heap_tuple_infomask_flags декодирует значения t_infomask и t_infomask2, которые возвращает функция heap_page_items, и выдает массивы с именами флагов в понятном для человека виде:
select lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_field3, t_ctid, heap_tuple_infomask_flags(t_infomask, t_infomask2), t_infomask, t_infomask2, t_hoff, t_bits, t_oid, t_data from heap_page_items(get_raw_page('test_pango', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | heap_tuple_infomask_flags | t_infomask | t_infomask2 | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+---------------------------------------------+------------+-------------+--------+--------+-------+----------------------------------
1 | 8152 | 1 | 39 | 5956 | 0 | 0 | (0,1) | ("{HEAP_HASVARWIDTH,HEAP_XMAX_INVALID}",{}) | 2050 | 2 | 24 | | | \x010000001741312020202020202020
2 | 8112 | 1 | 39 | 5956 | 0 | 0 | (0,2) | ("{HEAP_HASVARWIDTH,HEAP_XMAX_INVALID}",{}) | 2050 | 2 | 24 | | | \x020000001741322020202020202020
3 | 8072 | 1 | 39 | 5956 | 0 | 0 | (0,3) | ("{HEAP_HASVARWIDTH,HEAP_XMAX_INVALID}",{}) | 2050 | 2 | 24 | | | \x030000001741332020202020202020
4 | 8032 | 1 | 39 | 5956 | 0 | 0 | (0,4) | ("{HEAP_HASVARWIDTH,HEAP_XMAX_INVALID}",{}) | 2050 | 2 | 24 | | | \x040000001741342020202020202020
5 | 7992 | 1 | 39 | 5956 | 0 | 0 | (0,5) | ("{HEAP_HASVARWIDTH,HEAP_XMAX_INVALID}",{}) | 2050 | 2 | 24 | | | \x050000001741352020202020202020
(5 rows)
В примере t_infomask = 2050 = 1000 0000 0010, проставлены биты HEAP_HASVARWIDTH (имеет атрибуты переменной ширины) и HEAP_XMAX_INVALID (t_xmax недействителен/отменен). t_infomask2 = 2, в данном случае это количество атрибутов. Информация, хранящаяся в t_infomask:
#define HEAP_HASNULL 0x0001 /* есть атрибуты с NULL */
#define HEAP_HASVARWIDTH 0x0002 /* есть атрибуты переменной длины */
#define HEAP_HASEXTERNAL 0x0004 /* есть внешние хранимые атрибуты */
#define HEAP_HASOID_OLD 0x0008 /* есть поле object-id */
#define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax — блокировка с общим ключом */
#define HEAP_COMBOCID 0x0020 /* t_cid — комбинированный CID */
#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax — эксклюзивная блокировка */
#define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, если действителен, является только блокировкой */
/* xmax — блокировка с общим доступом */
#define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin зафиксирована */
#define HEAP_XMIN_INVALID 0x0200 /* t_xmin недействительна/прервана */
#define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
#define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax зафиксирована */
#define HEAP_XMAX_INVALID 0x0800 /* t_xmax недействительна/прервана */
#define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax — идентификатор MultiXact */
#define HEAP_UPDATED 0x2000 /* это обновленная версия строки */
#define HEAP_MOVED_OFF 0x4000 /* перемещена в другое место pre-9.0
* VACUUM FULL; сохраняется для поддержки
* двоичного обновления */
#define HEAP_MOVED_IN 0x8000 /* перемещена из другого места pre-9.0
* VACUUM FULL; сохраняется для поддержки
* двоичного обновления */
#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
#define HEAP_XACT_MASK 0xFFF0 /* биты, связанные с видимостью */
Информация, хранящаяся в t_infomask2:
#define HEAP_NATTS_MASK 0x07FF /* 11 бит под количество атрибутов */
/* биты 0x1800 зарезервированы */
#define HEAP_KEYS_UPDATED 0x2000 /* строка была обновлена и ключевые столбцы
* изменены, или строка удалена */
#define HEAP_HOT_UPDATED 0x4000 /* строка была обновлена по HOT (Heap-Only Tuple) */
#define HEAP_ONLY_TUPLE 0x8000 /* это строка-только-куча (heap-only tuple) */
#define HEAP2_XACT_MASK 0xE000 /* биты, связанные с видимостью */
/*
* HEAP_TUPLE_HAS_MATCH — временный флаг, используемый во время хеш-соединений.
* Используется только для строк в хеш-таблице, которым не требуется
* информация о видимости, поэтому он совмещен с флагом видимости
* вместо выделения отдельного бита.
*/
#define HEAP_TUPLE_HAS_MATCH HEAP_ONLY_TUPLE /* строка имеет совпадение при соединении */
Исследование индексов B-деревьев
Функция bt_metap выдает информацию о метастранице индекса B-tree, например:
select * from bt_metap('t_idx');
magic | version | root | level | fastroot | fastlevel | oldest_xact | last_cleanup_num_tuples | allequalimage
--------+---------+------+-------+----------+-----------+-------------+-------------------------+---------------
340322 | 4 | 46 | 2 | 46 | 2 | 0 | -1 | t
(1 row)
level– число уровней индекса(нумерация начинается с 0), в примере индекс трехуровневый;magicиversion– используются для быстрой проверки того, что объект является индексом btree поддерживаемой версии;root– адрес корневого блока;fastrootиfastlevel– используются для оптимизации поиска по индексу.
Функция bt_page_stats выводит сводную информацию по единичным страницам B-дерева:
select * from bt_page_stats('t_idx', 1);
blkno | type | live_items | dead_items | avg_item_size | page_size | free_size | btpo_prev | btpo_next | btpo | btpo_flags
-------+------+------------+------------+---------------+-----------+-----------+-----------+-----------+------+------------
1 | l | 6 | 0 | 138 | 8192 | 7292 | 0 | 2 | 0 | 1
(1 row)
blkno– номер блока/страницы;type– тип блока (r- корневой (root),i- внутренний (internal),l- листовой (list),e- (ignored),d- удаленный листовой (deleted leaf),D- удаленный внутренний (deleted internal));live_items– живых записей на странице;dead_items– мертвых записей на странице;avg_item_size– средний размер индексной записи на этой странице;page_size– размер страницы;free_size– свободное место на странице;btpo_prevиbtpo_next– предыдущий и следующий индексный блок (btpo_prev = 0– блок самый левый на своем уровне,btpo_next = 0блок самый правый на своем уровне).
Функция bt_page_items выдает подробную информацию обо всех элементах на странице индекса B-tree:
select itemoffset, ctid, itemlen, htid, left(data::text,18), tids from bt_page_items('t_idx',1);
itemoffset | ctid | itemlen | htid | left | tids
------------+--------+---------+--------+--------------------+------
1 | (16,1) | 144 | | 06 02 00 00 10 27 |
2 | (16,3) | 144 | (16,3) | 06 02 00 00 10 27 |
3 | (1,22) | 112 | (1,22) | 8e 01 00 00 4c 1d |
4 | (16,4) | 144 | (16,4) | 06 02 00 00 10 27 |
5 | (16,5) | 144 | (16,5) | 06 02 00 00 10 27 |
6 | (16,6) | 144 | (16,6) | 06 02 00 00 10 27 |
(6 rows)
itemoffset– номер записи в данном блоке;ctid– для root и internal блоков ссылка на нижний уровень, для list блоков ссылка на запись в таблице;itemlen– длина записи;htid– ссылка на запись в таблице, есть только у листовых блоков;data– ключ индексирования/ключ индексирования + покрывающее поле.
64-битный счетчик транзакций
Прямой метод увеличения разрядности полей t_xmin, t_xmax и pd_prune_xid в заголовке страницы приведет к существенному расходу дискового пространства и потребует переупаковки каждой версии строки при обновлении через pg_upgrade, поэтому в Pangolin вводится новый разряд страниц, но разрядность t_xmin, t_xmax и pd_prune_xid остается 32-битной. В конце каждой страницы резервируется место под специальную область для полей xid_base и multi_base, например:
select * from page_header(get_raw_page('test_pango', 0));
lsn | checksum | flags | lower | upper | special | pagesize | version | xid_base | multi_base | prune_xid
------------+----------+-------+-------+-------+---------+----------+---------+----------+------------+-----------
0/1F6A4950 | 0 | 0 | 44 | 7968 | 8168 | 8192 | 5 | 853 | 0 | 0
(1 row)
По полям version (на Pangolin 6.1 и выше version = 5), xid_base, multi_base и разнице между pagesize - special (разница должна быть 24 байта, если pagesize = special на Pangolin 6.1, необходим более детальный анализ для исключения возможных багов) – можно определить конвертировалась ли страница к странице 64-битного вида.
Часто используемые функции pageinspect
Заголовок страницы:
select * from page_header(get_raw_page('название_таблицы_или_индекса', 0));
Таблицы:
-
страница таблицы:
select * from heap_page_items(get_raw_page('название_таблицы', 0)); -
страница таблицы с названиями полей, чтобы удобнее редактировать и выводить необходимые:
select lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_field3, t_ctid, t_infomask2, t_infomask, t_hoff, t_bits, t_oid, t_data from heap_page_items(get_raw_page('название_таблицы', 0)); -
страница таблицы + расшифрованные биты
t_infomask,t_infomask2:select *, heap_tuple_infomask_flags(t_infomask, t_infomask2) from heap_page_items(get_raw_page('название_таблицы', 0)); -- страница таблицы + расшифрованные биты t_infomask, t_infomask2
Индекс B-tree:
-
информация о метастранице индекса B-tree:
select * from bt_metap('название_индекса'); -
сводная информация по страницам B-tree:
select * from bt_page_stats('название_индекса', 1); -
подробная информация обо всех элементах на странице индекса B-tree:
select * from bt_page_items('название_индекса', 1);