Организация данных
Низкий уровень
16
Авторские права
© Postgres Professional, 2017–2024
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Алексей Береснев
Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Файлы данных
Слои: данные, карты видимости и свободного пространства
Длинные версии строк и TOAST
3
Слои объекта
NNN_vm
NNN_fsm
сегмент
1 ГБ
main
fsm
vm
NNN_fsm
NNN_fsm.1
NNN
NNN.1
NNN.2
pg_relation_size
Обычно каждому объекту БД, хранящему данные (таблице, индексу,
последовательности, материализованному представлению),
соответствует несколько слоев (forks). Каждый слой содержит
определенный вид данных.
Вначале слой содержит один-единственный файл. Имя файла состоит
из числового идентификатора, к которому может быть добавлено
окончание, соответствующее имени слоя.
Файл постепенно растет и, когда его размер доходит до 1 Гбайта,
создается следующий файл этого же слоя. Такие файлы иногда
называют сегментами. Порядковый номер сегмента добавляется
в конец имени файла. Общий размер любого слоя показывает функция
pg_relation_size.
Ограничение размера файла в 1 Гбайт возникло исторически для
поддержки различных файловых систем, некоторые из которых не
умеют работать с файлами большого размера. Установить другой
размер можно только при сборке сервера из исходных кодов
(--with-segsize).
Таким образом, одному объекту БД на диске может соответствовать
несколько файлов. Для небольшой таблицы их будет 3, для индекса
два. Все файлы объектов, принадлежащих одному табличному
пространству и одной БД, будут помещены в один каталог. Это
необходимо учитывать, потому что файловые системы могут не очень
хорошо работать с большим количеством файлов в каталоге.
4
Слои
Основной слой
собственно данные (версии строк)
существует для всех объектов
Слой инициализации (init)
«пустышка» для основного слоя
используется при сбое; только для нежурналируемых таблиц
Карта видимости (vm)
существует только для таблиц
Карта свободного пространства (fsm)
существует и для таблиц, и для индексов
Посмотрим теперь на типы слоев.
Основной слой — это собственно данные: версии строк таблиц или
индексные записи. Имена файлов основного слоя совпадают
с идентификатором. Основной слой существует для любых объектов.
Имена файлов слоя инициализации оканчиваются на «_init». Этот слой
существует только для нежурналируемых таблиц (созданных
с указанием UNLOGGED) и их индексов. Такие объекты ничем не
отличаются от обычных, но действия с ними не записываются в журнал
упреждающей записи. За счет этого работа с ними происходит быстрее,
но в случае сбоя их содержимое невозможно восстановить. При
восстановлении PostgreSQL просто удаляет все слои таких объектов и
записывает слой инициализации на место основного слоя. В результате
получается пустая таблица.
Слой vm (visibility map) — битовая карта видимости. Имена файлов
этого слоя оканчиваются на «_vm». Слой существует только для
таблиц; для индексов не поддерживается отдельная версионность.
Слой fsm (free space map)карта свободного пространства. Имена
файлов этого слоя оканчиваются на «_fsm». Этот слой существует и
для таблиц, и для индексов.
Про две эти карты рассказывалось в модуле «Архитектура».
6
TOAST
Версия строки должна помещаться на одну страницу
можно сжать часть полей
можно вынести часть полей в отдельную toast-таблицу
можно сжать и вынести одновременно
Toast-таблица
находится в схеме pg_toast (pg_toast_temp_N)
поддержана собственным индексом
содержит фрагменты «длинных» значений размером меньше страницы
читается только при обращении к «длинному» полю
имеет собственную версионность
используется прозрачно для приложения
Любая версия строки в PostgreSQL должна целиком помещаться на
одну страницу. Для «длинных» версий строк применяется технология
TOAST — The Oversized Attributes Storage Technique. Она
подразумевает несколько стратегий работы с «длинными» полями.
Значение поля может быть сжато так, чтобы версия строки поместилась
на страницу. Значение может быть убрано из версии и перемещено
в отдельную служебную таблицу. Могут применяться и оба подхода:
какие-то значения будут сжаты, какие-то — перемещены, какие-то —
сжаты и перемещены одновременно.
Для каждой основной таблицы при необходимости создается отдельная
toast-таблица (и к ней специальный индекс). Такие таблицы и индексы
располагаются в отдельной схеме pg_toast и поэтому обычно не видны
(для временных таблиц используется схема pg_toast_temp_N
аналогично обычной pg_temp_N).
Версии строк в toast-таблице тоже должны помещаться на одну
страницу, поэтому длинные значения хранятся порезанными на
фрагменты. Из этих фрагментов PostgreSQL прозрачно для
приложения «склеивает» необходимое значение.
Toast-таблица используется только при обращении к длинному
значению. Кроме того, для toast-таблицы поддерживается своя
версионность: если обновление данных не затрагивает «длинное»
значение, новая версия строки будет ссылаться на то же самое
значение в toast-таблице — это экономит место.
8
Размер таблицы
pg_total_relation_size
pg_indexes_sizepg_table_size
Таблица TOAST Индексы
Как уже говорилось, размер отдельного слоя можно получить функцией
pg_relation_size. Чтобы не суммировать размеры отдельных слоев, есть
несколько функций, показывающих размеры таблицы:
pg_table_size показывает размер таблицы и ее toast-части (toast-
таблицы и обслуживающего ее индекса), но без обычных индексов.
Эту же функцию можно использовать для вычисления размера
отдельного индекса: и таблицы, и индексы являются отношениями,
и, несмотря на название, функция принимает любое отношение.
pg_indexes_size суммирует размеры всех индексов таблицы, кроме
индекса toast-таблицы.
pg_total_relation_size показывает полный размер таблицы, вместе
со всеми ее индексами.
10
Итоги
Объект представлен несколькими слоями
Слой состоит из одного или нескольких файлов-сегментов
Для «длинных» версий строк используется TOAST
11
Практика
1. Создайте нежурналируемую таблицу в пользовательском
табличном пространстве и убедитесь, что для таблицы
существует слой init.
Удалите созданное табличное пространство.
2. Создайте таблицу со столбцом типа text.
Какая стратегия хранения применяется для этого столбца?
Измените стратегию на external и вставьте в таблицу
короткую и длинную строки.
Проверьте, попали ли строки в toast-таблицу, выполнив
прямой запрос к ней. Объясните, почему.
12
Практика+
1. Создайте базу данных.
Сравните размер базы данных, возвращаемый функцией
pg_database_size, с общим размеров всех таблиц в этой базе.
Объясните полученный результат.
2. В TOAST поддерживаются два метода сжатия: pglz и lz4.
Проверьте средствами SQL, был ли PostgreSQL
скомпилирован с поддержкой этих методов.
3. Создайте текстовый файл размером больше 10 Мбайт.
Загрузите его содержимое в таблицу с текстовым полем,
сначала без сжатия, а затем используя каждый из
алгоритмов. Сравните итоговый размер таблицы и время
загрузки данных для трех вариантов.
1. Список таблиц базы данных можно получить из таблицы pg_class
системного каталога.
2. С помощью представления pg_config можно узнать, какие параметры
были установлены скрипту configure при сборке ПО сервера. Строка,
содержащая список параметров, велика; выделить необходимые
параметры можно функцией string_to_table().
3. Чтобы получить текст для эксперимента, можно взять достаточно
большой двоичный файл (например, исполняемый файл postgres)
и преобразовать его в текст. Для преобразования можно использовать
алгоритм Base32 люч -w0 отменяет переносы строк):
base32 -w0 < двоичный-файл > текстовый-файл