Developer FAQ/ru

From PostgreSQL wiki
Jump to navigationJump to search


Вовлечение

Как мне влиться в разработку PostgreSQL?

Скачайте исходный код, изучите его. См. раздел скачать дерево исходников.

Подпишитесь на рассылку pgsql-hackers (или просто "hackers"). Это именно то место, где обсуждаются вопросы разработки между ведущими разработчиками и членами core team.

Как мне скачать или обновить текущее дерево?

Существует несколько путей получения исходников. Для того, чтобы просто посмотреть исходный код, можно скачать свежий снимок с ftp.

Для разработки на постоянной основе можно воспользоваться анонимным доступом к системе управления кодом. Сейчас дерево находится под управлением git. Подробнее о том, как получить исходные коды из git, см. developer.postgresql.org/pgdocs/postgres/git.html и Working_with_Git/ru.

Какая среда необходима для разработки?

PostgreSQL в основном разрабатывается на языке C. Исходный код совместим с основными популярными Unix и Windows-платформами (XP, Windows 2000 и выше).

Большинство разработчиков под Unix-подобные ОС используют инструментарий свободного ПО: GCC, GNU Make, GDB, Autoconf и т.д. Если приходилось сталкиваться с разработкой свободного ПО ранее, то, скорее всего, вам эти инструменты уже известны. Разработчики под Windows могут использовать MinGW, хотя разработка в основном происходит с помощью среды Microsoft Visual Studio и связанных с нею средств.

Полный перечень необходимого для сборки PostgreSQL ПО можно найти в инструкциях по установке.

Если приходится часто пересобирать исходники, то удобно использовать флаг конфигурации --enable-depend. Благодаря этому флагу, при изменении заголовочного C-файла, все зависимости пересобираются автоматически.

С помощью src/Makefile.custom можно установить переменные окружения, например, CUSTOM_COPT, которые будут использованы при компиляции.

Где применить свой талант?

Список нереализованных функциональностей отражен в Todo.

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

Как мне влиться в разработку сайта PostgreSQL?

Разработка сайта PostgreSQL обсуждается в рассылке pgsql-www и курируется командой инфраструктуры. Исходный код сайта postgresql.org храниться в Git-репозитории.

Средства разработки и помощь

Как организован исходный код?

Если перейти по ссылке Как PostgreSQL обрабатывает запросы, можно увидеть разделы с описанием потоков данных, схему компонентов сервера и описание области распределенной памяти. Можно посмотреть подробное описание, нажав на интересующем элементе. Нажатие на названии директории приведет к просмотру исходного кода, реализующего функцию выбранного компонента. В некоторых директориях есть файлы README, которые описывают функции модуля. Файлы можно посмотреть в браузере.

Помимо документации, расположенной в исходниках, можно изучить презентации и документы, касающиеся обсуждения кода, например, www.postgresql.org/developer/coding. Очень хорошая презентация размещена на ресурсе Нила Конвея (Neil Conway)

Какие инструменты доступны разработчикам?

Первое - файлы директории src/tools предназначены для разработчиков.

   RELEASE_CHANGES изменения необходимо проводить для каждого релиза
   ccsym           содержит стандартные определения компилятора
   copyright       отражает авторские права
   entab           преобразует пробелы в знаки табуляции, используется pgindent
   find_static     ищет статические функции
   find_typedef    ищет определения типов в исходных кодах
   find_badmacros  ищет макросы, неверно использующие фигурные скобки
   fsync           скрипт, предоставляющий сведения о стоимости синхронизации кэша с диском
   make_ctags      создает 'tags'-файлы для редактора vi в каждой директории
   make_diff       создает *.orig и разностные файлы исходников
   make_etags      создает 'etags'-файлы редактора emacs
   make_keywords   сравнивает наши ключевые слова со стандартом SQL'92
   make_mkid       создает mkid ID-файлы
   git_changelog   используется для генерации перечня изменений в рамках релиза
   pginclude       скрипты для добавления/удаления включаемых файлов
   pgindent        форматирует отступы исходных файлов
   pgtest          полуавтоматическая система сборки
   thread          скрипт тестирования потока

src/include/catalog содержит:

   unused_oids     скрипт поиска неиспользуемых OID'ов в системном каталоге
   duplicate_oids  ищет дубли OID'ов в системном каталоге

tools/backend описан выше.

Второе - удобно использовать редактор с подсветкой, это позволяет посмотреть определение функций, например, перейти в них, чтобы увидеть реализацию, а затем вернуться в исходную точку кода. Большинство редакторов это поддерживают с помощью tags или etags-файлов.

Третье - необходимо скачать id-utils

Запустив tools/make_mkid можно создать архив идентификаторов, которые позже можно с легкостью запросить.

Некоторые разработчики используют cscope. Другие - glimpse.

tools/make_diff содержит утилиты для создания патч-файлов, которые позже можно накатить на дистрибутив. Инструмент генерирует контекст-зависимые разностные файлы.

pgindent используется для приведения исходников к принятому стилю кода, и обычно запускается в конце этапа разработки. См. более подробно в разделе #Какой стиль форматирования используется для исходников PostgreSQL?.

pginclude содержит набор скриптов для добавления #include во включаемые файлы с одновременным удалением лишних директив.

При добавлении встроенных объектов, таких как, типы или функции, им необходимо присвоить OID 'ы. По нашим соглашениям все назначенные руками OID 'ы лежат в пределах от 1 до 9999. На уровне отдельных системных каталогов объекты будут иметь уникальные идентификаторы независимо от выбранного вручную, но для прозрачности мы требуем, чтобы в рамках всей системы выбирались уникальные значения. Скрипт unused_oids, расположенный в каталоге src/include/catalog , выявляет неиспользованные OID 'ы. Чтобы назначить новый OID, возьмите любой свободный согласно unused_oids, а в придачу можно взять еще и соседний для логически связанных объектов. Скрипт duplicate_oids может помочь в выявлении коллизий OID 'ов при ошибке.

Какой стиль форматирования используется для исходников PostgreSQL?

Мы используем стиль BSD, при котором каждый уровень кода идет с отступом в один знак табуляции, состоящей из четырех пробелов. Необходимо настроить редактор или вьювер для отображения табуляции в четыре пробела.

В директории src/tools/editors расположены примеры настроек редакторов, например, emacs и xemacs, для упрощения настройки стиля, принятого для PostgreSQL.

Для vim лучше установить localvimrc с помощью Pathogen или Vundle, затем добавить файл .lvimrc в директорию вашего PostgreSQL, содержащий:

" Works best with vimrc entries:
"   let g:localvimrc_sandbox = 0
"   let g:localvimrc_whitelist = "/path/to/postgres/tree/.*"
"
if g:localvimrc_sourced_once_for_file
        finish
endif
au BufNewFile,BufRead *.[ch] setlocal expandtab autoindent cindent tabstop=4 shiftwidth=4 cinoptions="(0,t0"

Для less или more, установите -x4 для правильного отображения отступов.

pgindent проведет форматирование кода, установив флаги согласно вашей ОС. pgindent запускается для всех исходных файлов непосредственно перед бета-тестированием. Утилита автоматически форматирует все файлы для обеспечения целостности. Блоки комментариев, где требуется особое форматирование, необходимо начинать с /*------. Такие комментарии пропускаются при обработке.

Также см. раздел документации про форматирование. Тема затрагивает вопрос именования переменных и функций.

Чтобы понять, почему мы переживаем на этот счет, прочитайте статью, описывающую ценность целостного стиля кода.

Существует ли диаграмма системного каталога?

Да, как минимум здесь.

Какие книги стоит прочесть разработчику?

Есть несколько отличных книг:

  • An Introduction to Database Systems, by C.J. Date, Addison, Wesley
  • A Guide to the SQL Standard, by C.J. Date, et. al, Addison, Wesley
  • Fundamentals of Database Systems, by Elmasri and Navathe
  • Transaction Processing, by Jim Gray and Andreas Reuter, Morgan Kaufmann
  • Transactional Information Systems, by Gerhard Weikum and Gottfried Vossen, Morgan Kaufmann

Как собрать конфигурацию?

Файлы configure и configure.in относятся к GNU-пакету autoconf. Configure позволяет протестировать различные возможности ОС, и выставить значения переменных, которые в свою очередь будут проверены программами на C и Makefiles. Autoconf устанавливается на сервере PostgreSQL. Чтобы добавить флаги в configure, нужно отредактировать configure.in, затем запустить autoconf для генерации непосредственно configure.

При запуске configure производятся различные проверки возможностей ОС, данные сохраняются в config.status и config.cache, изменяются *.in-файлы . Например, если существует файл Makefile.in, configure генерирует Makefile, содержащий замененные значения для параметров @var@, найденных configure.

Если требуется отредактировать файлы, убедитесь, что вы не правите созданные утилитой configure файлы. Отредактируйте *.in-файлы, затем повторно запустите configure, чтобы получить конечные. Если запустить make distclean в директории верхнего уровня, все файлы, созданные configure будут удалены, это позволит получить исходный дистрибутив.

Как добавить новую платформу?

Чтобы добавить поддержку новой платформы, необходимо провести изменения в нескольких кусках кода.

Первое - начните с директории src/template. Добавьте соответствующую новой ОС запись. Также воспользуйтесь src/config.guess, чтобы добавить новую ОС в src/template/.similar. Нет нужды указывать точную версию ОС. При проверке configure ищет точную версию, и, если не находит, то использует запись без точной версии. Отредактируйте src/configure.in, добавив новую ОС. Подробности см. выше. Затем выполните autoconf, и/или поправьте src/configure.

Второе - проверьте src/include/port и добавьте файл для новой ОС, содержащий соответствующие значения. Возможно, уже существует реализация для желаемого CPU в src/include/storage/s_lock.h. Директория src/makefiles содержит файлы для обработки утилитой Makefile особых платформа-зависимых действий. Директория backend/port пригодится в случае, если требуются особые файлы для новой ОС.

Почему не используются потоки, сырые диски, асинхронный ввод/вывод и другие новомодные штучки?

Всегда существует соблазн, использовать крутые фичи по мере их появления. Мы этому соблазну не поддаемся.

Первое - мы поддерживаем больше 15 ОС, потому каждая новая фича должна себя хорошо зарекомендовать по надежности, прежде чем мы обратимся к ней. Второе - большинство новомодных штучек не ведут к значительным улучшениям. Третье - они часто имеют подводные камни, например, снижение надежности или дополнительные издержки в разработке. Таким образом, наша стратегия - выжидание состояния стабилизации новой технологии, и только затем уже тестирование возможного значительного прироста в производительности и других плюшек.

Например, сейчас не используются потоки вместо много-процессной архитектуры потому что:

  • Исторически потоки слабо поддерживаются и содержат много ошибок.
  • Ошибка одного потока может повредить другие, если они относятся к единому процессу.
  • Улучшение производительности при использовании потоков слабо соотносится с учетом необходимого времени запуска.
  • Код рабочего процесса значительно усложняется.
  • При завершении рабочего процесса высвобождение ресурсов ОС проходит значительно быстрее и чище, обеспечивая защиту от утечек памяти и файловых дескрипторов.
  • Отладка потоков значительно сложнее нежели отладка рабочих процессов, а снимки памяти менее полезны.
  • Выделение общих блоков памяти на чтение и использование разделяемых буферов позволяет процессам быть более эффективными по отношению к потокам.
  • Периодическое создание и уничтожение процессов позволяет избегать фрагментации памяти, что позволяет избегать сложности управления ею по отношению к длинным процессам.

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

В целом мы не выступаем против новых штучек. Скорее мы относимся с осторожностью в их отношении. TODO часто содержит ссылки на обсуждения касаемо аргументации в этой области.

Как управляется репозиторий?

Более подробно о стратегии ветвления и разработки патчей см. Использование обратного ветвления и Фиксация изменений в Git.

Где взять копию SQL-стандартов?

Мы рекомендуем купить их у ISO или ANSI. Поискать ISO/ANSI 9075. Предложение от ANSI немного дешевле, при том же содержимом, что у ISO.

Покупка официальной копии стоит денег, поэтому большинство разработчиков обращаются к доступным в Internet черновым версиям. Некоторые из них:

Документация PostgreSQL содержит информацию по соответствию стандартам.

Еще несколько ресурсов по SQL-стандартам:

Обратите внимание! Совсем необязательно иметь доступную копию SQL-стандартов, чтобы быть эффективным участником разработки PostgreSQL. Толкование стандарта сложно и требует годы опыта. Стандарт не затрагивает такие вопросы, как индексирование, например, а это хороший кусок разработки, выходящий за его рамки.

У кого просить технической помощи?

Многие вопросы технического характера имеют ответы в рассылке pgsql-hackers.

Если ответ не был найден, то не проблема - задайте его в рассылке.

Ведущие разработчики также задают вопросы, включая те, что касаются новых доработок по IRC-каналу #postgresql на irc.freenode.net.

Процесс разработки

Что делать после того, как сделан выбор?

Отправьте электронное письмо на pgsql-hackers с описанием плана разработки. Предполагается, что доработка существенна. Работать без связи с внешним миром крайне не рекомендовано, тк. кто-то другой уже может выполнять аналогичные работы из TODO, либо поставленная в TODO задача может быть неверно понята. В письме укажите планируемый подход внутренней реализации, все изменения, затрагивающие пользователя, например, новый синтаксис, и тд. Для сложных патчей важно получить обратную связь от сообщества до начала работы. Иначе ваш патч в итоге могут отклонить. Если компания спонсирует доработку, стоит прочесть эту статью.

Наша очередь патчей организована с помощью веб-приложения CommitFest на ресурсе commitfest.postgresql.org.

Как протестировать изменения?

Основные системные тесты

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

Хорошая практика - устанавливать флаг --enable-cassert для configure. Включенные утверждения упрощают отладку ошибок за счет проверок на сегментацию и повреждение памяти.

Далее необходимо выполнить тесты времени исполнения с помощью psql.

Среда времени исполнения

Чтобы протестировать проведенные изменения PostgreSQL, установите PostgreSQL в локальную директорию, например, свою домашнюю. Это позволит обойти возможные конфликты с глобально установленной версией. Используйте опцию --prefix=, чтобы указать расположение установленной базы. Опция --with-pgport позволит указать специфический порт. При запуске нового экземпляра базы убедитесь в том, что используются нужные бинарники. В зависимости от вашей ОС необходимо установить переменные окружения, такие как PATH и LD_LIBRARY_PATH (для большинства Linux/Unix-подобных систем). Также полезная установка PGDATA.

Чтобы автоматизировать установку значений, можно воспользоваться peg скриптами Грега Смита (Greg Smith), или скриптами, используемыми buildfarm.

Регрессионное тестирование

Следующим шагом необходимо протестировать изменения на существующих регрессионных тестах. Для запуска тестов выполните make check в корневой директории исходников. Если тесты валятся, необходимо понять почему?.

Регрессионные тесты и управляющие программы расположены в директории src/test/regress.

Управляющая программа - pg_regress, но обычно она запускается косвенно через make.

Может пригодиться конструкция PG_REGRESS_DIFF_OPTS=-ud make check для получения более читаемых объединенных разностных файлов, что удобнее работы с контекстными файлами pg_regress.

Если поведение изменено осознанно, регрессионные тесты могут поломаться, что вовсе не означает наличие регрессии. В этом случае регрессионные тесты необходимо изменить.

Изоляционные тесты

PostgreSQL содержит так называемый изоляционный тестер для тестирования конкурентных логик, расположенный в директории src/test/isolation. Инструмент поддерживает множественные подключения и полезен, когда требуется воспроизвести ошибки или протестировать новые логики, связанные с конкурентными запросами.

Valgrind

Чтобы воспользоваться Valgrind, необходимо поправить src/include/pg_config_manual.h, установив директиву #define USE_VALGRIND. Затем запустить postmaster под Valgrind с необходимым уровнем подавления. По использованию также см. Valgrind.

Другие тесты времени исполнения

Некоторые разработчики используют такие инструменты, как perf (из ядра Linux, perf.wiki.kernel.org), gprof (поставляемый в составе GNU-пакета binutils), ftrace, dtrace и oprofile (oprofile.sourceforge.net) для профилирования, например.

Как на счет юнит-тестов, статического анализа кода, проверки моделей...?

Был ряд обсуждений на счет других методик тестирования, и ряд разработчиков занимается их исследованием.

Стоит помнить, что Makefiles не содержит правильные зависимости для включаемых файлов. Необходимо выполнять make clean, а затем уже сборку. Если применяется GCC, можно использовать флаг --enable-depend, чтобы configure вычислял зависимости автоматом.

Я разработал патч, что дальше?

Патч необходимо предоставить на hackers. Чтобы упростить проверку вашего патча и ускорить фиксацию в мастер, пожалуйста, следуйте инструкции .

Что происходит дальше с моим патчем?

Патч проверяется другими разработчиками проекта и по результатам, либо принимается, либо отправляется на доработку. Процесс описан более подробно в предоставлении патчей.

Могу ли я помочь с проверкой патчей?

Ваше желание внести вклад, проверяя патчи из очереди CommitFest, всегда приветствуется! Пожалуйста, прочтите руководство по проверке патчей, чтобы получить более подробную информацию.

Необходимо ли передавать авторские права?

Нет. Разработчики сохраняют свои права, во всяком случае, в большинстве Европейских стран. Они просто считаются членами Postgres Global Development Group. Более того, передать права PGDG вовсе невозможно, потому что группа не является юридическим лицом. Все организовано по аналогии с Linux Kernel и большинством проектов свободного ПО.

Могу ли я добавить свое авторство?

Не делайте это, пожалуйста. Нам нравится четкость и краткость. Более того, возможны проблемы при использовании корпоративными пользователями.

Требует ли лицензия PostgreSQL сохранять уведомление об авторских правах неизменными?

Не требует. Это обосновано тем, что PostgreSQL Global Development Group охватывает всех правообладателей. Стоит обратить внимание, закон США не требует уведомлений об авторских правах для получения права на эксплуатацию, как и в большинстве европейских стран.

Технические вопросы

Как более эффективно работать с данными системных каталогов в коде?

Искать кортежи (ряды) можно двумя способами. Первый, SearchSysCache() и связные функции позволяют запрашивать системные каталоги, используя созданные системные индексы. Этот способ доступа к системным таблицам предпочитаемый, потому что первый вызов наполняет кэш необходимыми рядами, а последующие вызовы смогут возвращать данные без обращения к таблицам. Перечень доступных типов кэш можно найти в src/backend/utils/cache/syscache.c. src/backend/utils/cache/lsyscache.c содержит множество поисковых функций, завязанных на конкретные колонки.

Возвращенные ряды это кэшированные версии из кучи. Нельзя изменять или удалять кортежи, возвращенные SearchSysCache(). После использования данных, их необходимо высвободить с помощью вызова ReleaseSysCache(), который уведомляет кэшер о возможности выбросить данные при необходимости. Пренебрежение вызовом ReleaseSysCache() может приводить к блокировкам записей кэша до завершения транзакции, что допустимо при разработке, но никак не в продукционном коде.

Если по реализуемой логике использование системного кэша недопустимо, понадобится извлечение данных напрямую из кучи с использованием буферного кэша, разделяемого между рабочими процессами. Процесс берет управление над загрузкой рядов в буферный кэш. Для этого нужно открыть таблицу с помощью вызова heap_open(). Далее можно начать сканирование таблицы с помощью heap_beginscan(), а получать данные с помощью вызова heap_getnext(), продолжая пока HeapTupleIsValid() не вернет ложь. Для завершения сканирования нужно вызвать heap_endscan(). Для сканирования можно назначить ключи. При этом индексы не используются, и происходит сравнение рядов по ключам, чтобы вернуть нужные.

heap_fetch() можно использовать для извлечения рядов блоками данных. При сканировании с помощью heap_fetch() происходит автоматический захват рядов из буферного кэша, при этом необходимо передавать указатель Buffer, и после завершения вызвать ReleaseBuffer().

После получения ряда, можно извлечь данные, общие для всех кортежей, такие как t_self и t_oid, обратившись к полям структуры HeapTuple. Если требуется получить конкретную колонку таблицы, необходимо получить указатель HeapTuple, и передать в макрос GETSTRUCT(), чтобы получить начало кортежа таблицы. После необходимо привести указатель, например, к Form_pg_proc, если осуществляется доступ к таблице pg_proc, или Form_pg_type, если к pg_type. Далее можно обращаться к полям ряда, используя указатель на структуру:

((Form_pg_class) GETSTRUCT(tuple))->relnatts

Стоит помнить, что это справедливо только для колонок фиксированной длинны и не содержащих null-значения, и перед которыми также идут фиксированные колонки без null 'ов. В противном случае расположение колонок вариативно, и необходимо использовать heap_getattr() или аналогичные функции для извлечения из кортежа.

Также стоит избегать прямого сохранения в структуру полей, те. по факту изменения живых рядов. Лучше использовать функцию heap_modifytuple(), передав ей исходный кортеж и измененные значения. В результате вызова получается palloc-кортеж, который затем необходимо передать heap_update(). Чтобы удалить кортеж, нужно передать его t_self функции heap_delete(). t_self также необходимо передавать функции heap_update(). Помните! Кортежи могут быть копиями системного кэша, которые могут удалиться после вызова ReleaseSysCache(), или результатом прямого чтения с диска, который может удалиться после вызова heap_getnext(), heap_endscan(), или ReleaseBuffer(), при использовании heap_fetch(). Также кортеж может представлять запись palloc, которую после завершения работы с ней необходимо высвободить, вызвав pfree().

Почему имена таблиц, колонок, типов, функций, представлений иногда возвращаются как Name или NameData, а иногда как char *?

Имена таблиц, колонок, типов, функций и представлений хранятся в колонках системных таблиц с типом Name. Name это тип фиксированной длинны, завершенный null-терминатором и имеющий длину в NAMEDATALEN байт. По умолчанию значение NAMEDATALEN составляет 64 байта.

  typedef struct nameData
  {
      char    data[NAMEDATALEN];
  } NameData;
  typedef NameData *Name;

Имена таблиц, колонок, типов, функций и представлений, которые приходят на рабочий процесс из пользовательских запросов хранятся в символьных строках переменной длины и завершаются null-терминаторами.

Многие функции поддерживают оба типа имен, например, heap_open(). Т.к. тип Name завершен null-терминатором, передавать его в функции, ожидающие char *, безопасно. Существует множество мест, где производится сравнение имен, получаемых с диска (Name) с именами, передаваемыми пользователями (char *), и при этом типы взаимозаменяемы.

Почему используются Node и List для создания структур данных?

Это позволяет гибко передавать целостные данные рабочему процессу. Каждый узел имеет NodeTag, который указывает тип данных, содержащийся в Node. List представляет собой группы Node, объединенные вместе односвязным списком с указателем на первый элемент. Сортировка элементов списка может быть накладной в некоторых ситуациях.

Ниже приведены команды манипуляции List:

lfirst(i)
lfirst_int(i)
lfirst_oid(i)
возвращает данные (указатель, integer или OID соответственно) ячейки i из списка.
lnext(i)
возвращает из списка следующую за i ячейку.
foreach(i, list)
пробегается по списку, присваивая ячейку переменной i на каждой итерации.

Важно понимать то, что i присваивается ListCell *, а не данные ячейки List. Необходимо использовать один из вариантов lfirst(), чтобы получить данные ячейки.

Ниже приведен типичный кусок кода, который пробегается по List, содержащему Var * ячеек и обрабатывает их:

 List        *list;
 ListCell    *i;
 ...
 foreach(i, list)
 {
   Var *var = (Var *) lfirst(i);
   ...
   /* var обрабатывается здесь */
 }
lcons(node, list)
добавить node в начало списка, или создать новый список с node, если вместо list передан NIL.
lappend(list, node)
добавить node в конец list.
list_concat(list1, list2)
объединить list2, добавив его в конец list1.
list_length(list)
вернуть длину list.
list_nth(list, i)
вернуть i-ый элемент из list, считая от нулевого.
lcons_int, ...
существуют integer-версии функций: lcons_int(), lappend_int(), и т.д. А также версии для работы с OID: lcons_oid(), lappend_oid(), и т.д.

Можно с легкостью вывести узлы с помощью gdb. Первое, необходимо отключить обрезку вывода для команды print:

(gdb) set print elements 0

Вместо вывода значений в формате gdb, можно воспользоваться следующими командами для вывода List, Node и содержимого структур в более подробном и удобном для восприятия виде. List разворачивается в подробно распечатанные узлы. Первая команда выводит в краткой форме, а вторая команда - более подробной:

(gdb) call print(any_pointer)
(gdb) call pprint(any_pointer)

Вывод производится в лог-файл сервера, либо на консоль, если рабочий процесс запущен без головного postmaster.

Только что добавил поле в структуру, что еще необходимо предпринять?

Структуры, передаваемые в логиках парсера, реврайтера, оптимизатора и исполнителя запросов не требуют сложного сопровождения. Большинство структур каталога src/backend/nodes используются для создания, копирования, чтения и их вывода. Большинство типов узлов сопровождаются функциями из copyfuncs.c и equalfuncs.c, а некоторые из outfuncs.c и readfuncs.c. При добавлении нового поля необходимо внести сопровождающие функции в эти файлы. Найдите другие места, где может понадобиться сопровождающий код для нового поля, через поиск, например, уже существующих полей структуры. mkid может помочь в этом, см. раздел #Какие инструменты доступны разработчикам?.

Почему используются функции palloc() и pfree() при работе с памятью?

palloc() и pfree() используются вместо malloc() и free(), потому что так проще автоматически освобождать всю память, выделенную для запроса, после его завершения. Всегда есть уверенность в том, что вся выделенная память будет высвобождена в любом случае, даже, когда мы не следим за тем, где она выделена. Есть несколько контекстов, не связанных с выполнением запросов, где также выделяется память. Механизм помогает при высвобождении памяти рабочим процессом.

Можно получить снимок по этим контекстам памяти для, например, отлова утечек памяти. См. #Анализ используемой памяти рабочего процесса.

Что такое ereport()?

ereport() используется для отправки сообщений на фронт-енд, и опционально прекращает выполнение текущего запроса. См. как использовать более подробно.

Что такое CommandCounterIncrement()?

Обычно запросы не видят модифицируемые ряды, что позволяет UPDATE foo SET x = x + 1 работать корректно.

Однако существуют ситуации, в которых транзакции необходимо увидеть измененные в ее предшествующих частях ряды. Это выполняется с помощью Command Counter. Увеличивая счетчик можно разбить транзакцию на кусочки таким образом, чтобы каждый из них мог видеть изменения, произведенные предыдущими. CommandCounterIncrement() увеличивает Command Counter, создавая очередную часть транзакции.

Мне необходимо внести изменения в парсер запросов. Можете кратко осветить файлы, относящиеся к парсеру?

Файлы парсера расположены в директории src/backend/parser.

scan.l содержит лексику, описывающую алгоритм разбиения строк SQL-запросов, в потоки лексем. Лексема обычно представляет собой отдельное слово без пробелов, но выделенное с их помощью. Лексемы могут заключаться в одинарные и двойные кавычки. Лексика представляет собой набор регулярных выражений, описывающих различные типы лексем.

gram.y содержит грамматику, т.е. синтаксическую структуру SQL-выражений, на основе полученных с помощью лексики лексем в качестве строительных блоков. Грамматика реализована согласно BNF-нотации. BNF похожа на регулярные выражения, но работает на уровне лексем, а не символов. Также, шаблоны (правила или продукты согласно BNF) носят имена и могут быть рекурсивными.

Лексика (lexer) генерируется из scan.l инструментом flex. Найти руководство можно здесь

Грамматика (parser) формируется на основании gram.y инструментом bison. Руководство расположено здесь.

Стоит уделить значительное внимание изучению flex и bison при отсутствии практического опыта работы с ними.

У меня возник конфликт shift/reduce, который непонятно как решить

См. Fixing_shift/reduce_conflicts_in_Bison

Как посмотреть план запроса или разобранный запрос?

Часто требуется понимать структуру разобранного запроса или ожидаемый план его выполнения. PostgreSQL хранит эти данные в виде иерархических деревьев, которые можно распечатать в удобном формате.

Можно использовать функцию pprint для вывода этих данных в stderr рабочего процесса, а затем вытащить из лог-файла, например. Обычно функцию вызывают из подключенного к рабочему процессу отладчика, например, gdb или MSVC до выполнения интересующего запроса, предварительно установив точки прерывания в парсере, реврайтере, оптимизаторе и/или исполнителе запросов, в зависимости от решаемой задачи. После срабатывания точки прерывания выполните:

call pprint(theQueryVariable)

где theQueryVariable - любой требуемый Node* с типом, понятным вызову pprint. Распространена практика вывода различных частей запроса, например, списка целевых объектов и др.

Функциональность крайне полезна в совокупности с точками трассировки отладчиков gdb или MSVC.

Что предлагается для отладки?

Время компиляции

Первое, при разработке нового кода на C, собирать всегда нужно с флагами --enable-cassert и --enable-debug. Включенные утверждения проводят большое количество дополнительных проверок. Включенная отладкапозволяет использовать отладчик, например, gdb для трассировки сбойного кода. При компиляции с использованием gcc, дополнительные параметры -ggdb -Og -fno-omit-frame-pointer, переданные через cflags, крайне полезны, тк. добавляют дополнительную отладочную информацию. Передать их configure можно, например, так:

./configure --enable-cassert --enable-debug CFLAGS="-ggdb -Og -fno-omit-frame-pointer"

-O0 вместо -Og выключит большинство оптимизаций компилятора, включая inlining.

-fno-omit-frame-pointer полезен при трассировке и профилировании с использованием perf, например, тк. подобные средства могут захватывать полный стэк вызовов помимо верхнего уровня.

Время исполнения

Сервер postgres может принимать флаг -d, позволяющий выводить подробную информацию в лог-файл (отладка с помощью elog или ereport). Флаг передает значение уровня отладки. Чем выше уровень, тем быстрее будет расти лог-файл. Флаг недоступен при запуске сервера через команду pg_ctl, но можно использовать конструкцию -o log_min_messages=debug4, например.

gdb

Если запущен головной postmaster, запустите в одном терминале psql, найдите PID процесса postgres, созданного в результате выполнения SELECT pg_backend_pid() из консоли psql. Подключите отладчик к процессу postgres по PID, например, gdb -p 1234, или из уже запущенного gdb - attach 1234. Также может пригодиться этот скрипт. Можно установить точки прерывания в отладчике, и далее выполнить запрос из консоли psql.

Чтобы найти место генерации ошибки или сообщения в лог, установите точку прерывания на errfinish, это позволит отловить все вызовы elog и ereport в зависимости от уровня логгирования. Если же требуется найти лишь ERROR/FATAL/PANIC, используйте условные точки прерывания в gdb для errordata[errordata_stack_depth].elevel >= 20, или установите точку прерывания на строке с errfinish, используя нужное условие PANIC, FATAL, и ERROR. Стоит помнить, что не все ошибки проходят через errfinish, в частности, проверки разрешений идут особняком. Если на установленной точке исполнение не прерывается, выполните git grep с текстом искомой ошибки, и посмотрите, где возбуждается эта ошибка.

Если отлаживается логика на этапе начала сессии, можно установить PGOPTIONS="-W n", затем запустить psql. Опция отложит запуск на n-секунд, что позволит подключить отладчик к процессу и установить необходимые точки прерывания.

Автономный рабочий процесс

Можно запустить рабочий процесс из командной строки и передавать SQL-запросы напрямую в обход головного postmaster. Обычно это не требуется, тк. среда не столь дружелюбна по сравнению с psql (нет истории запросов, например), и нет возможности проверить конкурентные логики. Это может понадобиться, если сломался initdb, например.

Поломал initdb, как его отладить?

Иногда исправления приводят к отказу initdb. Редко бывает, чтобы проблема заключалась в самом initdb. Чаще сбои происходят в рабочих процессах postgres, запущенных через initdb.

Если процесс падает или отрабатывает утверждение, подключенный gdb к initdb вряд ли поможет. initdb не поломан, а значит gdb не остановится.

Что нужно сделать, так это запустить initdb с подключенным gdb, установив точку прерывания на fork, а затем продолжить исполнение. Когда будет достигнута точка прерывания, остановите функцию командой finish. gdb выведет сообщение о созданном дочернем процессе, и по факту это не цель, а сигнал, что создан реальный экземпляр postgres. Пока initdb на паузе, можно воспользоваться командой ps, чтобы найти идентификатор запущенного процесса postgres. pstree -p также может оказаться полезным. Как будет получен идентификатор, подключите отдельную сессию gdb с помощью команды gdb -p $the_postgres_pid, после чего можно безопасно отключить gdb от initdb, и отлаживать сбойный процесс postgres.

Профилирование производительности, утилизация CPU

Существуют разные способы профилирования PostgreSQL, но наиболее популярный на текущий момент это perf, инструмент из ядра Linux. См. Профилирование с помощью perf.

perf крайне мощный инструмент, не ограниченный профилированием лишь CPU, и полезен в качестве средства трассировки.

PostgreSQL можно скомпилировать с включенной опцией профилирования, чтобы увидеть время, затрачиваемое на исполнение той или иной функцией. Рекомендуется конфигурация с помощью флага --enable-profiling. Файлы профилирования серверных процессов складываются в директорию pgsql/data. Файлы профилирования клиентов, таких как psql, складываются в текущей на стороне клиента директории.

Обычно не требуется использовать --enable-cassert или любой из пользовательских -O флагов, например, -Og/-O0 при исследовании проблем производительности. При включенной проверке cassert вносятся дорогостоящие накладные расходы, что в свою очередь искажает данные профилирования. Важно удостовериться, что профилирование происходит при тех же параметрах оптимизации компилятора, что и реальный запуск.

--enable-debug прекрасно подходит, когда профилирование производится с использованием gcc, для других же компиляторов лучше этот флаг не использовать.

perf в составе современных Linux-систем является более удобной альтернативой флагу --enable-profiling.

Анализ используемой памяти рабочего процесса

palloc в PostgreSQL представляет собой механизм выделения памяти с иерархической организацией и является оберткой для вызовов ОС. См. раздел #Почему используются функции palloc() и pfree() при работе с памятью?.

Память, выделенная с помощью palloc, назначается контексту памяти, лежащему в иерархии с корнем TopMemoryContext. Каждый контекст памяти именован.

Можно снять данные статистики по интересующему контексту памяти, включая дочерние узлы, вызвав функцию MemoryContextStats(MemoryContext*). Обычно делается так:

gdb -p $the_backend_pid
(gdb) p MemoryContextStats(TopMemoryContext)

Вывод производится в лог-файл сервера, либо на консоль, если рабочий процесс запущен без головного postmaster.

gdb/MSVC точки трассировки

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

MSVC и gdb могут работать с точками трассировки. Этот инструмент более мощный по сравнению с perf, где требуется отладчик. По gdb, см. точки трассировки в gdb. Точки трассировки также можно использовать для создания снимков контекста памяти, или вывода дерева разобранных запросов, и тд.