← На главную

Простой пример использования pageinspect

Недавно мы разобрались, как PostgreSQL хранит данные на диске. Но как убедиться, что СУБД именно так и работает? Вдруг мы что-то упустили или недопоняли? Можно прочитать данные с диска с помощью утилиты hexdump и посмотреть, что там реально записано. Но это трудоемко, ведь все битики придется декодировать вручную. К счастью, с PostgreSQL идет расширение pageinspect, которое может декодировать битики за нас.

Pageinspect имеет много возможностей. Все они описаны в документации. Помимо прочего, расширение умеет декодировать индексы Hash, B-Tree, GiST, и другие. В рамках этого поста мы не будем уходить в такие дебри и рассмотрим только базовый функционал.

Включаем pageinspect:

CREATE EXTENSION pageinspect;

Для эксперимента воспользуемся той же таблицей, что и в прошлый раз:

CREATE TABLE phonebook( "id" SERIAL PRIMARY KEY NOT NULL, "name" NAME NOT NULL, "phone" INT NOT NULL); INSERT INTO phonebook ("name", "phone") VALUES ('Alice', 123), ('Bob', 456), ('Charlie', 789);

Заметьте, что таблица имеет первичный ключ. По нему автоматически будет создан B-Tree индекс.

Чтобы прочитать нулевую страницу таблицы phonebook и декодировать ее заголовок, выполним следующий запрос:

=# SELECT * FROM page_header(get_raw_page('phonebook', 0)); -[ RECORD 1 ]--------- lsn | 0/12631C00 checksum | 0 flags | 0 lower | 36 upper | 7904 special | 8192 pagesize | 8192 version | 4 prune_xid | 0

Здесь get_raw_page() считывает страницу в виде бинаря, а page_header() декодирует ее заголовок и возвращает, как record.

Также мы можем заглянуть в кортежи, их заголовки и содержимое ItemIdData[]:

=# SELECT lp, lp_off, lp_flags, lp_len, t_ctid, t_infomask2 FROM heap_page_items(get_raw_page('phonebook', 0)); lp | lp_off | lp_flags | lp_len | t_ctid | t_infomask2 ----+--------+----------+--------+--------+------------- 1 | 8096 | 1 | 96 | (0,1) | 3 2 | 8000 | 1 | 96 | (0,2) | 3 3 | 7904 | 1 | 96 | (0,3) | 3

Здесь lp* соответствуют элементам массива ItemIdData[]. Столбец lp представляет собой индекс массива, а остальные – декодированные поля соответствующего элемента. Вспомним, что хранится в ItemIdData:

typedef struct ItemIdData { unsigned lp_off:15, /* смещение кортежа */ lp_flags:2, /* состояние элемента */ lp_len:15; /* размер кортежа */ } ItemIdData;

Pageinspect не отображает флаги в текстовом виде, но их можно подсмотреть в исходном коде:

#define LP_UNUSED 0 #define LP_NORMAL 1 #define LP_REDIRECT 2 #define LP_DEAD 3

Видим, что у нас здесь три нормальных (lp_flags = 1) кортежа. Наконец, t_ctid и t_infomask2 – это одноименные поля из структуры HeapTupleHeaderData. Напомню, что t_ctid представляет собой ItemPointer на более новую версию кортежа. Более новых версий пока нет, поэтому все кортежи ссылаются сами на себя. Поле t_infomask2 может хранить следующую информацию:

/* 11 bits for number of attributes */ #define HEAP_NATTS_MASK 0x07FF #define HEAP_KEYS_UPDATED 0x2000 #define HEAP_HOT_UPDATED 0x4000 #define HEAP_ONLY_TUPLE 0x8000

Пока что оно просто говорит, что все кортежи имеют по три атрибута. Никаких других флагов не проставлено.

Что будет, если обновить одну из строк? Например:

=# UPDATE phonebook SET name = 'Alex' WHERE name = 'Alice'; UPDATE 1 =# SELECT lp, lp_off, lp_flags, lp_len, t_ctid, to_hex(t_infomask2) FROM heap_page_items(get_raw_page('phonebook', 0)); lp | lp_off | lp_flags | lp_len | t_ctid | to_hex ----+--------+----------+--------+--------+-------- 1 | 8096 | 1 | 96 | (0,4) | 4003 2 | 8000 | 1 | 96 | (0,2) | 3 3 | 7904 | 1 | 96 | (0,3) | 3 4 | 7808 | 1 | 96 | (0,4) | 8003

Поле t_ctid первого кортежа теперь указывает на четвертый кортеж. Кроме того, у старого кортежа был проставлен флаг HEAP_HOT_UPDATED, а у нового – HEAP_ONLY_TUPLE. Другими словами, мы видим HOT-цепочку. Следовательно, индекс по первичному ключу не был перестроен.

Если теперь выполнить VACCUM:

=# VACUUM phonebook; VACUUM =# SELECT lp, lp_off, lp_flags, lp_len, t_ctid, to_hex(t_infomask2) FROM heap_page_items(get_raw_page('phonebook', 0)); lp | lp_off | lp_flags | lp_len | t_ctid | to_hex ----+--------+----------+--------+--------+-------- 1 | 4 | 2 | 0 | | 2 | 8096 | 1 | 96 | (0,2) | 3 3 | 8000 | 1 | 96 | (0,3) | 3 4 | 7904 | 1 | 96 | (0,4) | 8003

… то СУБД поймет, что старый кортеж больше никому не виден, а значит, может быть удален. При этом в поле lp_flags соответствующего ItemIdData проставляется LP_REDIRECT, а поле lp_off указывает на более новую версию кортежа. Таким образом, хоть кортеж и был удален, HOT-цепочка не рвется.

При желании можно придумать еще множество захватывающих экспериментов. Придумать и провести которые я предлагаю вам самостоятельно. Моей целью было лишь показать, что pageinspect прост и приятен в использовании. Он может быть использован для отладки кода, диагностики проблем на проде, а также служить средством изучения внутренностей СУБД. Звучит как что-то, чем полезно уметь пользоваться.

Дополнение: Внутренности PostgreSQL: механизм TOAST