Иногда вам нужно определить какому файлу на диске соответствует таблица. У вас имеется путь, полный цифр, такой как base/16499/19401 и вы хотите разобраться в нем. Вы можете смотреть на сообщение об ошибке, которое упоминает имя файла, например:

ERROR:  could not read block 11857 of relation base/16396/3720450: read only 0 of 8192 bytes

В поисках пути отношения

Вы можете увидеть путь до таблицы используя:

SELECT pg_relation_filepath('tablename');

но что насчет обратного процесса, получения названия объекта из пути до него? Существует функция pg_filenode_relation, которая кажется подходящей для этого… но чтобы ее использовать, необходимо быть подключенным к конкретной базе данных, к которой относится этот файл, что подразумевает знание этой связи.

Структура пути до файла

Вот каким образом можно определить путь до таблиц и баз данных в современной версии PostgreSQL. (Более старые версии используют другой формат, про который можно почитать здесь).

Имеется 3 основных варианта пути:

  • Для файлов в табличном пространстве по умолчанию, base/database_oid/filenode id for the relation
  • Для файлов из прочих табличных пространств: pg_tblspc / tablespace_oid / tablespace_version_subdir / database_oid / filenode id отношения
  • Для общих отношений: global/filenode id отношения


Общие отношения будут обсуждаться в конце. Для первых же двух вариантов, которые являются самыми распространенными, с которыми вы будете чаще всего сталкиваться, последняя часть пути идентична, oid базы и oid отношения.

Обратите внимание, я употребил формулировку "filenode id отношения", а не "oid отношения". Это связано с тем, что PostgreSQL имеет карту relfilenode в файле с именем pg_relfilenode.map для каждой базы данных/табличного пространства. Имена файлов таблиц вовсе не обязательно совпадают с их oid'ами из pg_class, и они могут измениться после запуска VACUUM FULL, TRUNCATE и прочих. К примеру:

test=> select pg_relation_filepath('a');
 pg_relation_filepath 
----------------------
 base/16385/101565
(1 row)

test=> VACUUM FULL a;
VACUUM
test=> select pg_relation_filepath('a');
 pg_relation_filepath 
----------------------
 base/16385/101577
(1 row)

Итак. Как превратить этот путь обратно в имя отношения?

Oid'ы базы данных и filenode ids отношения

Предположим, Вы получили ошибку из начала этой статьи. Ее можно разбить на несколько частей:

  • base: в табличном пространстве по умолчанию
  • 16396: в базе данных с oid'ом 16396
  • 3720450 filenode id для таблицы с oid'ом 3720450

после чего рассмотреть что значит каждая из них.

Определение базы данных по oid

Во-первых, необходимо подключиться к любой базе данных в этом PostgreSQl процессе и выполнить:

select datname
from pg_database
where oid = 16396

(или любой другой oid базы, который вы имеете). Это вернет Вам имя базы данных.

После этого необходимо подключиться к этой базе.

Обратное преобразование relfilenodes на 9.4 версии

Если Вы используете версию 9.4, или более свежую, то для Вас следующая часть проста:

SELECT pg_filenode_relation(0, 3720450);

(0 означает «табличное пространство по умолчанию»)

Эта функция выполняет обратное преобразование relfilenode за вас. Таким образом, она просто покажет Вам имя таблицы. Для него не будет показана связь с какой-то схемой, если полученное имя таблицы принадлежит текущему search_path; Можно использовать SET search_path = ''; перед выполнением функции, для того, чтобы был указан путь вплоть до схемы.

Вы должны быть подключены к правильной базе данных, или будет получен неправильный ответ, либо вообще ответ не будет получен.

Обратное преобразование relfilenodes на 9.3 версии

Если вы используете версию 9.3, или старее, необходимо подключиться к базе данных, в которой находится таблица и выполнить следующий запрос к pg_class:

select 
    n.nspname AS tableschema, 
    c.relname AS tablename
from pg_class c
inner join pg_namespace n on (c.relnamespace = n.oid)
where c.relfilenode = 3720450;

(или любой другой полученный relfilenode id таблицы).

Это расскажет Вам о том, к какой таблице относится эта ошибка.

Нет результатов?

Что ж, обычно это помогает.

Relfilenode также может быть нулем, это в свою очередь означает, что файл расположен посредством pg_relfilenode.map. Это является типичным сценарием для общих и некоторых системных каталогов, их индексов, TOAST таблиц и т.д. К примеру, это могут быть pg_database, pg_class и pg_proc.

Что насчет схемы?

Вы обратили внимание, что схема (пространство имен) не фигурирует в пути?

В PostgreSQL схемы является только пространством имен внутри базы данных. Они не имеют никакого влияния на то, где физически хранятся таблицы на диске.

Другие пути табличных пространств

Недавний случай, с которым я столкнулся, был следующей ошибкой:

ERROR: could not truncate file "pg_tblspc / 16709 / PG_9.3_201306121 / 16499/19401" to 8 blocks: Permission denied

Это не табличное пространство по умолчанию, так как путь начинается с pg_tblspc.

Сам процесс нахождения таблицы на самом деле тот же. Можно проигнорировать pg_tblspc/nnn/PG_n.n_nnnnnn/ часть и сфокусироваться сразу на database_oid/relation_oid, как описано выше для случаев с табличным пространством по умолчанию. Для этого стоит понимать что означает путь.

Таким образом текст ошибки разбивается на следующие части:

  • pg_tblspc: это не табличное пространство по умолчанию
  • 16709: это табличное пространство с oid'ом 16709
  • PG_9.3_201306121: используется PostgreSQL 9.3 с версией каталога 201306121.
  • 16499: база данных с oid'ом 16499
  • 19401 таблица с relfilenode id 19401

Мы уже обсуждали часть об oid'е базы данных и табличном relfilenode id. Они не отличаются от табличного пространства, только начинаются в другом месте.

Так что насчет части с табличным пространством?

pg_tblspc является директорией в директории данных PostgreSQL, которая содержит в себе символьные ссылки ко всем положениям табличных пространств (или на NTFS, точки соединения для них). Каждая символьная ссылка названа в честь oid табличного пространства. Именно так PostgreSQL находит табличные пространства. SQL команды к табличным пространствам оперируют этими ссылками.

Oid относится к pg_tablespace записи для табличного пространства, как видно из:

select spcname
from pg_tablespace
where oid = 16709;

Внутри директории табличного пространства, имеется еще одна директория, имеющая название, соответствующее версии PostgreSQL. Оно статично для этой версии и единственное применение этому — это множественный доступ нескольких PostgreSQL процессов к одному табличному пространству, например, во время pg_upgrade. Как правило, имеется только одна запись.

В целом же, структура та же как и для base/ путей — сначала oid базы данных, потом oid отношения.

Глобальные (общие) таблицы

Имеется еще третья категория ошибок, в случае если Вы ее наблюдаете, то Вы определенно в беде. PostgreSQL имеет общие каталоги — таблицы, которые имеют одинаковое содержимое в каждой базе данных. Они обитают в специальном табличном пространстве с relfilenode id 16709.

Пути к ним начинаются с global вместо base и у них отсутствует компонент с oid'ом базы данных.

Общие каталоги не отмечены relfilenode в pg_class. То есть Вы не сможете посмотреть, к примеру, pg_database из pg_class. pg_filenode_relation возвращает null, независимо от того, вызывать ли его с oid'ом табличного пространства по умолчанию, или же с oid'ом глобального табличного пространства 1664.

Выяснение этого является темой для последующей статьи с разобранными связями.

Конечно же, если вы испытываете проблемы с общими каталогами, вы, вероятнее всего, не сможете в принципе запустить базу данных.

Имея дело с повреждениями

Повреждения базы данных не должно случаться. Но оно может произойти в любом случае. Это могут быть проблемы с железом, баги ядра, или файловой системы, ССД, которые врут о совершении надежных дисковых приливах, глючные сети хранения данных, ну и конечно же баги самого PostgreSQL. Если Вы подозреваете повреждение базы данных, перед тем как что либо предпринять, прочтите и действуйте по советам с вики странички о повреждениях.

Внутренности

Чтобы увидеть как все это работает, запустите макрос relpathbackend в src/include/common/relpath.h. Он вызывает GetRelationPath в src/common/relpath.c.

В мануале рассматривается структура базы данных на диске. Ссылка.

Комментарии (0)