1. Процессная модель и жизненный цикл соединения: от Postmaster до Backend-процесса
Процессная модель и жизненный цикл соединения: от Postmaster до Backend-процесса
Если вы заглянете в диспетчер задач операционной системы на сервере с активно работающим PostgreSQL, вы не увидите одного монолитного процесса, утилизирующего все ресурсы. Вместо этого перед вами предстанут десятки, а иногда и сотни процессов с именем postgres. В отличие от многих современных многопоточных систем (например, MySQL или веб-серверов вроде Nginx), PostgreSQL исторически и архитектурно опирается на многопроцессную модель (process-based architecture). Каждое клиентское подключение обслуживается отдельным тяжеловесным процессом операционной системы. Это фундаментальное дизайнерское решение определяет всё: от потребления оперативной памяти до способов защиты от сбоев и стратегий масштабирования.
Архитектура процессов: почему не потоки?
В операционных системах семейства Linux существует два основных способа конкурентной обработки задач: процессы и потоки (threads). Потоки делят между собой единое адресное пространство памяти — это делает их легковесными, они быстро создаются и требуют минимум накладных расходов на переключение контекста. Процессы же полностью изолированы друг от друга на уровне ядра ОС. Каждый процесс имеет собственную виртуальную память.
PostgreSQL использует процессы. Когда разработчики принимали это решение в 90-х годах, стандарты работы с потоками (POSIX threads) ещё не были окончательно сформированы и вели себя по-разному на разных Unix-системах. Процессная модель обеспечивала максимальную переносимость. Но сегодня главная причина сохранения этой архитектуры — надежность и изоляция.
Если в многопоточной СУБД из-за бага в коде или повреждения данных один поток выполнит недопустимую операцию с памятью (Segmentation fault), рухнет весь процесс СУБД целиком, оборвав соединения всех пользователей. В PostgreSQL изоляция процессов означает, что фатальная ошибка при выполнении конкретного SQL-запроса убьет только тот процесс, который его выполнял. Соседние клиентские сессии даже не заметят сбоя на уровне ОС (хотя сам кластер предпримет защитные действия, о которых пойдет речь ниже).
Обратная сторона этой надежности — высокая стоимость. Процесс ОС требует выделения структур в ядре, собственной памяти для стека и кучи. Базовое потребление памяти одним простаивающим соединением в PostgreSQL составляет от 2 до 10 мегабайт в зависимости от ОС и конфигурации, не считая памяти, которая потребуется для сортировок или хеширования при выполнении реального запроса.
Postmaster: Главный дирижёр кластера
Всё начинается с одного процесса. Когда systemd или утилита pg_ctl запускает кластер, стартует корневой процесс postgres. В терминологии администраторов и в исходном коде его традиционно называют Postmaster.
!Иерархия процессов PostgreSQL
Postmaster не выполняет SQL-запросы пользователей. Его задача — инициализация инфраструктуры и управление жизненным циклом остальных процессов. При старте он выполняет строго определенную последовательность действий:
После завершения инициализации Postmaster переходит в бесконечный цикл ожидания (event loop). Он спит, пока на открытом сетевом порту (по умолчанию 5432) или в Unix-сокете не появится сигнал о новом входящем соединении от клиента.
Жизненный цикл клиентского соединения
Когда приложение (например, ORM вашего бэкенда или консольный клиент psql) пытается подключиться к базе данных, запускается сложный механизм делегирования полномочий. Postmaster не может сам обрабатывать запросы клиента, иначе он был бы заблокирован и не смог бы принимать другие подключения.
!Механизм fork при новом соединении
Процесс установки соединения выглядит следующим образом:
fork(). Операционная система создает точную копию процесса Postmaster. Этот новый дочерний процесс называется Backend-процессом (или серверным процессом).exit(), уничтожая себя.Важно понимать, что системный вызов fork() — это относительно дорогая операция для операционной системы. Создание нового процесса занимает миллисекунды, но в масштабах высоконагруженной системы, где приложения могут открывать и закрывать сотни соединений в секунду, постоянный fork() становится узким местом (bottleneck), сжигающим ресурсы CPU.
Анатомия Backend-процесса и системный мониторинг
Каждый Backend-процесс жестко привязан к одной клиентской сессии и к одной конкретной базе данных (той, которая была указана при подключении). Переключиться на другую базу данных в рамках того же соединения невозможно — для этого клиент должен разорвать соединение и инициировать новое, что приведет к созданию нового Backend-процесса.
Администратор Linux может наблюдать за состоянием этих процессов напрямую через системные утилиты. Если выполнить команду ps -ef | grep postgres, вывод продемонстрирует текущее состояние кластера.
Типичный вывод содержит строки следующего вида:
postgres 12345 1234 0 10:00 ? 00:00:00 postgres: user app_db 192.168.1.10(45678) idle
Эта строка — не просто имя процесса. PostgreSQL динамически перезаписывает аргументы командной строки (argv) для своих Backend-процессов, чтобы администратор видел их статус без необходимости заходить в СУБД. В этой строке:
12345 — PID (Process ID) Backend-процесса.1234 — PPID (Parent PID), указывающий на Postmaster.user — роль, под которой выполнено подключение.app_db — база данных, к которой подключен клиент.192.168.1.10(45678) — IP-адрес и порт клиента.idle — текущее состояние процесса.Состояние (status tag) — критически важная метрика. Процесс может находиться в нескольких базовых состояниях:
BEGIN; и пару UPDATE), но сейчас ничего не делает, ожидая дальнейших команд от клиента. Это самое опасное состояние: такой процесс удерживает блокировки на строки таблиц и блокирует механизмы очистки старых данных (Autovacuum), что может привести к деградации производительности всего кластера.Цена соединения и пределы масштабирования
Понимание процессной модели дает четкий ответ на вопрос, почему параметр max_connections (максимальное число одновременных подключений) нельзя просто установить в значение 10000.
Допустим, каждое соединение потребляет в среднем 10 мегабайт оперативной памяти только на базовые структуры процесса. При 10000 соединений сервер потратит 100 гигабайт RAM исключительно на поддержание этих процессов, даже если 99% из них будут находиться в состоянии idle. Эта память будет отнята у операционной системы и у внутреннего кэша PostgreSQL (shared_buffers), что приведет к катастрофическому падению скорости чтения с диска.
Вторая проблема — планировщик задач ядра Linux (CPU Scheduler). Когда тысячи процессов одновременно просыпаются, чтобы выполнить короткий запрос, ядро начинает тратить больше процессорного времени на переключение контекста между процессами (context switching), чем на полезную работу (выполнение SQL). Нагрузка на CPU уходит в System (sys), а не в User (usr).
Именно поэтому в профессиональной эксплуатации PostgreSQL перед базой данных всегда ставят пулер соединений (Connection Pooler), такой как PgBouncer. Пулер держит тысячи легковесных соединений от клиентов, но к самому PostgreSQL открывает лишь несколько десятков реальных Backend-процессов, мультиплексируя клиентские запросы через них.
Управление сбоями: OOM Killer и защитный рестарт
Изоляция процессов защищает от многих проблем, но разделяемая память (Shared Memory) создает уязвимость. Все Backend-процессы читают и пишут в один и тот же участок памяти (shared_buffers) и используют механизмы межпроцессных блокировок (LWLocks, спинлоки) для синхронизации доступа.
Что произойдет, если Backend-процесс, выполняющий тяжелый запрос, исчерпает всю доступную оперативную память на сервере? Ядро Linux вызовет механизм OOM Killer (Out-Of-Memory Killer), который найдет процесс, потребляющий больше всего памяти, и принудительно убьет его сигналом SIGKILL.
С точки зрения ОС убит всего один процесс. Но с точки зрения PostgreSQL произошла катастрофа. Убитый Backend-процесс в момент смерти мог находиться в середине операции записи в разделяемую память. Он мог удерживать критическую блокировку на страницу данных или оставить структуры данных в памяти в несогласованном состоянии. Если другие Backend-процессы продолжат работу, они прочитают поврежденные данные или зависнут навсегда, ожидая снятия блокировки от мертвого процесса.
Postmaster, будучи супервизором, мгновенно обнаруживает нештатное завершение своего дочернего процесса (через сигнал SIGCHLD от ядра ОС). Понимая, что целостность разделяемой памяти скомпрометирована, Postmaster переходит в режим паники:
SIGQUIT (немедленное завершение) всем остальным работающим Backend-процессам. Все клиентские соединения обрываются с ошибкой the database system is in recovery mode.Этот механизм называется Crash Recovery. Он гарантирует, что логическая ошибка в одном процессе или агрессивное вмешательство ОС не приведут к физическому разрушению базы данных на диске. Однако для бизнеса это означает даун-тайм (от секунд до минут), пока кластер восстанавливается. Поэтому администраторы баз данных жестко контролируют локальную память процессов (параметр work_mem), чтобы не провоцировать OOM Killer, и настраивают ядро Linux (vm.overcommit_memory) на более предсказуемое поведение.
Архитектура процессов PostgreSQL — это компромисс между производительностью и абсолютной надежностью. Понимание того, что каждое соединение — это отдельный процесс со своей памятью и жизненным циклом, является фундаментом для траблшутинга производительности, настройки пулеров соединений и профилирования потребления ресурсов.