1. Архитектура PostgreSQL: процессы, память и модель MVCC
Архитектура PostgreSQL: процессы, память и модель MVCC
PostgreSQL — это не просто хранилище данных, а сложная операционная система внутри вашей операционной системы. Чтобы писать эффективные запросы и администрировать базу данных, необходимо понимать, как она устроена «под капотом». В этой статье мы разберем три кита, на которых держится PostgreSQL: процессная модель, управление памятью и механизм многоверсионности (MVCC).
Процессная модель: Client-Server
В отличие от многих современных приложений, использующих потоки (threads), PostgreSQL опирается на процессы. Это фундаментальное архитектурное решение обеспечивает высокую стабильность: сбой в одном процессе обработки запроса не обрушивает всю базу данных.
Главный процесс (Postmaster)
Когда вы запускаете PostgreSQL, первым стартует процесс postgres (часто называемый Postmaster). Его задачи:
Обслуживающие процессы (Backends)
Для каждого нового клиента Postmaster создает (форкает) отдельный процесс — Backend. Если у вас 100 активных клиентов, в системе будет 100 процессов postgres, обслуживающих эти соединения. Именно поэтому в PostgreSQL так важно использовать пулеры соединений (например, PgBouncer), чтобы не перегружать сервер созданием тысяч процессов.
Фоновые процессы (Background Workers)
Помимо работы с клиентами, база данных выполняет множество служебных задач. Этим занимаются специализированные процессы:
* Background Writer: сбрасывает «грязные» (измененные) страницы из памяти на диск, чтобы освободить место для новых данных. * Checkpointer: выполняет контрольные точки (checkpoints), гарантируя, что все данные до определенного момента записаны на диск. * Autovacuum Launcher: следит за необходимостью очистки таблиц от мусора. * WalWriter: записывает журнал предзаписи (WAL) на диск.
!Взаимодействие процессов в PostgreSQL
Управление памятью
Память в PostgreSQL строго разделена на две категории: общая (доступная всем процессам) и локальная (личная память каждого процесса).
Общая память (Shared Memory)
Это область, выделяемая при старте сервера. Самый важный компонент здесь — Shared Buffers (общий буферный кеш).
PostgreSQL не работает с данными напрямую на диске. Чтобы прочитать или изменить строку, база данных сначала должна загрузить страницу (блок данных размером обычно 8 КБ), содержащую эту строку, в Shared Buffers. Если вы делаете UPDATE, изменение происходит именно в памяти. На диск оно попадет позже (благодаря Checkpointer или Background Writer).
Также в общей памяти находится WAL Buffers — буфер для журнала предзаписи, куда временно сохраняются транзакции перед фиксацией на диске.
Локальная память (Local Memory)
Каждый Backend-процесс запрашивает память для выполнения своих запросов. Ключевые параметры:
work_mem: память для операций сортировки (ORDER BY), хеширования (Hash Join) и группировки. Важно: этот лимит выделяется на каждую операцию* внутри запроса. Если сложный запрос имеет 5 сортировок, он может потребовать .
* maintenance_work_mem: память для операций обслуживания, таких как создание индексов (CREATE INDEX) или VACUUM.
* temp_buffers: буфер для временных таблиц.
MVCC: Многоверсионное управление конкурентным доступом
Самая интересная и сложная часть PostgreSQL — это MVCC (Multi-Version Concurrency Control). Это механизм, который позволяет множеству пользователей читать и писать данные одновременно, не блокируя друг друга.
Главное правило MVCC: > «Пишущие не блокируют читающих, а читающие не блокируют пишущих».
В классических системах блокировок, если кто-то редактирует строку, никто другой не может её прочитать до завершения транзакции. В PostgreSQL это не так. Каждый видит согласованный снимок данных (Snapshot) на момент начала своей транзакции.
Как это работает: Версии строк
Когда вы делаете UPDATE, PostgreSQL не перезаписывает старую строку на диске. Вместо этого он:
Таким образом, в таблице могут одновременно существовать несколько версий одной и той же строки. Транзакция А может видеть старую версию, а транзакция Б — новую.
!Принцип создания версий строк при обновлении
Служебные поля: xmin и xmax
Чтобы понять, какую версию строки нужно показать конкретной транзакции, PostgreSQL добавляет к каждой строке скрытые служебные поля. Самые важные из них:
* xmin: ID транзакции, которая создала эту версию строки.
* xmax: ID транзакции, которая удалила (или обновила) эту версию строки.
Рассмотрим пример на числах. Допустим, текущий ID транзакции = 100.
xmin = 100, xmax = 0 (или NULL). Это значит: строка жива, создана транзакцией 100.xmax = 101. Теперь строка считается удаленной для всех транзакций, начавшихся после 101.DELETE + INSERT.xmin = 100, xmax = 102 (помечена как удаленная).
* Новая версия: xmin = 102, xmax = 0 (актуальная).Когда вы делаете SELECT, PostgreSQL проверяет эти поля и сравнивает их с ID вашей текущей транзакции, чтобы решить: «Вижу я эту строку или нет?».
Более подробно о механике версионности можно прочитать в книге postgrespro.ru или в статье про MVCC.
Проблема «мертвых душ» и VACUUM
Из-за механизма MVCC таблицы накапливают «мертвые кортежи» (dead tuples) — старые версии строк, которые больше не нужны ни одной активной транзакции. Если их не чистить, таблица раздуется (bloat), и запросы станут медленными, так как базе придется читать больше данных с диска.
Для решения этой проблемы существует процесс VACUUM (и его автоматическая версия Autovacuum). Он сканирует таблицы, находит мертвые версии строк и помечает занимаемое ими место как свободное для повторного использования.
Изоляция транзакций
MVCC является основой для уровней изоляции транзакций. В PostgreSQL по умолчанию используется уровень Read Committed. Это означает, что запрос видит данные, зафиксированные на момент начала выполнения этого запроса.
Более строгий уровень — Repeatable Read — гарантирует, что транзакция видит снимок данных на момент начала первой инструкции в транзакции, и этот снимок не меняется до самого конца, даже если другие транзакции параллельно меняют данные.
Итоги
work_mem).UPDATE) создает новую версию строки, а не перезаписывает старую. Это позволяет читать данные без блокировок со стороны пишущих транзакций.