1. Основы реляционных баз данных и архитектура PostgreSQL
Основы реляционных баз данных и архитектура PostgreSQL
В 1970 году Эдгар Кодд, математик из IBM, опубликовал статью, которая навсегда изменила мир ИТ. Он предложил отказаться от запутанных иерархических структур данных в пользу строгой математической модели, основанной на теории множеств. Спустя десятилетия, несмотря на взлет NoSQL, графовых и векторных баз данных, реляционная модель остается фундаментом индустрии. Но понимание того, как данные ложатся на диск и почему PostgreSQL считается «самой продвинутой open-source СУБД в мире», требует выхода за рамки простого написания запросов. На техническом интервью уровня Middle+ вас не спросят, как работает SELECT, вас спросят, почему ваш SELECT заставляет страдать диск и оперативную память.
Реляционная парадигма: от кортежей к таблицам
Реляционная модель данных строится на понятии отношения (relation). В математическом смысле отношение — это подмножество декартова произведения доменов. В инженерном смысле — это таблица. Однако важно понимать, что таблица в реляционной БД — это не просто «Excel-файл». Она обладает строгими свойствами:
ORDER BY СУБД имеет право возвращать данные как ей удобно.Ключевым инструментом обеспечения связности данных является использование ключей.
Представьте систему управления складом. У нас есть таблица products и таблица categories. Если мы удалим категорию «Электроника», когда в таблице товаров на нее ссылаются 500 позиций, система без контроля ссылочной целостности превратится в «кладбище битых ссылок». PostgreSQL предотвращает это на уровне ядра, используя ограничения (constraints), что делает базу данных не просто хранилищем, а гарантом бизнес-логики.
Процессная модель PostgreSQL: кто выполняет ваш запрос?
В отличие от многих других СУБД (например, MySQL или MS SQL Server), которые используют потоковую модель (threads), PostgreSQL исторически базируется на архитектуре process-per-connection. Это фундаментальное различие, которое часто становится темой для обсуждения на интервью по системному дизайну.
Когда клиентское приложение (например, ваш бэкенд на Python или Java) подключается к PostgreSQL, происходит следующее:
postgres в современных версиях) — прослушивает сетевой порт (по умолчанию 5432).fork().Этот подход дает невероятную надежность: если один запрос вызовет критическую ошибку (например, из-за бага в расширении на языке C), упадет только один процесс обслуживания клиента, а не вся база данных. Однако у этого есть цена — создание процесса в ОС обходится «дороже», чем создание потока. Именно поэтому в высоконагруженных системах с PostgreSQL обязательным является использование Connection Poolers (например, PgBouncer или Odyssey), которые позволяют держать тысячи соединений, переиспользуя ограниченный пул процессов бэкенда.
Анатомия памяти: Shared Buffers и Local Memory
PostgreSQL управляет памятью очень экономно, разделяя ее на две большие категории: общую (Shared) и локальную (Local).
Shared Memory (Общая память)
Это область, доступная всем процессам PostgreSQL одновременно. Главный житель здесь — Shared Buffers. Когда вы выполняете запрос, PostgreSQL никогда не читает данные напрямую с диска в ваше приложение. Сначала страница данных (размером 8 КБ) копируется с диска в Shared Buffers. Если другой пользователь запросит те же данные, они уже будут в памяти. > Эффективность кэширования в Shared Buffers напрямую определяет производительность системы. На интервью часто спрашивают: «Какого размера должен быть shared_buffers?». Классический ответ: около 25% от общего объема оперативной памяти сервера, так как PostgreSQL также полагается на кэш операционной системы (OS Page Cache).Помимо буферов, в общей памяти находятся:
Local Memory (Локальная память)
Каждый процесс бэкенда имеет свою частную память. Самый важный параметр здесь — work_mem. Эта память используется для операций сортировки (ORDER BY), группировки (DISTINCT, GROUP BY) и построения хэш-таблиц для соединений (HASH JOIN).
Важный нюанс: лимит work_mem задается не на запрос, а на каждую операцию внутри запроса. Если в вашем сложном запросе 4 сортировки и 3 хэш-соединения, он может потребовать в 7 раз больше памяти, чем указано в work_mem. Если памяти не хватает, PostgreSQL начинает сбрасывать временные данные на диск (создавать temp files), что замедляет запрос в десятки раз.Физическая структура: как данные лежат на диске
На логическом уровне мы видим таблицы и схемы. На физическом уровне PostgreSQL оперирует файлами и страницами.
Каждая таблица — это набор файлов в директории данных (PGDATA). Основной файл данных разбит на страницы (pages) фиксированного размера (обычно 8 КБ).
Структура страницы 8 КБ:
Почему это важно? PostgreSQL реализует механизм MVCC (Multi-Version Concurrency Control). Когда вы обновляете строку (UPDATE), старая версия строки не заменяется новой. Вместо этого создается новая версия строки в свободном месте страницы (или на другой странице), а старая помечается как «мертвая» (dead tuple). Это позволяет читающим процессам не блокировать пишущие: кто-то читает старую версию, кто-то пишет новую.
Однако это приводит к «раздуванию» (bloat) таблиц. Очисткой этого мусора занимается процесс Autovacuum. Понимание работы MVCC и Autovacuum — это «золотой стандарт» вопросов на собеседовании. Если кандидат не знает, почему UPDATE в Postgres — это фактически DELETE + INSERT, он не сможет оптимизировать высоконагруженную БД.
Жизненный цикл запроса: от парсера до исполнителя
Когда SQL-запрос попадает в бэкенд-процесс, он проходит через конвейер обработки:
ANALYZE) планировщик оценивает стоимость (cost) разных способов выполнения запроса. Использовать ли индекс? Или проще просканировать всю таблицу (Seq Scan)? В каком порядке соединять таблицы?Рассмотрим пример оценки стоимости. Допустим, у нас есть запрос:
SELECT * FROM users WHERE id = 42;
Если на id есть индекс, планировщик сравнит:
WHERE status = 'active' и 90% пользователей активны, планировщик выберет Seq Scan, так как это быстрее, чем прыгать по индексу для каждой строки.Надежность и WAL: почему данные не пропадают
База данных должна гарантировать сохранность данных даже при внезапном отключении питания. В PostgreSQL это решается через Write-Ahead Logging (WAL). Принцип прост: любые изменения сначала записываются в лог транзакций (WAL) на диск, и только потом фиксируются в основных файлах данных. Запись в WAL — это последовательная запись (sequential I/O), которая выполняется очень быстро. Запись в файлы данных — это случайная запись (random I/O), которая медленнее.
Если сервер упадет, при перезагрузке PostgreSQL прочитает WAL и «проиграет» все зафиксированные транзакции, которые еще не успели попасть в основные файлы данных. Это обеспечивает соблюдение принципа Durability (D в аббревиатуре ACID).
Системный каталог: мозг базы данных
PostgreSQL хранит информацию о себе в самой себе. Системный каталог — это набор служебных таблиц в схеме pg_catalog.
pg_class: информация о таблицах, индексах и представлениях.pg_attribute: информация о колонках.pg_type: информация о типах данных (Postgres невероятно расширяем в плане типов).pg_stats: статистика распределения данных, используемая планировщиком.Профессионал SQL знает, как заглянуть «под капот». Например, запрос к pg_stat_activity покажет все текущие выполняющиеся запросы в системе, а pg_stat_user_tables поможет найти таблицы, которые давно не очищались вакуумом или по которым идет слишком много последовательных сканирований вместо индексных.
Резюмируя архитектурный подход
PostgreSQL — это сложная экосистема, где каждый компонент сбалансирован. Процессная модель обеспечивает изоляцию, Shared Buffers оптимизирует работу с диском, MVCC позволяет работать без блокировок чтения, а WAL гарантирует надежность. Понимание этих основ позволяет не просто писать запросы, а предсказывать поведение системы под нагрузкой.
В следующей главе мы перейдем от внутреннего устройства к практике: научимся манипулировать данными, используя расширенные возможности фильтрации и специфические операторы PostgreSQL, которые делают код лаконичным и производительным.