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

EnglishRussianUkrainian